Merge "Rename PhoneNumberUtils APIs" into mnc-dev
diff --git a/Android.mk b/Android.mk
index 0a93c32..1440fcc 100644
--- a/Android.mk
+++ b/Android.mk
@@ -32,7 +32,6 @@
LOCAL_JAVA_LIBRARIES := telephony-common
LOCAL_STATIC_JAVA_LIBRARIES := \
android-common \
- android-ex-variablespeed \
android-support-v13 \
android-support-v4 \
android-support-v7-cardview \
@@ -42,8 +41,6 @@
guava \
libphonenumber
-LOCAL_REQUIRED_MODULES := libvariablespeed
-
LOCAL_PACKAGE_NAME := Dialer
LOCAL_CERTIFICATE := shared
LOCAL_PRIVILEGED_MODULE := true
diff --git a/res/layout/call_detail.xml b/res/layout/call_detail.xml
index c077851..5d1607e 100644
--- a/res/layout/call_detail.xml
+++ b/res/layout/call_detail.xml
@@ -87,6 +87,12 @@
</LinearLayout>
</LinearLayout>
+ <com.android.dialer.voicemail.VoicemailPlaybackLayout
+ android:id="@+id/voicemail_playback_layout"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:visibility="gone" />
+
<!--
The list view is under everything.
It contains a first header element which is hidden under the controls UI.
diff --git a/res/mipmap-xxxhdpi/ic_launcher_phone.png b/res/mipmap-xxxhdpi/ic_launcher_phone.png
index 26f51f1..8c92ac1 100644
--- a/res/mipmap-xxxhdpi/ic_launcher_phone.png
+++ b/res/mipmap-xxxhdpi/ic_launcher_phone.png
Binary files differ
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index 49a6bd9..cd2bc26 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -131,6 +131,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"Spoedbel"</string>
<string name="tab_recents" msgid="929949073851377206">"Onlangse kontakte"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"Kontakte"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"Stemboodskap"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"Verwyder uit gunstelinge"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"Ontdoen"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"Bel <xliff:g id="NUMBER">%s</xliff:g>"</string>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index 35c474d..ec86001 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -131,6 +131,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"ፈጣን ደውል"</string>
<string name="tab_recents" msgid="929949073851377206">"የቅርብ ጊዜዎቹ"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"ዕውቂያዎች"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"የድምፅ መልዕክት"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"ከተወዳጆች ውስጥ ተወግዷል።"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"ቀልብስ"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"ለ<xliff:g id="NUMBER">%s</xliff:g> ደውል"</string>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index 0f7fc02..86fd385 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -135,6 +135,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"اتصال سريع"</string>
<string name="tab_recents" msgid="929949073851377206">"الحديثة"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"جهات الاتصال"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"البريد الصوتي"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"تمت إزالة جهة الاتصال من المفضلة"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"تراجع"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"الاتصال بالرقم <xliff:g id="NUMBER">%s</xliff:g>"</string>
diff --git a/res/values-az-rAZ/strings.xml b/res/values-az-rAZ/strings.xml
index 4b5ef9b..e53efce 100644
--- a/res/values-az-rAZ/strings.xml
+++ b/res/values-az-rAZ/strings.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
-<!--
+<!--
~ Copyright (C) 2012 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
@@ -18,54 +18,53 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="applicationLabel" msgid="8490255569343340580">"Nömrə yığan"</string>
+ <string name="launcherActivityLabel" msgid="1129729740601172692">"Telefon"</string>
<string name="dialerIconLabel" msgid="6500826552823403796">"Telefon"</string>
- <string name="recentCallsIconLabel" msgid="1419116422359067949">"Zəng jurnalı"</string>
+ <string name="recentCallsIconLabel" msgid="2639489159797075507">"Zəng tarixçəsi"</string>
<string name="recentCalls_callNumber" msgid="1756372533999226126">"Zəng <xliff:g id="NAME">%s</xliff:g>"</string>
<string name="recentCalls_editNumberBeforeCall" msgid="7756171675833267857">"Zəng etmədən öncə nömrəyə düzəliş edin"</string>
<string name="recentCalls_addToContact" msgid="1429899535546487008">"Kontaktlara əlavə edin"</string>
- <string name="recentCalls_removeFromRecentList" msgid="401662244636511330">"Zəng jurnalından silin"</string>
- <string name="recentCalls_deleteAll" msgid="6352364392762163704">"Zəng jurnalını təmizləyin"</string>
+ <string name="recentCalls_removeFromRecentList" msgid="5551148439199439404">"Zəng tarixçəsindən sil"</string>
+ <string name="recentCalls_deleteAll" msgid="5157887960461979812">"Zəng tarixçəsini təmizlə"</string>
<string name="recentCalls_trashVoicemail" msgid="7604696960787435655">"Səsli məktubu silin"</string>
<string name="recentCalls_shareVoicemail" msgid="1416112847592942840">"Səsli məktubu paylaşın"</string>
- <string name="recentCalls_empty" msgid="247053222448663107">"Zəng jurnalı boşdur."</string>
- <string name="clearCallLogConfirmation_title" msgid="6427524640461816332">"Zəng jurnalı silinsin?"</string>
- <string name="clearCallLogConfirmation" msgid="5043563133171583152">"Bütün zəng qeydləriniz silinəcək."</string>
- <string name="clearCallLogProgress_title" msgid="8365943000154295771">"Zəng jurnalı silinir..."</string>
- <plurals name="notification_voicemail_title">
- <item quantity="one" msgid="1746619685488504230">"Səsli məktub"</item>
- <item quantity="other" msgid="5513481419205061254">"<xliff:g id="COUNT">%1$d</xliff:g> Səsli məktub"</item>
- </plurals>
+ <string name="recentCalls_empty" msgid="8555115547405030734">"Zəng yoxdur"</string>
+ <string name="clearCallLogConfirmation_title" msgid="801753155679372984">"Zəng tarixçəsi təmizlənsin?"</string>
+ <string name="clearCallLogConfirmation" msgid="7899552396101432827">"Bu, tarixçənizdən bütün zəngləri siləcəkdir"</string>
+ <string name="clearCallLogProgress_title" msgid="3372471156216306132">"Zəng tarixçəsi silinir…"</string>
+ <plurals name="notification_voicemail_title" formatted="false" msgid="9088953961148324851">
+ <item quantity="other"> <xliff:g id="COUNT">%1$d</xliff:g> Səsli poçt </item>
+ <item quantity="one">Səsli poçt</item>
+ </plurals>
<string name="notification_action_voicemail_play" msgid="6113133136977996863">"Oxudun"</string>
<string name="notification_voicemail_callers_list" msgid="1153954809339404149">"<xliff:g id="NEWER_CALLERS">%1$s</xliff:g>, <xliff:g id="OLDER_CALLER">%2$s</xliff:g>"</string>
<string name="notification_new_voicemail_ticker" msgid="895342132049452081">"<xliff:g id="CALLER">%1$s</xliff:g> adlı şəxsdən yeni səsli məktub"</string>
- <string name="voicemail_playback_error" msgid="1811242131549854624">"Səsli məktub oxudula bilmədi."</string>
+ <string name="voicemail_playback_error" msgid="3356071912353297599">"Səsli poçtu səsləndirmək mümkün deyil"</string>
<string name="voicemail_buffering" msgid="738287747618697097">"Buferlənir..."</string>
- <string name="voicemail_fetching_content" msgid="877911315738258780">"Səsli məktub əldə edilir..."</string>
- <string name="voicemail_fetching_timout" msgid="6691792377574905201">"Səsli məktub əldə edilə bilmədi."</string>
+ <string name="voicemail_fetching_content" msgid="1287895365599580842">"Səsli poçt yüklənir…"</string>
+ <string name="voicemail_fetching_timout" msgid="3959428065511972176">"Səsli poçtu yükləmək mümkün olmadı"</string>
<string name="call_log_voicemail_header" msgid="3945407886667089173">"Yalnız səsli məktublu zənglər"</string>
<string name="call_log_incoming_header" msgid="2787722299753674684">"Yalnız daxil olan zənglər"</string>
<string name="call_log_outgoing_header" msgid="761009180766735769">"Yalnız gedən zənglər"</string>
<string name="call_log_missed_header" msgid="8017148056610855956">"Yalnız buraxılmış zənglər"</string>
- <string name="voicemail_status_voicemail_not_available" msgid="3021980206152528883">"Səsli poçt serverinə birləşmək olmur."</string>
- <string name="voicemail_status_messages_waiting" msgid="7113421459602803605">"Səsli poçt serveri ilə bağlantı yaratmaq olmur. Yeni səsli poçtlar gözlənilir."</string>
- <string name="voicemail_status_configure_voicemail" msgid="3738537770636895689">"Səsli poçtunuzu quraşdırın."</string>
- <string name="voicemail_status_audio_not_available" msgid="3369618334553341626">"Audio əlçatımlı deyil."</string>
+ <string name="voicemail_status_voicemail_not_available" msgid="5222480147701456390">"Səsli poçt yeniləmələri mövcud deyil"</string>
+ <string name="voicemail_status_messages_waiting" msgid="6329544650250068650">"Yeni səsli poçt gözləyir. İndi yükləmək mümkün deyil."</string>
+ <string name="voicemail_status_configure_voicemail" msgid="8300808991932816153">"Səsli poçtunuzu qurun"</string>
+ <string name="voicemail_status_audio_not_available" msgid="2449801102560158082">"Audio mövcud deyil"</string>
<string name="voicemail_status_action_configure" msgid="8671796489912239589">"Quraşdırın"</string>
<string name="voicemail_status_action_call_server" msgid="1824816252288551794">"Səsli poçta zəng edin"</string>
- <string name="voicemail_speed_slowest" msgid="1733460666177707312">"Ən alçaq sürət"</string>
- <string name="voicemail_speed_slower" msgid="1508601287347216244">"Zəif sürət"</string>
- <string name="voicemail_speed_normal" msgid="9033988544627228892">"Normal sürət"</string>
- <string name="voicemail_speed_faster" msgid="2019965121475935488">"Yüksək sürət"</string>
- <string name="voicemail_speed_fastest" msgid="5758712343491183292">"Ən yüksək sürət"</string>
<string name="call_log_item_count_and_date" msgid="7641933305703520787">"(<xliff:g id="COUNT">%1$d</xliff:g>) <xliff:g id="DATE">%2$s</xliff:g>"</string>
<string name="sms_disambig_title" msgid="5846266399240630846">"Nömrə seçin"</string>
<string name="call_disambig_title" msgid="4392886850104795739">"Nömrə seçin"</string>
<string name="make_primary" msgid="5829291915305113983">"Bu seçimi yadda saxla"</string>
- <string name="description_voicemail_button" msgid="3402506823655455591">"Səsli məktub"</string>
<string name="description_search_button" msgid="3660807558587384889">"axtarış"</string>
<string name="description_dial_button" msgid="1274091017188142646">"nömrə yığın"</string>
- <string name="description_delete_button" msgid="6263102114033407382">"backspace"</string>
<string name="description_digits_edittext" msgid="8760207516497016437">"yığmaq üçün nömrə"</string>
+ <string name="description_playback_start_stop" msgid="5060732345522492292">"Oxudun və ya dayandırın"</string>
+ <string name="description_playback_speakerphone" msgid="6008323900245707504">"Spikerfonu aktiv və ya deaktiv edin"</string>
+ <string name="description_playback_seek" msgid="4509404274968530055">"Oxutma pozisiyası axtarın"</string>
+ <string name="description_rate_decrease" msgid="3161652589401708284">"Oxutma reytinqini azaldın"</string>
+ <string name="description_rate_increase" msgid="6324606574127052385">"Oxutma reytinqini artırın"</string>
<string name="action_menu_call_history_description" msgid="9018442816219748968">"Çağrı Tarixçəsi"</string>
<string name="action_menu_overflow_description" msgid="2303272250613084574">"Daha çox seçim"</string>
<string name="action_menu_dialpad_button" msgid="1425910318049008136">"nömrə yığımı paneli"</string>
@@ -77,32 +76,39 @@
<string name="menu_show_all_calls" msgid="7560347482073345885">"Bütün zəngləri göstərin"</string>
<string name="add_2sec_pause" msgid="9214012315201040129">"2 saniyəlik pauza əlavə edin"</string>
<string name="add_wait" msgid="3360818652790319634">"Gözləmə əlavə edin"</string>
+ <string name="dialer_settings_label" msgid="4305043242594150479">"Ayarlar"</string>
<string name="menu_newContact" msgid="1209922412763274638">"Yeni kontakt"</string>
<string name="menu_allContacts" msgid="6948308384034051670">"Bütün kontaktlar"</string>
<string name="callDetailTitle" msgid="5340227785196217938">"Zəng detalları"</string>
- <string name="toast_call_detail_error" msgid="7200975244804730096">"Sorğu göndərilmiş zəng üçün detallar oxuna bilmədi."</string>
+ <string name="toast_call_detail_error" msgid="6947041258280380832">"Təfərrüatlar mövcud deyil"</string>
<string name="dialer_useDtmfDialpad" msgid="1707548397435075040">"Toxunma ton klaviaturasını istifadə edin"</string>
<string name="dialer_returnToInCallScreen" msgid="3719386377550913067">"Davam edən zəngə qayıdın"</string>
<string name="dialer_addAnotherCall" msgid="4205688819890074468">"Zəng əlavə edin"</string>
<string name="type_incoming" msgid="6502076603836088532">"Gələn zəng"</string>
<string name="type_outgoing" msgid="343108709599392641">"Gedən zəng"</string>
<string name="type_missed" msgid="2720502601640509542">"Buraxılmış zəng"</string>
+ <string name="type_incoming_video" msgid="82323391702796181">"Gələn video zəng"</string>
+ <string name="type_outgoing_video" msgid="2858140021680755266">"Gedən video zəng"</string>
+ <string name="type_missed_video" msgid="954396897034220545">"Buraxılmış video zəng"</string>
<string name="type_voicemail" msgid="5153139450668549908">"Səsli poçt"</string>
<string name="actionIncomingCall" msgid="6028930669817038600">"Gələn zənglər"</string>
<string name="description_call_log_play_button" msgid="651182125650429846">"Səsli məktubu oxudun"</string>
<string name="description_view_contact" msgid="5205669345700598415">"<xliff:g id="NAME">%1$s</xliff:g> adlı kontakta baxın"</string>
<string name="description_call" msgid="3443678121983852666">"Zəng edin: <xliff:g id="NAME">%1$s</xliff:g>"</string>
- <string name="description_send_text_message" msgid="7803126439934046891">"<xliff:g id="NAME">%1$s</xliff:g> mesaj göndər"</string>
+ <string name="description_contact_details" msgid="51229793651342809">"<xliff:g id="NAMEORNUMBER">%1$s</xliff:g> üçün kontakt detalları"</string>
+ <string name="description_new_voicemail" msgid="2133792360865517746">"Yeni səsli poçt."</string>
+ <string name="description_num_calls" msgid="1601505153694540074">"<xliff:g id="NUMBEROFCALLS">%1$s</xliff:g> zəng."</string>
+ <string name="description_video_call" msgid="2933838090743214204">"Video çağrı."</string>
+ <string name="description_send_text_message" msgid="3118485319691414221">"<xliff:g id="NAME">%1$s</xliff:g> adlı şəxsə SMS göndər"</string>
<string name="description_call_log_unheard_voicemail" msgid="118101684236996786">"Eşidilməmiş səsli mesaj"</string>
<string name="description_start_voice_search" msgid="520539488194946012">"Səs axtarışına başlayın"</string>
<string name="menu_callNumber" msgid="997146291983360266">"<xliff:g id="NUMBER">%s</xliff:g> zəng edin"</string>
<string name="unknown" msgid="740067747858270469">"Naməlum"</string>
<string name="voicemail" msgid="3851469869202611441">"Səsli poçt"</string>
<string name="private_num" msgid="6374339738119166953">"Şəxsi nömrə"</string>
- <string name="payphone" msgid="4864313342828942922">"Telefon ödənişi"</string>
- <string name="dialerKeyboardHintText" msgid="5401660096579787344">"Nömrə yığmaq üçün klaviaturadan istifadə ediin"</string>
- <string name="callDetailsDurationFormat" msgid="8157706382818184268">"<xliff:g id="MINUTES">%s</xliff:g> dəqiqə <xliff:g id="SECONDS">%s</xliff:g> saniyə"</string>
- <string name="dialog_phone_call_prohibited_message" msgid="6554711866586660441">"Zəng edilmədi"</string>
+ <string name="payphone" msgid="7726415831153618726">"Taksofon"</string>
+ <string name="callDetailsDurationFormat" msgid="6061406028764382234">"<xliff:g id="MINUTES">%s</xliff:g> dəq <xliff:g id="SECONDS">%s</xliff:g> san"</string>
+ <string name="dialog_phone_call_prohibited_message" msgid="5730565540182492608">"Bu nömrəyə zəng etmək mümkün deyil"</string>
<string name="dialog_voicemail_not_ready_message" msgid="4384716252789515378">"Səsli poçtu ayarlamaq üçün Menyu, sonra isə > Ayarlara daxil olun."</string>
<string name="dialog_voicemail_airplane_mode_message" msgid="530922773669546093">"Səsli poçta zəng etmək üçün Təyyarə rejimini söndürün."</string>
<string name="contact_list_loading" msgid="5488620820563977329">"Yüklənir…"</string>
@@ -110,17 +116,76 @@
<string name="meid" msgid="6210568493746275750">"MEID"</string>
<string name="simContacts_emptyLoading" msgid="6700035985448642408">"SIM kartdan yüklənir..."</string>
<string name="simContacts_title" msgid="27341688347689769">"SIM kart kontaktları"</string>
- <string name="add_contact_not_available" msgid="1419207765446461366">"Bu özəlliyi istifadə etmək üçün Adamlar tətbiqinə yenidən icazə verin."</string>
- <!-- no translation found for dialer_hint_find_contact (8798845521253672403) -->
- <skip />
- <string name="call_log_all_title" msgid="3566738938889333307">"Bütün"</string>
- <string name="call_log_missed_title" msgid="4541142293870638971">"Buraxılmış"</string>
- <string name="recentMissed_empty" msgid="5427113551557296665">"Buraxılmış zəng yoxdur"</string>
- <string name="recentVoicemails_empty" msgid="5163114119598014048">"Səsli məktub yoxdur."</string>
+ <string name="add_contact_not_available" msgid="5547311613368004859">"Əlaqələr proqramı mövcud deyil"</string>
+ <string name="voice_search_not_available" msgid="2977719040254285301">"Səsli axtarış mövcud deyil"</string>
+ <string name="call_not_available" msgid="8941576511946492225">"Telefon tətbiqi deaktiv edildiyinə görə telefon zəngi etmək mümkün deyil."</string>
+ <string name="activity_not_available" msgid="2287665636817987623">"Bu cihazda onun üçün heç bir proqram yoxdur"</string>
+ <string name="dialer_hint_find_contact" msgid="1012544667033887519">"Ad və ya tel. nömrəsi daxil et"</string>
+ <string name="recentMissed_empty" msgid="4901789420356796156">"Zəng yoxdur"</string>
+ <string name="recentVoicemails_empty" msgid="8582424947259156664">"Son səsli poçt yoxdur"</string>
<string name="show_favorites_only" msgid="5520072531022614595">"Yalnız seçilmişləri göstər"</string>
<string name="call_log_activity_title" msgid="4612824396355272023">"Tarixçə"</string>
- <!-- no translation found for favorite_hidden (5011234945140912047) -->
- <skip />
+ <string name="call_log_all_title" msgid="3566738938889333307">"Bütün"</string>
+ <string name="call_log_missed_title" msgid="4541142293870638971">"Buraxılmış"</string>
+ <string name="call_log_voicemail_title" msgid="940422274047025948">"Səsli poçt"</string>
+ <string name="tab_speed_dial" msgid="7552166276545648893">"Sürətli nömrə yığımı"</string>
+ <string name="tab_recents" msgid="929949073851377206">"Sonuncular"</string>
+ <string name="tab_all_contacts" msgid="1410922767166533690">"Kontaktlar"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"Səsli poçt"</string>
+ <string name="favorite_hidden" msgid="5011234945140912047">"Seçilmişlərdən silindi"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"Geri qaytar"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"<xliff:g id="NUMBER">%s</xliff:g> nömrəsinə zəng edin"</string>
+ <string name="search_shortcut_create_new_contact" msgid="1679917465521554093">"Yeni kontakt yaradın"</string>
+ <string name="search_shortcut_add_to_existing_contact" msgid="4403132207405813444">"Mövcud kontakta əlavə edin"</string>
+ <string name="search_shortcut_send_sms_message" msgid="2569304043345025525">"SMS göndərin"</string>
+ <string name="search_shortcut_make_video_call" msgid="1265971685034465166">"Video zəng edin"</string>
+ <string name="show_call_history" msgid="1141502332266697170">"Tam çağrı tarixçəsinə baxın"</string>
+ <string name="num_missed_calls" msgid="8081736535604293886">"<xliff:g id="NUMBER">%s</xliff:g> yeni buraxılmış zəng"</string>
+ <string name="speed_dial_empty" msgid="1931474498966072849">"Sürətli nömrə yığımı seçilmişlər və tez-tez zəng etdiyiniz nömrələr üçün bir toxunuşla zəng deməkdir"</string>
+ <string name="all_contacts_empty" msgid="2299508125100209367">"Əlaqə yoxdur"</string>
+ <string name="contact_tooltip" msgid="2019777545923635266">"Bütün nömrələri görmək üçün təsvirə toxunun və ya yenidən qaydaya salmaq üçün toxunun və saxlayın"</string>
+ <string name="remove_contact" msgid="1080555335283662961">"Yığışdır"</string>
+ <string name="favorites_menu_all_contacts" msgid="992506284449891186">"BÜTÜN KONTAKTLAR"</string>
+ <string name="call_log_action_video_call" msgid="7724301709041128296">"Video zəng"</string>
+ <string name="call_log_action_voicemail" msgid="4978620572562925654">"DİNLƏYİN"</string>
+ <string name="call_log_action_send_message" msgid="2826466379787846163">"Mesaj Göndərin"</string>
+ <string name="call_log_action_details" msgid="7957138590190911171">"Detallara baxın"</string>
+ <string name="description_incoming_missed_call" msgid="2381085098795943627">"Buraxılmış zənglər: <xliff:g id="NAMEORNUMBER">^1</xliff:g>, <xliff:g id="TYPEORLOCATION">^2</xliff:g>, <xliff:g id="TIMEOFCALL">^3</xliff:g>, <xliff:g id="PHONEACCOUNT">^4</xliff:g>."</string>
+ <string name="description_incoming_answered_call" msgid="7117665748428816544">"Zəngləri cavablandırıb: <xliff:g id="NAMEORNUMBER">^1</xliff:g>, <xliff:g id="TYPEORLOCATION">^2</xliff:g>, <xliff:g id="TIMEOFCALL">^3</xliff:g>, <xliff:g id="PHONEACCOUNT">^4</xliff:g>."</string>
+ <string name="description_outgoing_call" msgid="6386364390619734734">"Zəng et: <xliff:g id="NAMEORNUMBER">^1</xliff:g>, <xliff:g id="TYPEORLOCATION">^2</xliff:g>, <xliff:g id="TIMEOFCALL">^3</xliff:g>, <xliff:g id="PHONEACCOUNT">^4</xliff:g>."</string>
+ <string name="description_phone_account" msgid="1767072759541443861">"Zəngdədir: <xliff:g id="PHONEACCOUNT">^1</xliff:g>"</string>
+ <string name="description_call_log_call_action" msgid="3682561657090693134">"Çağrı"</string>
+ <string name="description_call_action" msgid="4000549004089776147">"Çağrı <xliff:g id="NAMEORNUMBER">^1</xliff:g>"</string>
+ <string name="description_video_call_action" msgid="7386922428155062213">"<xliff:g id="NAMEORNUMBER">^1</xliff:g> video zəng edin."</string>
+ <string name="description_voicemail_action" msgid="8054891873788903063">"<xliff:g id="NAMEORNUMBER">^1</xliff:g> tərəfdən səsli mesajı dinləyin"</string>
+ <string name="description_create_new_contact_action" msgid="818755978791008167">"<xliff:g id="NAMEORNUMBER">^1</xliff:g> üçün kontakt yaradın"</string>
+ <string name="description_add_to_existing_contact_action" msgid="6081200053494414351">"Mövcud kontakta <xliff:g id="NAMEORNUMBER">^1</xliff:g> əlavə edin"</string>
+ <string name="description_details_action" msgid="2433827152749491785">"<xliff:g id="NAMEORNUMBER">^1</xliff:g> üçün detalları çağırın"</string>
+ <string name="description_report_action" msgid="5011961125980353172">"<xliff:g id="NAMEORNUMBER">^1</xliff:g> haqqında bildirin"</string>
+ <string name="toast_entry_removed" msgid="8010830299576311534">"Zəng tarixçəsindən silindi"</string>
+ <string name="call_log_action_report" msgid="4327809827087468864">"Şikayət edin"</string>
+ <string name="call_log_header_today" msgid="3225248682434212981">"Bu gün"</string>
+ <string name="call_log_header_yesterday" msgid="9139172458834033092">"Dünən"</string>
+ <string name="call_log_header_other" msgid="5769921959940709084">"Keçmi"</string>
+ <string name="call_detail_list_header" msgid="3752717059699600861">"Zənglər siyahısı"</string>
+ <string name="voicemail_speaker_on" msgid="209154030283487068">"Dinamiki aktiv et."</string>
+ <string name="voicemail_speaker_off" msgid="7390530056413093958">"Dinamiki deaktiv et."</string>
+ <string name="voicemail_play_faster" msgid="3444751008615323006">"Daha sürətlə oxut."</string>
+ <string name="voicemail_play_slower" msgid="4544796503902818832">"Daha yavaş oxut."</string>
+ <string name="voicemail_play_start_pause" msgid="3687447935787768983">"Oxunuşu başlat və ya durdur"</string>
+ <string name="list_delimeter" msgid="4571593167738725100">", "</string>
+ <string name="display_options_title" msgid="7812852361055667468">"Ekran seçimləri"</string>
+ <string name="sounds_and_vibration_title" msgid="1692290115642160845">"Səslər və vibrasiya"</string>
+ <string name="accessibility_settings_title" msgid="6068141142874046249">"Giriş imkanı"</string>
+ <string name="ringtone_title" msgid="760362035635084653">"Telefon zəng səsi"</string>
+ <string name="vibrate_on_ring_title" msgid="3362916460327555241">"Həmçinin zənglər üçün vibrasiya olsun"</string>
+ <string name="dtmf_tone_enable_title" msgid="6571449695997521615">"Yığım tonları"</string>
+ <string name="dtmf_tone_length_title" msgid="8581125689808919460">"Yığım paneli ton uzunluğu"</string>
+ <string-array name="dtmf_tone_length_entries">
+ <item msgid="1036113889050195575">"Normal"</item>
+ <item msgid="6177579030803486015">"Uzun"</item>
+ </string-array>
+ <string name="respond_via_sms_setting_title" msgid="1318281521087951580">"Qısa cavablar"</string>
+ <string name="call_settings_label" msgid="313434211353070209">"Zənglər"</string>
+ <string name="phone_account_settings_label" msgid="5864322009841175294">"Hesabların çağrılması"</string>
</resources>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index 42e3d9b..3416bc6 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -131,6 +131,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"Бързо набиране"</string>
<string name="tab_recents" msgid="929949073851377206">"Скорошни"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"Контакти"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"Гласова поща"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"Премахнато от любимите"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"Отмяна"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"Обаждане на <xliff:g id="NUMBER">%s</xliff:g>"</string>
diff --git a/res/values-bn-rBD/strings.xml b/res/values-bn-rBD/strings.xml
index 4daa6c1..1fb13f1 100644
--- a/res/values-bn-rBD/strings.xml
+++ b/res/values-bn-rBD/strings.xml
@@ -131,6 +131,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"দ্রুত ডায়াল"</string>
<string name="tab_recents" msgid="929949073851377206">"সাম্প্রতিকগুলি"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"পরিচিতিগুলি"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"ভয়েস মেল"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"পছন্দসই থেকে সরানো হয়েছে"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"পূর্বাবস্থায় ফিরুন"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"<xliff:g id="NUMBER">%s</xliff:g> কে কল করুন"</string>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index 610e4f1..8d9fba4 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -131,6 +131,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"Marcatge ràpid"</string>
<string name="tab_recents" msgid="929949073851377206">"Recents"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"Contactes"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"Bústia de veu"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"Eliminat dels preferits"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"Desfés"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"Truca al <xliff:g id="NUMBER">%s</xliff:g>"</string>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index f586cd8..c061879 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -133,6 +133,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"Rychlá volba"</string>
<string name="tab_recents" msgid="929949073851377206">"Poslední"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"Kontakty"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"Hlasová schránka"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"Odebráno z oblíbených"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"Vrátit zpět"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"Zavolat na číslo <xliff:g id="NUMBER">%s</xliff:g>"</string>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index 7cf5d8e..6552e1d 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -131,6 +131,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"Hurtigopkald"</string>
<string name="tab_recents" msgid="929949073851377206">"Seneste"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"Kontaktpersoner"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"Telefonsvarer"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"Fjernet fra foretrukne"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"Fortryd"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"Ring til <xliff:g id="NUMBER">%s</xliff:g>"</string>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index 3718744..0a80009 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -131,6 +131,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"Schnellauswahl"</string>
<string name="tab_recents" msgid="929949073851377206">"Neueste"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"Kontakte"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"Mailbox"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"Aus Favoriten entfernt"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"Rückgängig machen"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"<xliff:g id="NUMBER">%s</xliff:g> wählen"</string>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index 87e3492..fcc0f3b 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -131,6 +131,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"Γρήγορη κλήση"</string>
<string name="tab_recents" msgid="929949073851377206">"Πρόσφατα"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"Επαφές"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"Αυτόματος τηλεφωνητής"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"Καταργήθηκε από τα αγαπημένα"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"Αναίρεση"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"Κλήση του αριθμού <xliff:g id="NUMBER">%s</xliff:g>"</string>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index b802e62..7bcb658 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -131,6 +131,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"Speed dial"</string>
<string name="tab_recents" msgid="929949073851377206">"Recents"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"Contacts"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"Voicemail"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"Removed from favourites"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"Undo"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"Call <xliff:g id="NUMBER">%s</xliff:g>"</string>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index b802e62..7bcb658 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -131,6 +131,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"Speed dial"</string>
<string name="tab_recents" msgid="929949073851377206">"Recents"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"Contacts"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"Voicemail"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"Removed from favourites"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"Undo"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"Call <xliff:g id="NUMBER">%s</xliff:g>"</string>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index b802e62..7bcb658 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -131,6 +131,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"Speed dial"</string>
<string name="tab_recents" msgid="929949073851377206">"Recents"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"Contacts"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"Voicemail"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"Removed from favourites"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"Undo"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"Call <xliff:g id="NUMBER">%s</xliff:g>"</string>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index 88ca1ab..74ad6b9 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -131,6 +131,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"Marcado rápido"</string>
<string name="tab_recents" msgid="929949073851377206">"Recientes"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"Contactos"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"Buzón de voz"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"Eliminado de favoritos"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"Deshacer"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"Llamar al <xliff:g id="NUMBER">%s</xliff:g>"</string>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index 4af50f9..b04848a 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -131,6 +131,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"Marcación rápida"</string>
<string name="tab_recents" msgid="929949073851377206">"Recientes"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"Contactos"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"Buzón de voz"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"Eliminado de favoritos"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"Deshacer"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"Llamar a <xliff:g id="NUMBER">%s</xliff:g>"</string>
diff --git a/res/values-et-rEE/strings.xml b/res/values-et-rEE/strings.xml
index da1b3d8..b2e7523 100644
--- a/res/values-et-rEE/strings.xml
+++ b/res/values-et-rEE/strings.xml
@@ -131,6 +131,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"Kiirvalimine"</string>
<string name="tab_recents" msgid="929949073851377206">"Hiljutised"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"Kontaktid"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"Kõnepost"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"Eemaldatud lemmikute hulgast"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"Võta tagasi"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"Helista <xliff:g id="NUMBER">%s</xliff:g>"</string>
diff --git a/res/values-eu-rES/strings.xml b/res/values-eu-rES/strings.xml
index 47e1472..b5e57a7 100644
--- a/res/values-eu-rES/strings.xml
+++ b/res/values-eu-rES/strings.xml
@@ -131,6 +131,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"Markatze bizkorra"</string>
<string name="tab_recents" msgid="929949073851377206">"Azkenak"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"Kontaktuak"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"Erantzungailua"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"Gogokoetatik kendu da"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"Desegin"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"Deitu <xliff:g id="NUMBER">%s</xliff:g> zenbakira"</string>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index a60302a..6ec5a7f 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -131,6 +131,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"شمارهگیری سریع"</string>
<string name="tab_recents" msgid="929949073851377206">"موارد اخیر"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"مخاطبین"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"پست صوتی"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"از موارد دلخواه حذف شد"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"لغو"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"تماس با <xliff:g id="NUMBER">%s</xliff:g>"</string>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index af90c99..7260441 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -131,6 +131,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"Pikavalinta"</string>
<string name="tab_recents" msgid="929949073851377206">"Viimeisimmät"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"Yhteystiedot"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"Puhelinvastaaja"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"Poistettu suosikeista"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"Kumoa"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"Soita <xliff:g id="NUMBER">%s</xliff:g>"</string>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index d149440..5b2935d 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -131,6 +131,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"Composition abrégée"</string>
<string name="tab_recents" msgid="929949073851377206">"Récents"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"Contacts"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"Messagerie vocale"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"Supprimé des favoris"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"Annuler"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"Appeler le <xliff:g id="NUMBER">%s</xliff:g>"</string>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index 611c74c..abac9f9 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -131,6 +131,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"Numérotation abrégée"</string>
<string name="tab_recents" msgid="929949073851377206">"Récents"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"Contacts"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"Messagerie vocale"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"Supprimé des favoris."</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"Annuler"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"Appeler le <xliff:g id="NUMBER">%s</xliff:g>"</string>
diff --git a/res/values-gl-rES/strings.xml b/res/values-gl-rES/strings.xml
index 529ee5a..8658b7f 100644
--- a/res/values-gl-rES/strings.xml
+++ b/res/values-gl-rES/strings.xml
@@ -131,6 +131,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"Marcación rápida"</string>
<string name="tab_recents" msgid="929949073851377206">"Recentes"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"Contactos"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"Correo de voz"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"Eliminado dos favoritos"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"Desfacer"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"Chamar a <xliff:g id="NUMBER">%s</xliff:g>"</string>
diff --git a/res/values-gu-rIN/strings.xml b/res/values-gu-rIN/strings.xml
index 1097faf..dbabef6 100644
--- a/res/values-gu-rIN/strings.xml
+++ b/res/values-gu-rIN/strings.xml
@@ -131,6 +131,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"સ્પીડ ડાયલ"</string>
<string name="tab_recents" msgid="929949073851377206">"તાજેતરના"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"સંપર્કો"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"વૉઇસમેઇલ"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"મનપસંદમાંથી દૂર કર્યું"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"પૂર્વવત્ કરો"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"<xliff:g id="NUMBER">%s</xliff:g> ને કૉલ કરો"</string>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index 30be2be..fcb199f 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -131,6 +131,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"स्पीड डायल"</string>
<string name="tab_recents" msgid="929949073851377206">"हाल ही के"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"संपर्क"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"वॉइसमेल"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"पसंदीदा से निकाल दिया गया"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"वापस लाएं"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"<xliff:g id="NUMBER">%s</xliff:g> पर कॉल करें"</string>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index 29b0ca1..46eebf1 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -132,6 +132,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"Brzo biranje"</string>
<string name="tab_recents" msgid="929949073851377206">"Najnoviji"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"Kontakti"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"Govorna pošta"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"Uklonjeno iz favorita"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"Poništi"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"Nazovi <xliff:g id="NUMBER">%s</xliff:g>"</string>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index 03653bb..e175faa 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -131,6 +131,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"Gyorshívó"</string>
<string name="tab_recents" msgid="929949073851377206">"Legutóbbiak"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"Címtár"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"Hangposta"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"Eltávolítva a kedvencek közül"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"Visszavonás"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"Hívás: <xliff:g id="NUMBER">%s</xliff:g>"</string>
diff --git a/res/values-hy-rAM/strings.xml b/res/values-hy-rAM/strings.xml
index 632adc4..ff8738e 100644
--- a/res/values-hy-rAM/strings.xml
+++ b/res/values-hy-rAM/strings.xml
@@ -131,6 +131,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"Արագ համարարկում"</string>
<string name="tab_recents" msgid="929949073851377206">"Վերջինները"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"Կոնտակտներ"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"Ձայնային փոստ"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"Հեռացված է ընտրյալներից"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"Հետարկել"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"Զանգել <xliff:g id="NUMBER">%s</xliff:g>"</string>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index 2d64b62..2f4907e 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -131,6 +131,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"Panggilan cepat"</string>
<string name="tab_recents" msgid="929949073851377206">"Terbaru"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"Kontak"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"Pesan suara"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"Dihapus dari favorit"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"Batalkan"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"Telepon <xliff:g id="NUMBER">%s</xliff:g>"</string>
diff --git a/res/values-is-rIS/strings.xml b/res/values-is-rIS/strings.xml
index 28816b6..020ef3e 100644
--- a/res/values-is-rIS/strings.xml
+++ b/res/values-is-rIS/strings.xml
@@ -131,6 +131,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"Hraðval"</string>
<string name="tab_recents" msgid="929949073851377206">"Nýlegt"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"Tengiliðir"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"Talhólf"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"Fjarlægður úr uppáhaldi"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"Afturkalla"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"Hringja í <xliff:g id="NUMBER">%s</xliff:g>"</string>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index 64ec2b1..7fefd60 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -131,6 +131,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"Composizione rapida"</string>
<string name="tab_recents" msgid="929949073851377206">"Recenti"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"Contatti"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"Segreteria"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"Rimosso dai preferiti"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"Annulla"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"Chiama <xliff:g id="NUMBER">%s</xliff:g>"</string>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index 2d53b9f..4ae0710 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -133,6 +133,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"חיוג מהיר"</string>
<string name="tab_recents" msgid="929949073851377206">"אחרונים"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"אנשי קשר"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"דואר קולי"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"הוסר מהמועדפים"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"בטל"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"התקשר אל <xliff:g id="NUMBER">%s</xliff:g>"</string>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index 55d31e9..25cb120 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -131,6 +131,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"クイックアクセス"</string>
<string name="tab_recents" msgid="929949073851377206">"最近"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"連絡先"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"ボイスメール"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"お気に入りから削除されました"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"元に戻す"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"<xliff:g id="NUMBER">%s</xliff:g>に発信"</string>
diff --git a/res/values-ka-rGE/strings.xml b/res/values-ka-rGE/strings.xml
index ae9d44c..e1098bf 100644
--- a/res/values-ka-rGE/strings.xml
+++ b/res/values-ka-rGE/strings.xml
@@ -131,6 +131,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"სწრაფი დარეკვა"</string>
<string name="tab_recents" msgid="929949073851377206">"ბოლო"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"კონტაქტები"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"ხმოვანი ფოსტა"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"წაიშალა რჩეულებიდან"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"დაბრუნება"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"დარეკვა <xliff:g id="NUMBER">%s</xliff:g>-ზე"</string>
diff --git a/res/values-kk-rKZ/strings.xml b/res/values-kk-rKZ/strings.xml
index 83b0120..de4a879 100644
--- a/res/values-kk-rKZ/strings.xml
+++ b/res/values-kk-rKZ/strings.xml
@@ -47,7 +47,7 @@
<string name="call_log_incoming_header" msgid="2787722299753674684">"Келген қоңыраулар ғана"</string>
<string name="call_log_outgoing_header" msgid="761009180766735769">"Шығыс қоңыраулары ғана"</string>
<string name="call_log_missed_header" msgid="8017148056610855956">"Қабылданбаған қоңыраулар ғана"</string>
- <string name="voicemail_status_voicemail_not_available" msgid="5222480147701456390">"Дауыс-хабар жаңартулары қол жетімді емес"</string>
+ <string name="voicemail_status_voicemail_not_available" msgid="5222480147701456390">"Дауыстық пошта жаңартулары қол жетімді емес"</string>
<string name="voicemail_status_messages_waiting" msgid="6329544650250068650">"Жаңа дауыстық хабар күтуде. Дәл қазір жүктеу мүмкін емес."</string>
<string name="voicemail_status_configure_voicemail" msgid="8300808991932816153">"Дауыс-хабарды реттеу"</string>
<string name="voicemail_status_audio_not_available" msgid="2449801102560158082">"Аудио қол жетімді емес"</string>
@@ -90,7 +90,7 @@
<string name="type_incoming_video" msgid="82323391702796181">"Кіріс бейне қоңырау"</string>
<string name="type_outgoing_video" msgid="2858140021680755266">"Шығыс бейне қоңырау"</string>
<string name="type_missed_video" msgid="954396897034220545">"Өткізіп алынған бейне қоңырау"</string>
- <string name="type_voicemail" msgid="5153139450668549908">"Дауыс-хабар"</string>
+ <string name="type_voicemail" msgid="5153139450668549908">"Дауыстық пошта"</string>
<string name="actionIncomingCall" msgid="6028930669817038600">"Келген қоңыраулар"</string>
<string name="description_call_log_play_button" msgid="651182125650429846">"Дауыс-хабарды ойнату"</string>
<string name="description_view_contact" msgid="5205669345700598415">"<xliff:g id="NAME">%1$s</xliff:g> контактісін көру"</string>
@@ -104,7 +104,7 @@
<string name="description_start_voice_search" msgid="520539488194946012">"Дауыс іздеуді бастау"</string>
<string name="menu_callNumber" msgid="997146291983360266">"<xliff:g id="NUMBER">%s</xliff:g> нөміріне қоңырау шалу"</string>
<string name="unknown" msgid="740067747858270469">"Белгісіз"</string>
- <string name="voicemail" msgid="3851469869202611441">"Дауыс-хабар"</string>
+ <string name="voicemail" msgid="3851469869202611441">"Дауыстық пошта"</string>
<string name="private_num" msgid="6374339738119166953">"Жеке нөмір"</string>
<string name="payphone" msgid="7726415831153618726">"Автомат-телефон"</string>
<string name="callDetailsDurationFormat" msgid="6061406028764382234">"<xliff:g id="MINUTES">%s</xliff:g> мин <xliff:g id="SECONDS">%s</xliff:g> сек"</string>
@@ -131,6 +131,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"Жылдам теру"</string>
<string name="tab_recents" msgid="929949073851377206">"Жақындағылар"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"Контактілер"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"Дауыстық хабар"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"Сүйіктілерден алынған"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"Кері орындау"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"<xliff:g id="NUMBER">%s</xliff:g> нөміріне қоңырау шалу"</string>
diff --git a/res/values-km-rKH/strings.xml b/res/values-km-rKH/strings.xml
index 36ce36a..168fbe3 100644
--- a/res/values-km-rKH/strings.xml
+++ b/res/values-km-rKH/strings.xml
@@ -131,6 +131,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"ហៅទូរស័ព្ទល្បឿនលឿន"</string>
<string name="tab_recents" msgid="929949073851377206">"ថ្មីៗ"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"ទំនាក់ទំនង"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"សារជាសំឡេង"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"បានលុបចេញពីទំនាក់ទំនងដែលនិយមប្រើ"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"មិនធ្វើវិញ"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"ហៅ <xliff:g id="NUMBER">%s</xliff:g>"</string>
diff --git a/res/values-kn-rIN/strings.xml b/res/values-kn-rIN/strings.xml
index ace7517..40e6127 100644
--- a/res/values-kn-rIN/strings.xml
+++ b/res/values-kn-rIN/strings.xml
@@ -132,6 +132,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"ಸ್ಪೀಡ್ ಡಯಲ್"</string>
<string name="tab_recents" msgid="929949073851377206">"ಇತ್ತೀಚಿನವುಗಳು"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"ಸಂಪರ್ಕಗಳು"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"ಧ್ವನಿಮೇಲ್"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"ಮೆಚ್ಚಿನವುಗಳಿಂದ ತೆಗೆದುಹಾಕಲಾಗಿದೆ"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"ರದ್ದುಮಾಡು"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"<xliff:g id="NUMBER">%s</xliff:g> ಕರೆ ಮಾಡಿ"</string>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index 1fd8cde..33c40e2 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -131,6 +131,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"단축번호"</string>
<string name="tab_recents" msgid="929949073851377206">"최근"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"주소록"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"음성사서함"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"즐겨찾기에서 삭제됨"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"실행취소"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"<xliff:g id="NUMBER">%s</xliff:g>에 전화"</string>
diff --git a/res/values-ky-rKG/strings.xml b/res/values-ky-rKG/strings.xml
index 7349947..0983c97 100644
--- a/res/values-ky-rKG/strings.xml
+++ b/res/values-ky-rKG/strings.xml
@@ -158,6 +158,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"Тез терүү"</string>
<string name="tab_recents" msgid="929949073851377206">"Акыркылар"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"Байланыштар"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"Үн почтасы"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"Тандамалдардан өчүрүлдү"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"Кайтаруу"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"Чалуу <xliff:g id="NUMBER">%s</xliff:g>"</string>
diff --git a/res/values-lo-rLA/strings.xml b/res/values-lo-rLA/strings.xml
index 1303d22..2e29d91 100644
--- a/res/values-lo-rLA/strings.xml
+++ b/res/values-lo-rLA/strings.xml
@@ -131,6 +131,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"ການໂທດ່ວນ"</string>
<string name="tab_recents" msgid="929949073851377206">"ຫາກໍໃຊ້"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"ລາຍຊື່ຜູ່ຕິດຕໍ່"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"ຂໍ້ຄວາມສຽງ"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"ລຶບອອກຈາກລາຍການທີ່ມັກແລ້ວ"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"ຍົກເລີກ"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"ໂທຫາ <xliff:g id="NUMBER">%s</xliff:g>"</string>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index d803ced..147c974 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -133,6 +133,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"Spartusis rinkimas"</string>
<string name="tab_recents" msgid="929949073851377206">"Naujausi"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"Kontaktai"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"Balso paštas"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"Pašalintas iš adresyno"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"Anuliuoti"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"Skambinti numeriu <xliff:g id="NUMBER">%s</xliff:g>"</string>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index 6541493..b39562d 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -132,6 +132,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"Ātrie zvani"</string>
<string name="tab_recents" msgid="929949073851377206">"Pēdējie"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"Kontaktpersonas"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"Balss pasts"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"Noņemts no izlases"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"Atsaukt"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"Zvaniet: <xliff:g id="NUMBER">%s</xliff:g>"</string>
diff --git a/res/values-mk-rMK/strings.xml b/res/values-mk-rMK/strings.xml
index c5668b6..d79e7b9 100644
--- a/res/values-mk-rMK/strings.xml
+++ b/res/values-mk-rMK/strings.xml
@@ -131,6 +131,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"Брзо бирање"</string>
<string name="tab_recents" msgid="929949073851377206">"Неодамнешни"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"Контакти"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"Говорна пошта"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"Отстранет од омилени"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"Врати"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"Повикај <xliff:g id="NUMBER">%s</xliff:g>"</string>
diff --git a/res/values-ml-rIN/strings.xml b/res/values-ml-rIN/strings.xml
index 95aac8a..caac273 100644
--- a/res/values-ml-rIN/strings.xml
+++ b/res/values-ml-rIN/strings.xml
@@ -131,6 +131,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"സ്പീഡ് ഡയൽ"</string>
<string name="tab_recents" msgid="929949073851377206">"പുതിയവ"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"കോണ്ടാക്റ്റുകള്"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"വോയ്സ്മെയിൽ"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"പ്രിയപ്പെട്ടവയിൽ നിന്നും നീക്കംചെയ്തു"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"പഴയപടിയാക്കുക"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"<xliff:g id="NUMBER">%s</xliff:g> എന്നതിൽ വിളിക്കുക"</string>
diff --git a/res/values-mn-rMN/strings.xml b/res/values-mn-rMN/strings.xml
index c005bc9..92f29f8 100644
--- a/res/values-mn-rMN/strings.xml
+++ b/res/values-mn-rMN/strings.xml
@@ -131,6 +131,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"Түргэн залгалт"</string>
<string name="tab_recents" msgid="929949073851377206">"Саяхных"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"Харилцагчид"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"Дуут шуудан"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"Дуртай жагсаалтаас хасав"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"Буцаах"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"<xliff:g id="NUMBER">%s</xliff:g> руу залгах"</string>
diff --git a/res/values-mr-rIN/strings.xml b/res/values-mr-rIN/strings.xml
index 4594d81..998fba6 100644
--- a/res/values-mr-rIN/strings.xml
+++ b/res/values-mr-rIN/strings.xml
@@ -131,6 +131,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"स्पीड डायल"</string>
<string name="tab_recents" msgid="929949073851377206">"अलीकडील"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"संपर्क"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"व्हॉइसमेल"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"आवडी मधून काढले"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"पूर्ववत करा"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"<xliff:g id="NUMBER">%s</xliff:g> ला कॉल करा"</string>
diff --git a/res/values-ms-rMY/strings.xml b/res/values-ms-rMY/strings.xml
index 0ed605a..18f9545 100644
--- a/res/values-ms-rMY/strings.xml
+++ b/res/values-ms-rMY/strings.xml
@@ -131,6 +131,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"Dail laju"</string>
<string name="tab_recents" msgid="929949073851377206">"Terkini"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"Kenalan"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"Mel suara"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"Dialih keluar daripada kegemaran"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"Buat asal"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"Panggil <xliff:g id="NUMBER">%s</xliff:g>"</string>
diff --git a/res/values-my-rMM/strings.xml b/res/values-my-rMM/strings.xml
index 441aa42..52dc93b 100644
--- a/res/values-my-rMM/strings.xml
+++ b/res/values-my-rMM/strings.xml
@@ -131,6 +131,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"အမြန် နံပါတ်လှည့်မှု"</string>
<string name="tab_recents" msgid="929949073851377206">"မကြာမီက"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"အဆက်အသွယ်များ"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"အသံမေးလ်"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"အနှစ်သက်ဆုံးများထဲမှာ ထုတ်လိုက်ပါပြီ"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"နောက်ပြန်လုပ်ပါ"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"<xliff:g id="NUMBER">%s</xliff:g> ကိုခေါ်ပါ"</string>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index 09d7d3c..220324c 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -131,6 +131,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"Hurtigvalg"</string>
<string name="tab_recents" msgid="929949073851377206">"Sist brukte"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"Kontakter"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"Talepostkasse"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"Fjernet fra favoritter"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"Angre"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"Ring <xliff:g id="NUMBER">%s</xliff:g>"</string>
diff --git a/res/values-ne-rNP/strings.xml b/res/values-ne-rNP/strings.xml
index c042fa7..46786f9 100644
--- a/res/values-ne-rNP/strings.xml
+++ b/res/values-ne-rNP/strings.xml
@@ -131,6 +131,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"द्रूत डायल"</string>
<string name="tab_recents" msgid="929949073851377206">"हालैका"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"सम्पर्कहरू"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"भ्वाइसमेल"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"मनपर्नेहरूबाट हटाइयो"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"अनडु गर्नुहोस्"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"<xliff:g id="NUMBER">%s</xliff:g> कल गर्नुहोस्"</string>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index 1e338ff..7991394 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -131,6 +131,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"Snelkeuze"</string>
<string name="tab_recents" msgid="929949073851377206">"Recent"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"Contacten"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"Voicemail"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"Verwijderd uit favorieten"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"Ongedaan maken"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"Bel <xliff:g id="NUMBER">%s</xliff:g>"</string>
diff --git a/res/values-pa-rIN/strings.xml b/res/values-pa-rIN/strings.xml
index 76b8f40..d73dce0 100644
--- a/res/values-pa-rIN/strings.xml
+++ b/res/values-pa-rIN/strings.xml
@@ -131,6 +131,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"ਸਪੀਡ ਡਾਇਲ"</string>
<string name="tab_recents" msgid="929949073851377206">"ਹਾਲੀਆ"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"ਸੰਪਰਕ"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"ਵੌਇਸਮੇਲ"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"ਮਨਪਸੰਦ ਵਿੱਚੋਂ ਹਟਾਇਆ ਗਿਆ"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"ਪਹਿਲਾਂ ਵਰਗਾ ਕਰੋ"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"<xliff:g id="NUMBER">%s</xliff:g> ਨੂੰ ਕਾਲ ਕਰੋ"</string>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index 5ca819a..eda54a8 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -133,6 +133,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"Szybkie wybieranie"</string>
<string name="tab_recents" msgid="929949073851377206">"Ostatnie"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"Kontakty"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"Poczta głosowa"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"Usunięto z ulubionych"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"Cofnij"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"Zadzwoń: <xliff:g id="NUMBER">%s</xliff:g>"</string>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index 0a12b2c..fd1896f 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -131,6 +131,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"Marcação rápida"</string>
<string name="tab_recents" msgid="929949073851377206">"Recentes"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"Contactos"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"Correio de voz"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"Removido dos favoritos"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"Anular"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"Telefonar para <xliff:g id="NUMBER">%s</xliff:g>"</string>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index ba4a950..57bc9d1 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -131,6 +131,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"Discagem rápida"</string>
<string name="tab_recents" msgid="929949073851377206">"Recentes"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"Contatos"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"Correio de voz"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"Removido dos favoritos"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"Desfazer"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"Ligar para <xliff:g id="NUMBER">%s</xliff:g>"</string>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index 04ca243..55e9f1d 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -132,6 +132,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"Apelare rapidă"</string>
<string name="tab_recents" msgid="929949073851377206">"Recente"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"Agendă"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"Mesagerie vocală"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"S-a eliminat din preferate"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"Anulați"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"Apelați <xliff:g id="NUMBER">%s</xliff:g>"</string>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index b0d950c..3100c6c 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -133,6 +133,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"Быстрый набор"</string>
<string name="tab_recents" msgid="929949073851377206">"Недавние"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"Контакты"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"Голосовая почта"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"Контакт удален из избранных"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"Отмена"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"Позвонить: <xliff:g id="NUMBER">%s</xliff:g>"</string>
diff --git a/res/values-si-rLK/strings.xml b/res/values-si-rLK/strings.xml
index 78c0179..c3f2c5a 100644
--- a/res/values-si-rLK/strings.xml
+++ b/res/values-si-rLK/strings.xml
@@ -131,6 +131,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"වේග ඩයල් කිරීම"</string>
<string name="tab_recents" msgid="929949073851377206">"මෑත"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"සම්බන්ධතා"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"හඬ තැපෑල"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"ප්රියතමයන්ගෙන් ඉවත් කරන්න"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"අස් කරන්න"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"<xliff:g id="NUMBER">%s</xliff:g> අමතන්න"</string>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index 0929e12..85b0aa2 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -133,6 +133,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"Rýchla voľba"</string>
<string name="tab_recents" msgid="929949073851377206">"Nedávne"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"Kontakty"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"Hlasová schránka"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"Odstránené z obľúbených"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"Späť"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"Volať na číslo <xliff:g id="NUMBER">%s</xliff:g>"</string>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index 48d0349..2beeb77 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -133,6 +133,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"Hitro izbiranje"</string>
<string name="tab_recents" msgid="929949073851377206">"Nedavni"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"Stiki"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"Odzivnik"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"Odstranjeno iz priljubljenih"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"Razveljavi"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"Pokliči <xliff:g id="NUMBER">%s</xliff:g>"</string>
diff --git a/res/values-sq-rAL/strings.xml b/res/values-sq-rAL/strings.xml
index a3d247d..8de2b4b 100644
--- a/res/values-sq-rAL/strings.xml
+++ b/res/values-sq-rAL/strings.xml
@@ -131,6 +131,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"Telefonatë e shpejtë"</string>
<string name="tab_recents" msgid="929949073851377206">"Të fundit"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"Kontaktet"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"Posta zanore"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"U hoq nga të preferuarat"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"Zhbëj"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"Telefono <xliff:g id="NUMBER">%s</xliff:g>"</string>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index 6a9b9fb..d4a73f8 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -132,6 +132,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"Брзо бирање"</string>
<string name="tab_recents" msgid="929949073851377206">"Недавни контакти"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"Контакти"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"Говорна пошта"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"Уклоњено је из омиљених"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"Опозови"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"Позови <xliff:g id="NUMBER">%s</xliff:g>"</string>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index 1eaeeaf..61d01c5 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -131,6 +131,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"Kortnummer"</string>
<string name="tab_recents" msgid="929949073851377206">"Senaste"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"Kontakter"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"Röstbrevlåda"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"Borttagen från favoriter"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"Ångra"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"Ring <xliff:g id="NUMBER">%s</xliff:g>"</string>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index ea99b51..bebcd10 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -131,6 +131,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"Unaowasiliana nao zaidi"</string>
<string name="tab_recents" msgid="929949073851377206">"Zilizotumika majuzi"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"Anwani zote"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"Ujumbe wa sauti"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"Imeondolowa kwenye vipendwa"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"Tendua"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"Piga simu <xliff:g id="NUMBER">%s</xliff:g>"</string>
diff --git a/res/values-ta-rIN/strings.xml b/res/values-ta-rIN/strings.xml
index 8bd2e85..f0fe1e7 100644
--- a/res/values-ta-rIN/strings.xml
+++ b/res/values-ta-rIN/strings.xml
@@ -131,6 +131,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"விரைவு டயல்"</string>
<string name="tab_recents" msgid="929949073851377206">"சமீபத்தியவை"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"தொடர்புகள்"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"குரலஞ்சல்"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"பிடித்தவற்றிலிருந்து அகற்றப்பட்டது"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"செயல்தவிர்"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"<xliff:g id="NUMBER">%s</xliff:g> ஐ அழை"</string>
diff --git a/res/values-te-rIN/strings.xml b/res/values-te-rIN/strings.xml
index 76b79c2..c6ba2c3 100644
--- a/res/values-te-rIN/strings.xml
+++ b/res/values-te-rIN/strings.xml
@@ -131,6 +131,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"స్పీడ్ డయల్"</string>
<string name="tab_recents" msgid="929949073851377206">"ఇటీవలివి"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"పరిచయాలు"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"వాయిస్ మెయిల్"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"ఇష్టమైనవాటి నుండి తీసివేయబడింది"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"చర్య రద్దు చేయి"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"<xliff:g id="NUMBER">%s</xliff:g>కు కాల్ చేయండి"</string>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index 2bac245..e946ae5 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -131,6 +131,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"การโทรด่วน"</string>
<string name="tab_recents" msgid="929949073851377206">"ล่าสุด"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"รายชื่อติดต่อ"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"ข้อความเสียง"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"ลบจากรายการโปรด"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"เลิกทำ"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"โทร <xliff:g id="NUMBER">%s</xliff:g>"</string>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index 9493e14..d0ffdc3 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -131,6 +131,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"Speed dial"</string>
<string name="tab_recents" msgid="929949073851377206">"Mga Kamakailan"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"Mga Contact"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"Voicemail"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"Inalis sa mga paborito"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"I-undo"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"Tumawag sa <xliff:g id="NUMBER">%s</xliff:g>"</string>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index 8fc84d7..c9132a3 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -131,6 +131,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"Hızlı arama"</string>
<string name="tab_recents" msgid="929949073851377206">"Son Kişiler"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"Kişiler"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"Sesli mesaj"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"Favorilerden kaldırıldı"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"Geri al"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"Telefon et: <xliff:g id="NUMBER">%s</xliff:g>"</string>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index 041f453..5df028b 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -133,6 +133,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"Швидкий набір"</string>
<string name="tab_recents" msgid="929949073851377206">"Останні"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"Контакти"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"Голосова пошта"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"Вилучено з вибраного"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"Відмінити"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"Набрати <xliff:g id="NUMBER">%s</xliff:g>"</string>
diff --git a/res/values-ur-rPK/strings.xml b/res/values-ur-rPK/strings.xml
index fded54a..80469a6 100644
--- a/res/values-ur-rPK/strings.xml
+++ b/res/values-ur-rPK/strings.xml
@@ -131,6 +131,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"اسپیڈ ڈائل"</string>
<string name="tab_recents" msgid="929949073851377206">"حالیہ"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"رابطے"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"صوتی میل"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"پسندیدہ سے ہٹا دیا گیا"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"کالعدم کریں"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"<xliff:g id="NUMBER">%s</xliff:g> کو کال کریں"</string>
diff --git a/res/values-uz-rUZ/strings.xml b/res/values-uz-rUZ/strings.xml
index 6735fa3..6d67c6d 100644
--- a/res/values-uz-rUZ/strings.xml
+++ b/res/values-uz-rUZ/strings.xml
@@ -131,6 +131,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"Tezkor terish"</string>
<string name="tab_recents" msgid="929949073851377206">"So‘nggi"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"Kontaktlar"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"Ovozli pochta"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"Tanlanganlardan o‘chirilgan"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"Bekor qilish"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"Qo‘ng‘iroq: <xliff:g id="NUMBER">%s</xliff:g>"</string>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index eb4ea3f..ce5deeb 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -131,6 +131,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"Quay số nhanh"</string>
<string name="tab_recents" msgid="929949073851377206">"Gần đây"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"Địa chỉ liên hệ"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"Thư thoại"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"Đã xóa khỏi mục yêu thích"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"Hoàn tác"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"Gọi <xliff:g id="NUMBER">%s</xliff:g>"</string>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index 290263d..86abaa6 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -131,6 +131,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"快速拨号"</string>
<string name="tab_recents" msgid="929949073851377206">"最近"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"联系人"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"语音邮件"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"已从收藏中移除"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"撤消"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"拨打<xliff:g id="NUMBER">%s</xliff:g>"</string>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index b6917fd..55d5cae 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -131,6 +131,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"快速撥號"</string>
<string name="tab_recents" msgid="929949073851377206">"最近存取的聯絡人"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"聯絡人"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"留言信箱"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"已從「我的最愛」中移除"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"復原"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"撥打 <xliff:g id="NUMBER">%s</xliff:g>"</string>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index ee66149..16e06ba 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -131,6 +131,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"快速撥號"</string>
<string name="tab_recents" msgid="929949073851377206">"近期通話"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"聯絡人"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"語音信箱"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"已從最愛的聯絡人移除"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"復原"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"撥打 <xliff:g id="NUMBER">%s</xliff:g>"</string>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index 1a5b0c6..798044c 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -131,6 +131,7 @@
<string name="tab_speed_dial" msgid="7552166276545648893">"Ukudayela okusheshayo"</string>
<string name="tab_recents" msgid="929949073851377206">"Okwakamuva"</string>
<string name="tab_all_contacts" msgid="1410922767166533690">"Oxhumana nabo"</string>
+ <string name="tab_voicemail" msgid="155024725947496746">"Ivoyisimeyili"</string>
<string name="favorite_hidden" msgid="5011234945140912047">"Kukhishiwe kusuka kuzintandokazi"</string>
<string name="favorite_hidden_undo" msgid="2508998611039406474">"Hlehlisa"</string>
<string name="search_shortcut_call_number" msgid="7277194656832895870">"Shayela ku-<xliff:g id="NUMBER">%s</xliff:g>"</string>
diff --git a/src/com/android/dialer/CallDetailActivity.java b/src/com/android/dialer/CallDetailActivity.java
index 1c684a5..ce29049 100644
--- a/src/com/android/dialer/CallDetailActivity.java
+++ b/src/com/android/dialer/CallDetailActivity.java
@@ -61,7 +61,8 @@
import com.android.dialer.util.IntentUtil;
import com.android.dialer.util.DialerUtils;
import com.android.dialer.util.TelecomUtil;
-import com.android.dialer.voicemail.VoicemailPlaybackFragment;
+import com.android.dialer.voicemail.VoicemailPlaybackLayout;
+import com.android.dialer.voicemail.VoicemailPlaybackPresenter;
import java.util.List;
@@ -217,7 +218,7 @@
/** Helper to load contact photos. */
private ContactPhotoManager mContactPhotoManager;
- private LinearLayout mVoicemailHeader;
+ private VoicemailPlaybackPresenter mVoicemailPlaybackPresenter;
private Uri mVoicemailUri;
private BidiFormatter mBidiFormatter = BidiFormatter.getInstance();
@@ -255,6 +256,7 @@
getActionBar().setDisplayHomeAsUpEnabled(true);
optionallyHandleVoicemail();
+
if (getIntent().getBooleanExtra(EXTRA_FROM_NOTIFICATION, false)) {
closeSystemDialogs();
}
@@ -267,6 +269,38 @@
CallLogAsyncTaskUtil.getCallDetails(this, getCallLogEntryUris(), mCallLogAsyncTaskListener);
}
+ @Override
+ public void onPause() {
+ if (mVoicemailPlaybackPresenter != null) {
+ mVoicemailPlaybackPresenter.onPause(isFinishing());
+ }
+ super.onPause();
+ }
+
+ @Override
+ public void onDestroy() {
+ if (mVoicemailPlaybackPresenter != null) {
+ mVoicemailPlaybackPresenter.onDestroy(isFinishing());
+ }
+ super.onDestroy();
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ if (mVoicemailPlaybackPresenter != null) {
+ mVoicemailPlaybackPresenter.onSaveInstanceState(outState);
+ }
+ }
+
+ @Override
+ public void onRestoreInstanceState(Bundle savedInstanceState) {
+ if (mVoicemailPlaybackPresenter != null) {
+ mVoicemailPlaybackPresenter.onRestoreInstanceState(savedInstanceState);
+ }
+ super.onRestoreInstanceState(savedInstanceState);
+ }
+
/**
* Handle voicemail playback or hide voicemail ui.
* <p>
@@ -274,37 +308,15 @@
* playback. If it doesn't, then don't inflate the voicemail ui.
*/
private void optionallyHandleVoicemail() {
-
if (hasVoicemail()) {
- LayoutInflater inflater =
- (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- mVoicemailHeader =
- (LinearLayout) inflater.inflate(R.layout.call_details_voicemail_header, null);
- View voicemailContainer = mVoicemailHeader.findViewById(R.id.voicemail_container);
- ListView historyList = (ListView) findViewById(R.id.history);
- historyList.addHeaderView(mVoicemailHeader);
- // Has voicemail: add the voicemail fragment. Add suitable arguments to set the uri
- // to play and optionally start the playback.
- // Do a query to fetch the voicemail status messages.
- VoicemailPlaybackFragment playbackFragment;
+ VoicemailPlaybackLayout voicemailPlaybackLayout =
+ (VoicemailPlaybackLayout) findViewById(R.id.voicemail_playback_layout);
- playbackFragment = (VoicemailPlaybackFragment) getFragmentManager().findFragmentByTag(
- VOICEMAIL_FRAGMENT_TAG);
+ mVoicemailPlaybackPresenter = new VoicemailPlaybackPresenter(this);
+ mVoicemailPlaybackPresenter.setPlaybackView(
+ voicemailPlaybackLayout, mVoicemailUri, false /* startPlayingImmediately */);
- if (playbackFragment == null) {
- playbackFragment = new VoicemailPlaybackFragment();
- Bundle fragmentArguments = new Bundle();
- fragmentArguments.putParcelable(EXTRA_VOICEMAIL_URI, mVoicemailUri);
- if (getIntent().getBooleanExtra(EXTRA_VOICEMAIL_START_PLAYBACK, false)) {
- fragmentArguments.putBoolean(EXTRA_VOICEMAIL_START_PLAYBACK, true);
- }
- playbackFragment.setArguments(fragmentArguments);
- getFragmentManager().beginTransaction()
- .add(R.id.voicemail_container, playbackFragment, VOICEMAIL_FRAGMENT_TAG)
- .commitAllowingStateLoss();
- }
-
- voicemailContainer.setVisibility(View.VISIBLE);
+ voicemailPlaybackLayout.setVisibility(View.VISIBLE);
CallLogAsyncTaskUtil.markVoicemailAsRead(this, mVoicemailUri);
}
}
diff --git a/src/com/android/dialer/DialerApplication.java b/src/com/android/dialer/DialerApplication.java
index 7bc3bb4..b177d83 100644
--- a/src/com/android/dialer/DialerApplication.java
+++ b/src/com/android/dialer/DialerApplication.java
@@ -19,14 +19,12 @@
import android.app.Application;
import android.os.Trace;
-import com.android.contacts.common.ContactPhotoManager;
import com.android.contacts.common.extensions.ExtensionsFactory;
import com.android.contacts.commonbind.analytics.AnalyticsUtil;
public class DialerApplication extends Application {
private static final String TAG = "DialerApplication";
- private ContactPhotoManager mContactPhotoManager;
@Override
public void onCreate() {
diff --git a/src/com/android/dialer/DialtactsActivity.java b/src/com/android/dialer/DialtactsActivity.java
index 56d5ad1..aefe01f 100644
--- a/src/com/android/dialer/DialtactsActivity.java
+++ b/src/com/android/dialer/DialtactsActivity.java
@@ -29,12 +29,10 @@
import android.net.Uri;
import android.os.Bundle;
import android.os.Trace;
-import android.provider.ContactsContract.Intents;
import android.speech.RecognizerIntent;
import android.support.v4.view.ViewPager;
import android.telecom.PhoneAccount;
import android.telecom.TelecomManager;
-import android.telephony.TelephonyManager;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
@@ -63,6 +61,7 @@
import com.android.contacts.common.interactions.ImportExportDialogFragment;
import com.android.contacts.common.interactions.TouchPointManager;
import com.android.contacts.common.list.OnPhoneNumberPickerActionListener;
+import com.android.contacts.common.util.PermissionsUtil;
import com.android.contacts.common.widget.FloatingActionButtonController;
import com.android.contacts.commonbind.analytics.AnalyticsUtil;
import com.android.dialer.calllog.CallLogActivity;
@@ -111,7 +110,7 @@
ActionBarController.ActivityUi {
private static final String TAG = "DialtactsActivity";
- public static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ public static final boolean DEBUG = false;
public static final String SHARED_PREFS_NAME = "com.android.dialer_preferences";
@@ -167,9 +166,7 @@
AnimationListenerAdapter mSlideInListener = new AnimationListenerAdapter() {
@Override
public void onAnimationEnd(Animation animation) {
- if (!isInSearchUi()) {
- enterSearchUi(true /* isSmartDial */, mSearchQuery, false);
- }
+ maybeEnterSearchUi();
}
};
@@ -248,11 +245,19 @@
@Override
public void show() {
+ final boolean hasContactsPermission =
+ PermissionsUtil.hasContactsPermissions(DialtactsActivity.this);
final Menu menu = getMenu();
final MenuItem clearFrequents = menu.findItem(R.id.menu_clear_frequents);
clearFrequents.setVisible(mListsFragment != null &&
mListsFragment.getSpeedDialFragment() != null &&
- mListsFragment.getSpeedDialFragment().hasFrequents());
+ mListsFragment.getSpeedDialFragment().hasFrequents() && hasContactsPermission);
+
+ menu.findItem(R.id.menu_import_export).setVisible(hasContactsPermission);
+ menu.findItem(R.id.menu_add_contact).setVisible(hasContactsPermission);
+
+ menu.findItem(R.id.menu_history).setVisible(
+ PermissionsUtil.hasPhonePermissions(DialtactsActivity.this));
super.show();
}
}
@@ -359,6 +364,7 @@
protected void onCreate(Bundle savedInstanceState) {
Trace.beginSection(TAG + " onCreate");
super.onCreate(savedInstanceState);
+
mFirstLaunch = true;
final Resources resources = getResources();
@@ -585,7 +591,7 @@
public void onClick(View view) {
switch (view.getId()) {
case R.id.floating_action_button:
- if (mListsFragment.getTabPosition() == ListsFragment.TAB_INDEX_ALL_CONTACTS) {
+ if (mListsFragment.getCurrentTabIndex() == ListsFragment.TAB_INDEX_ALL_CONTACTS) {
DialerUtils.startActivityWithErrorToast(
this,
IntentUtil.getNewContactIntent(),
@@ -695,6 +701,7 @@
mFloatingActionButtonController.scaleOut();
} else {
mFloatingActionButtonController.setVisible(false);
+ maybeEnterSearchUi();
}
mActionBarController.onDialpadUp();
@@ -818,7 +825,6 @@
protected OptionsPopupMenu buildOptionsMenu(View invoker) {
final OptionsPopupMenu popupMenu = new OptionsPopupMenu(this, invoker);
popupMenu.inflate(R.menu.dialtacts_options);
- final Menu menu = popupMenu.getMenu();
popupMenu.setOnMenuItemClickListener(this);
return popupMenu;
}
@@ -829,7 +835,9 @@
mSearchView.setText(mPendingSearchViewQuery);
mPendingSearchViewQuery = null;
}
- mActionBarController.restoreActionBarOffset();
+ if (mActionBarController != null) {
+ mActionBarController.restoreActionBarOffset();
+ }
return false;
}
@@ -1029,6 +1037,12 @@
}
}
+ private void maybeEnterSearchUi() {
+ if (!isInSearchUi()) {
+ enterSearchUi(true /* isSmartDial */, mSearchQuery, false);
+ }
+ }
+
/**
* @return True if the search UI was exited, false otherwise
*/
@@ -1162,25 +1176,25 @@
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
- position = mListsFragment.getRtlPosition(position);
- // Only scroll the button when the first tab is selected. The button should scroll from
- // the middle to right position only on the transition from the first tab to the second
- // tab.
- // If the app is in RTL mode, we need to check against the second tab, rather than the
- // first. This is because if we are scrolling between the first and second tabs, the
- // viewpager will report that the starting tab position is 1 rather than 0, due to the
- // reversal of the order of the tabs.
- final boolean isLayoutRtl = DialerUtils.isRtl();
- final boolean shouldScrollButton = position == (isLayoutRtl
- ? ListsFragment.TAB_INDEX_RECENTS : ListsFragment.TAB_INDEX_SPEED_DIAL);
- if (shouldScrollButton && !mIsLandscape) {
- mFloatingActionButtonController.onPageScrolled(
- isLayoutRtl ? 1 - positionOffset : positionOffset);
- } else if (position != ListsFragment.TAB_INDEX_SPEED_DIAL) {
+ int tabIndex = mListsFragment.getCurrentTabIndex();
+
+ // Scroll the button from center to end when moving from the Speed Dial to Recents tab.
+ // In RTL, scroll when the current tab is Recents instead of Speed Dial, because the order
+ // of the tabs is reversed and the ViewPager returns the left tab position during scroll.
+ boolean isRtl = DialerUtils.isRtl();
+ if (!isRtl && tabIndex == ListsFragment.TAB_INDEX_SPEED_DIAL && !mIsLandscape) {
+ mFloatingActionButtonController.onPageScrolled(positionOffset);
+ } else if (isRtl && tabIndex == ListsFragment.TAB_INDEX_RECENTS && !mIsLandscape) {
+ mFloatingActionButtonController.onPageScrolled(1 - positionOffset);
+ } else if (tabIndex != ListsFragment.TAB_INDEX_SPEED_DIAL) {
mFloatingActionButtonController.onPageScrolled(1);
}
+ }
- if (position == ListsFragment.TAB_INDEX_ALL_CONTACTS) {
+ @Override
+ public void onPageSelected(int position) {
+ int tabIndex = mListsFragment.getCurrentTabIndex();
+ if (tabIndex == ListsFragment.TAB_INDEX_ALL_CONTACTS) {
mFloatingActionButtonController.changeIcon(
getResources().getDrawable(R.drawable.ic_person_add_24dp),
getResources().getString(R.string.search_shortcut_create_new_contact));
@@ -1192,10 +1206,6 @@
}
@Override
- public void onPageSelected(int position) {
- }
-
- @Override
public void onPageScrollStateChanged(int state) {
}
@@ -1239,7 +1249,7 @@
*/
private void updateFloatingActionButtonControllerAlignment(boolean animate) {
int align = (!mIsLandscape &&
- mListsFragment.getTabPosition() == ListsFragment.TAB_INDEX_SPEED_DIAL) ?
+ mListsFragment.getCurrentTabIndex() == ListsFragment.TAB_INDEX_SPEED_DIAL) ?
FloatingActionButtonController.ALIGN_MIDDLE :
FloatingActionButtonController.ALIGN_END;
mFloatingActionButtonController.align(align, 0 /* offsetX */, 0 /* offsetY */, animate);
diff --git a/src/com/android/dialer/calllog/CallLogAdapter.java b/src/com/android/dialer/calllog/CallLogAdapter.java
index 523b5b9..c472ef2 100644
--- a/src/com/android/dialer/calllog/CallLogAdapter.java
+++ b/src/com/android/dialer/calllog/CallLogAdapter.java
@@ -34,6 +34,7 @@
import android.view.ViewTreeObserver;
import android.view.accessibility.AccessibilityEvent;
+import com.android.contacts.common.util.PermissionsUtil;
import com.android.dialer.PhoneCallDetails;
import com.android.dialer.PhoneCallDetailsHelper;
import com.android.dialer.R;
@@ -163,10 +164,15 @@
*/
private AccessibilityDelegate mAccessibilityDelegate = new AccessibilityDelegate() {
@Override
- public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child,
- AccessibilityEvent event) {
+ public boolean onRequestSendAccessibilityEvent(
+ ViewGroup host, View child, AccessibilityEvent event) {
if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED) {
- expandViewHolderActions((CallLogListItemViewHolder) host.getTag());
+ // Only expand if actions are not already expanded, because triggering the expand
+ // function on clicks causes the action views to lose the focus indicator.
+ CallLogListItemViewHolder viewHolder = (CallLogListItemViewHolder) host.getTag();
+ if (mCurrentlyExpandedPosition != viewHolder.getAdapterPosition()) {
+ expandViewHolderActions((CallLogListItemViewHolder) host.getTag());
+ }
}
return super.onRequestSendAccessibilityEvent(host, child, event);
}
@@ -184,8 +190,9 @@
public boolean onPreDraw() {
// We only wanted to listen for the first draw (and this is it).
unregisterPreDrawListener();
-
- mContactInfoCache.start();
+ if (PermissionsUtil.hasContactsPermissions(mContext)) {
+ mContactInfoCache.start();
+ }
return true;
}
@@ -205,6 +212,9 @@
mContactInfoCache = new ContactInfoCache(
mContactInfoHelper, mOnContactInfoChangedListener);
+ if (!PermissionsUtil.hasContactsPermissions(context)) {
+ mContactInfoCache.disableRequestProcessing();
+ }
Resources resources = mContext.getResources();
CallTypeHelper callTypeHelper = new CallTypeHelper(resources);
@@ -534,7 +544,7 @@
@VisibleForTesting
void disableRequestProcessingForTest() {
// TODO: Remove this and test the cache directly.
- mContactInfoCache.disableRequestProcessingForTest();
+ mContactInfoCache.disableRequestProcessing();
}
@VisibleForTesting
diff --git a/src/com/android/dialer/calllog/CallLogAsyncTaskUtil.java b/src/com/android/dialer/calllog/CallLogAsyncTaskUtil.java
index 20e213c..aa186eb 100644
--- a/src/com/android/dialer/calllog/CallLogAsyncTaskUtil.java
+++ b/src/com/android/dialer/calllog/CallLogAsyncTaskUtil.java
@@ -34,6 +34,8 @@
import com.android.dialer.util.AsyncTaskExecutors;
import com.android.dialer.util.TelecomUtil;
+import com.google.common.annotations.VisibleForTesting;
+
public class CallLogAsyncTaskUtil {
private static String TAG = CallLogAsyncTaskUtil.class.getSimpleName();
@@ -297,4 +299,9 @@
}
});
}
+
+ @VisibleForTesting
+ public static void resetForTest() {
+ sAsyncTaskExecutor = null;
+ }
}
diff --git a/src/com/android/dialer/calllog/CallLogFragment.java b/src/com/android/dialer/calllog/CallLogFragment.java
index e8ed17e..36d9bb6 100644
--- a/src/com/android/dialer/calllog/CallLogFragment.java
+++ b/src/com/android/dialer/calllog/CallLogFragment.java
@@ -92,7 +92,6 @@
/** Whether there is at least one voicemail source installed. */
private boolean mVoicemailSourcesAvailable = false;
- private VoicemailStatusHelper mVoicemailStatusHelper;
private View mEmptyListView;
private KeyguardManager mKeyguardManager;
@@ -188,7 +187,6 @@
mContactsObserver);
resolver.registerContentObserver(Status.CONTENT_URI, true, mVoicemailStatusObserver);
setHasOptionsMenu(true);
- fetchCalls();
}
/** Called by the CallLogQueryHandler when the list of calls has been fetched or updated. */
@@ -203,7 +201,7 @@
// This will update the state of the "Clear call log" menu item.
getActivity().invalidateOptionsMenu();
- boolean showListView = cursor.getCount() > 0;
+ boolean showListView = cursor != null && cursor.getCount() > 0;
mRecyclerView.setVisibility(showListView ? View.VISIBLE : View.GONE);
mEmptyListView.setVisibility(!showListView ? View.VISIBLE : View.GONE);
@@ -266,6 +264,7 @@
mRecyclerView.setHasFixedSize(true);
mLayoutManager = new LinearLayoutManager(getActivity());
mRecyclerView.setLayoutManager(mLayoutManager);
+ mEmptyListView = view.findViewById(R.id.empty_list_view);
String currentCountryIso = GeoUtil.getCurrentCountryIso(getActivity());
boolean isShowingRecentsTab = mLogLimit != NO_LOG_LIMIT || mDateLimit != NO_DATE_LIMIT;
@@ -277,15 +276,13 @@
this);
mRecyclerView.setAdapter(mAdapter);
- mVoicemailStatusHelper = new VoicemailStatusHelperImpl();
+ fetchCalls();
return view;
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
- mEmptyListView = view.findViewById(R.id.empty_list_view);
-
updateEmptyMessage(mCallTypeFilter);
}
diff --git a/src/com/android/dialer/calllog/CallLogNotificationsService.java b/src/com/android/dialer/calllog/CallLogNotificationsService.java
index 22809db..9f48b09 100644
--- a/src/com/android/dialer/calllog/CallLogNotificationsService.java
+++ b/src/com/android/dialer/calllog/CallLogNotificationsService.java
@@ -22,6 +22,7 @@
import android.net.Uri;
import android.util.Log;
+import com.android.contacts.common.util.PermissionsUtil;
import com.android.dialer.util.TelecomUtil;
/**
@@ -78,6 +79,10 @@
return;
}
+ if (!PermissionsUtil.hasPhonePermissions(this)) {
+ return;
+ }
+
if (ACTION_MARK_NEW_VOICEMAILS_AS_OLD.equals(intent.getAction())) {
mVoicemailQueryHandler.markNewVoicemailsAsOld();
} else if (ACTION_UPDATE_NOTIFICATIONS.equals(intent.getAction())) {
diff --git a/src/com/android/dialer/calllog/CallLogQueryHandler.java b/src/com/android/dialer/calllog/CallLogQueryHandler.java
index 49d6a41..a0e563a 100644
--- a/src/com/android/dialer/calllog/CallLogQueryHandler.java
+++ b/src/com/android/dialer/calllog/CallLogQueryHandler.java
@@ -35,6 +35,7 @@
import android.util.Log;
import com.android.contacts.common.database.NoNullCursorAsyncQueryHandler;
+import com.android.contacts.common.util.PermissionsUtil;
import com.android.dialer.util.TelecomUtil;
import com.android.dialer.voicemail.VoicemailStatusHelperImpl;
@@ -93,6 +94,10 @@
Log.w(TAG, "Exception on background worker thread", e);
} catch (IllegalArgumentException e) {
Log.w(TAG, "ContactsProvider not present on device", e);
+ } catch (SecurityException e) {
+ // Shouldn't happen if we are protecting the entry points correctly,
+ // but just in case.
+ Log.w(TAG, "No permission to access ContactsProvider.", e);
}
}
}
@@ -124,7 +129,11 @@
*/
public void fetchCalls(int callType, long newerThan) {
cancelFetch();
- fetchCalls(QUERY_CALLLOG_TOKEN, callType, false /* newOnly */, newerThan);
+ if (PermissionsUtil.hasPhonePermissions(mContext)) {
+ fetchCalls(QUERY_CALLLOG_TOKEN, callType, false /* newOnly */, newerThan);
+ } else {
+ updateAdapterData(null);
+ }
}
public void fetchCalls(int callType) {
@@ -187,6 +196,9 @@
/** Updates all new calls to mark them as old. */
public void markNewCallsAsOld() {
+ if (!PermissionsUtil.hasPhonePermissions(mContext)) {
+ return;
+ }
// Mark all "new" calls as not new anymore.
StringBuilder where = new StringBuilder();
where.append(Calls.NEW);
@@ -201,6 +213,9 @@
/** Updates all missed calls to mark them as read. */
public void markMissedCallsAsRead() {
+ if (!PermissionsUtil.hasPhonePermissions(mContext)) {
+ return;
+ }
// Mark all "new" calls as not new anymore.
StringBuilder where = new StringBuilder();
where.append(Calls.IS_READ).append(" = 0");
diff --git a/src/com/android/dialer/calllog/ContactInfoHelper.java b/src/com/android/dialer/calllog/ContactInfoHelper.java
index 38c9bba..9a660e1 100644
--- a/src/com/android/dialer/calllog/ContactInfoHelper.java
+++ b/src/com/android/dialer/calllog/ContactInfoHelper.java
@@ -30,6 +30,7 @@
import android.util.Log;
import com.android.contacts.common.util.Constants;
+import com.android.contacts.common.util.PermissionsUtil;
import com.android.contacts.common.util.PhoneNumberHelper;
import com.android.contacts.common.util.UriUtils;
import com.android.dialer.service.CachedNumberLookupService;
@@ -162,6 +163,9 @@
if (uri == null) {
return null;
}
+ if (!PermissionsUtil.hasContactsPermissions(mContext)) {
+ return ContactInfo.EMPTY;
+ }
final ContactInfo info;
Cursor phonesCursor =
mContext.getContentResolver().query(uri, PhoneQuery._PROJECTION, null, null, null);
diff --git a/src/com/android/dialer/contactinfo/ContactInfoCache.java b/src/com/android/dialer/contactinfo/ContactInfoCache.java
index 2bb0f1e..568f488 100644
--- a/src/com/android/dialer/contactinfo/ContactInfoCache.java
+++ b/src/com/android/dialer/contactinfo/ContactInfoCache.java
@@ -51,7 +51,7 @@
private volatile boolean mDone = false;
public QueryThread() {
- super("CallLogAdapter.QueryThread");
+ super("ContactInfoCache.QueryThread");
}
public void stopProcessing() {
@@ -316,20 +316,12 @@
&& TextUtils.equals(callLogInfo.label, info.label);
}
- /**
- * Can be set to true by tests to disable processing of requests.
- */
- @VisibleForTesting
private volatile boolean mRequestProcessingDisabled = false;
/**
* Sets whether processing of requests for contact details should be enabled.
- *
- * This method should be called in tests to disable such processing of requests when not
- * needed.
*/
- @VisibleForTesting
- public void disableRequestProcessingForTest() {
+ public void disableRequestProcessing() {
mRequestProcessingDisabled = true;
}
diff --git a/src/com/android/dialer/database/DialerDatabaseHelper.java b/src/com/android/dialer/database/DialerDatabaseHelper.java
index 2177878..eec24f5 100644
--- a/src/com/android/dialer/database/DialerDatabaseHelper.java
+++ b/src/com/android/dialer/database/DialerDatabaseHelper.java
@@ -36,6 +36,7 @@
import android.text.TextUtils;
import android.util.Log;
+import com.android.contacts.common.util.PermissionsUtil;
import com.android.contacts.common.util.StopWatch;
import com.android.dialer.R;
import com.android.dialer.dialpad.SmartDialNameMatcher;
@@ -485,7 +486,9 @@
* Starts the database upgrade process in the background.
*/
public void startSmartDialUpdateThread() {
- new SmartDialUpdateAsyncTask().execute();
+ if (PermissionsUtil.hasContactsPermissions(mContext)) {
+ new SmartDialUpdateAsyncTask().execute();
+ }
}
private class SmartDialUpdateAsyncTask extends AsyncTask {
diff --git a/src/com/android/dialer/dialpad/DialpadFragment.java b/src/com/android/dialer/dialpad/DialpadFragment.java
index 8d27c14..89cab41 100644
--- a/src/com/android/dialer/dialpad/DialpadFragment.java
+++ b/src/com/android/dialer/dialpad/DialpadFragment.java
@@ -70,6 +70,7 @@
import com.android.contacts.common.ContactsUtils;
import com.android.contacts.common.GeoUtil;
+import com.android.contacts.common.util.PermissionsUtil;
import com.android.contacts.common.util.PhoneNumberFormatter;
import com.android.contacts.common.util.StopWatch;
import com.android.contacts.common.widget.FloatingActionButtonController;
@@ -441,6 +442,9 @@
setFormattedDigits(converted, null);
return true;
} else {
+ if (!PermissionsUtil.hasContactsPermissions(getActivity())) {
+ return false;
+ }
String type = intent.getType();
if (People.CONTENT_ITEM_TYPE.equals(type)
|| Phones.CONTENT_ITEM_TYPE.equals(type)) {
@@ -1596,6 +1600,9 @@
*/
private void queryLastOutgoingCall() {
mLastNumberDialed = EMPTY_NUMBER;
+ if (!PermissionsUtil.hasPhonePermissions(getActivity())) {
+ return;
+ }
CallLogAsync.GetLastOutgoingCallArgs lastCallArgs =
new CallLogAsync.GetLastOutgoingCallArgs(
getActivity(),
diff --git a/src/com/android/dialer/dialpad/SmartDialCursorLoader.java b/src/com/android/dialer/dialpad/SmartDialCursorLoader.java
index 372692e..f83f18c 100644
--- a/src/com/android/dialer/dialpad/SmartDialCursorLoader.java
+++ b/src/com/android/dialer/dialpad/SmartDialCursorLoader.java
@@ -25,6 +25,7 @@
import android.util.Log;
import com.android.contacts.common.list.PhoneNumberListAdapter.PhoneQuery;
+import com.android.contacts.common.util.PermissionsUtil;
import com.android.dialer.database.DialerDatabaseHelper;
import com.android.dialer.database.DialerDatabaseHelper.ContactNumber;
import com.android.dialerbind.DatabaseHelperManager;
@@ -77,6 +78,10 @@
Log.v(TAG, "Load in background " + mQuery);
}
+ if (!PermissionsUtil.hasContactsPermissions(mContext)) {
+ return new MatrixCursor(PhoneQuery.PROJECTION_PRIMARY);
+ }
+
/** Loads results from the database helper. */
final DialerDatabaseHelper dialerDatabaseHelper = DatabaseHelperManager.getDatabaseHelper(
mContext);
diff --git a/src/com/android/dialer/interactions/UndemoteOutgoingCallReceiver.java b/src/com/android/dialer/interactions/UndemoteOutgoingCallReceiver.java
index 960a31b..fd3d512 100644
--- a/src/com/android/dialer/interactions/UndemoteOutgoingCallReceiver.java
+++ b/src/com/android/dialer/interactions/UndemoteOutgoingCallReceiver.java
@@ -27,6 +27,8 @@
import android.provider.ContactsContract.PinnedPositions;
import android.text.TextUtils;
+import com.android.contacts.common.util.PermissionsUtil;
+
/**
* This broadcast receiver is used to listen to outgoing calls and undemote formerly demoted
* contacts if a phone call is made to a phone number belonging to that contact.
@@ -39,12 +41,15 @@
@Override
public void onReceive(final Context context, Intent intent) {
+ if (!PermissionsUtil.hasContactsPermissions(context)) {
+ return;
+ }
if (intent != null && Intent.ACTION_NEW_OUTGOING_CALL.equals(intent.getAction())) {
final String number = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);
if (TextUtils.isEmpty(number)) {
return;
}
- final Thread thread = new Thread() {
+ new Thread() {
@Override
public void run() {
final long id = getContactIdFromPhoneNumber(context, number);
@@ -52,8 +57,7 @@
undemoteContactWithId(context, id);
}
}
- };
- thread.start();
+ }.start();
}
}
diff --git a/src/com/android/dialer/list/AllContactsFragment.java b/src/com/android/dialer/list/AllContactsFragment.java
index 94efc48..eaa5cc8 100644
--- a/src/com/android/dialer/list/AllContactsFragment.java
+++ b/src/com/android/dialer/list/AllContactsFragment.java
@@ -29,6 +29,7 @@
import com.android.contacts.common.list.ContactEntryListFragment;
import com.android.contacts.common.list.ContactListFilter;
import com.android.contacts.common.list.DefaultContactListAdapter;
+import com.android.contacts.common.util.PermissionsUtil;
import com.android.contacts.common.util.ViewUtil;
import com.android.dialer.R;
import com.android.dialer.util.DialerUtils;
@@ -60,7 +61,18 @@
}
@Override
+ protected void startLoading() {
+ if (PermissionsUtil.hasContactsPermissions(getActivity())) {
+ super.startLoading();
+ }
+ }
+
+ @Override
protected ContactEntryListAdapter createListAdapter() {
+ if (!PermissionsUtil.hasContactsPermissions(getActivity())) {
+ return new EmptyContactsListAdapter(getActivity());
+ }
+
final DefaultContactListAdapter adapter = new DefaultContactListAdapter(getActivity()) {
@Override
protected void bindView(View itemView, int partition, Cursor cursor, int position) {
diff --git a/src/com/android/dialer/list/EmptyContactsListAdapter.java b/src/com/android/dialer/list/EmptyContactsListAdapter.java
new file mode 100644
index 0000000..54bd477
--- /dev/null
+++ b/src/com/android/dialer/list/EmptyContactsListAdapter.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dialer.list;
+
+import android.content.Context;
+import android.content.CursorLoader;
+
+import com.android.contacts.common.list.ContactEntryListAdapter;
+
+/**
+ * Used to display an empty contact list when we don't have the permissions to read contacts.
+ */
+public class EmptyContactsListAdapter extends ContactEntryListAdapter {
+
+ public EmptyContactsListAdapter(Context context) {
+ super(context);
+ }
+
+ @Override
+ public String getContactDisplayName(int position) {
+ return null;
+ }
+
+ @Override
+ public void configureLoader(CursorLoader loader, long directoryId) {
+ loader.setUri(null);
+ }
+
+ @Override
+ public int getCount() {
+ return 0;
+ }
+}
diff --git a/src/com/android/dialer/list/ListsFragment.java b/src/com/android/dialer/list/ListsFragment.java
index 0ac6b1a..a0e443c 100644
--- a/src/com/android/dialer/list/ListsFragment.java
+++ b/src/com/android/dialer/list/ListsFragment.java
@@ -62,9 +62,6 @@
// Oldest recents entry to display is 2 weeks old.
private static final long OLDEST_RECENTS_DATE = 1000L * 60 * 60 * 24 * 14;
- private static final String KEY_LAST_DISMISSED_CALL_SHORTCUT_DATE =
- "key_last_dismissed_call_shortcut_date";
-
public interface HostInterface {
public ActionBarController getActionBarController();
}
@@ -92,18 +89,7 @@
/**
* The position of the currently selected tab.
*/
- private int mTabPosition = TAB_INDEX_SPEED_DIAL;
-
- /**
- * Call shortcuts older than this date (persisted in shared preferences) will not show up in
- * at the top of the screen
- */
- private long mLastCallShortcutDate = 0;
-
- /**
- * The date of the current call shortcut that is showing on screen.
- */
- private long mCurrentCallShortcutDate = 0;
+ private int mTabIndex = TAB_INDEX_SPEED_DIAL;
public class ViewPagerAdapter extends FragmentPagerAdapter {
public ViewPagerAdapter(FragmentManager fm) {
@@ -182,12 +168,9 @@
public void onResume() {
Trace.beginSection(TAG + " onResume");
super.onResume();
- final SharedPreferences prefs = getActivity().getSharedPreferences(
- DialtactsActivity.SHARED_PREFS_NAME, Context.MODE_PRIVATE);
- mLastCallShortcutDate = prefs.getLong(KEY_LAST_DISMISSED_CALL_SHORTCUT_DATE, 0);
mActionBar = getActivity().getActionBar();
if (getUserVisibleHint()) {
- sendScreenViewForPosition(mViewPager.getCurrentItem());
+ sendScreenViewForCurrentPosition();
}
// Fetch voicemail status to determine if we should show the voicemail tab.
@@ -208,7 +191,7 @@
mViewPager = (ViewPager) parentView.findViewById(R.id.lists_pager);
mViewPagerAdapter = new ViewPagerAdapter(getChildFragmentManager());
mViewPager.setAdapter(mViewPagerAdapter);
- mViewPager.setOffscreenPageLimit(TAB_COUNT_DEFAULT - 1);
+ mViewPager.setOffscreenPageLimit(TAB_COUNT_WITH_VOICEMAIL - 1);
mViewPager.setOnPageChangeListener(this);
mViewPager.setCurrentItem(getRtlPosition(TAB_INDEX_SPEED_DIAL));
@@ -245,6 +228,8 @@
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
+ mTabIndex = getRtlPosition(position);
+
final int count = mOnPageChangeListeners.size();
for (int i = 0; i < count; i++) {
mOnPageChangeListeners.get(i).onPageScrolled(position, positionOffset,
@@ -254,13 +239,13 @@
@Override
public void onPageSelected(int position) {
- mTabPosition = getRtlPosition(position);
+ mTabIndex = getRtlPosition(position);
final int count = mOnPageChangeListeners.size();
for (int i = 0; i < count; i++) {
mOnPageChangeListeners.get(i).onPageSelected(position);
}
- sendScreenViewForPosition(position);
+ sendScreenViewForCurrentPosition();
}
@Override
@@ -293,8 +278,8 @@
return false;
}
- public int getTabPosition() {
- return mTabPosition;
+ public int getCurrentTabIndex() {
+ return mTabIndex;
}
public void showRemoveView(boolean show) {
@@ -316,7 +301,7 @@
return mRemoveView;
}
- public int getRtlPosition(int position) {
+ private int getRtlPosition(int position) {
if (DialerUtils.isRtl()) {
return mViewPagerAdapter.getCount() - 1 - position;
}
@@ -324,15 +309,12 @@
}
public void sendScreenViewForCurrentPosition() {
- sendScreenViewForPosition(mViewPager.getCurrentItem());
- }
-
- private void sendScreenViewForPosition(int position) {
if (!isResumed()) {
return;
}
+
String fragmentName;
- switch (getRtlPosition(position)) {
+ switch (getCurrentTabIndex()) {
case TAB_INDEX_SPEED_DIAL:
fragmentName = SpeedDialFragment.class.getSimpleName();
break;
diff --git a/src/com/android/dialer/list/RegularSearchListAdapter.java b/src/com/android/dialer/list/RegularSearchListAdapter.java
index 6c70458..2be8a1d 100644
--- a/src/com/android/dialer/list/RegularSearchListAdapter.java
+++ b/src/com/android/dialer/list/RegularSearchListAdapter.java
@@ -22,6 +22,7 @@
import com.android.contacts.common.CallUtil;
import com.android.contacts.common.list.DirectoryPartition;
+import com.android.contacts.common.util.PhoneNumberHelper;
import com.android.dialer.calllog.ContactInfo;
import com.android.dialer.service.CachedNumberLookupService;
import com.android.dialer.service.CachedNumberLookupService.CachedContactInfo;
@@ -30,6 +31,7 @@
* List adapter to display regular search results.
*/
public class RegularSearchListAdapter extends DialerPhoneNumberListAdapter {
+ private boolean mIsQuerySipAddress;
public RegularSearchListAdapter(Context context) {
super(context);
@@ -67,12 +69,24 @@
}
@Override
+ public String getFormattedQueryString() {
+ if (mIsQuerySipAddress) {
+ // Return unnormalized SIP address
+ return getQueryString();
+ }
+ return super.getFormattedQueryString();
+ }
+
+ @Override
public void setQueryString(String queryString) {
// Don't show actions if the query string contains a letter.
final boolean showNumberShortcuts = !TextUtils.isEmpty(getFormattedQueryString())
&& hasDigitsInQueryString();
+ // Email addresses that could be SIP addresses are an exception.
+ mIsQuerySipAddress = PhoneNumberHelper.isUriNumber(queryString);
boolean changed = false;
- changed |= setShortcutEnabled(SHORTCUT_DIRECT_CALL, showNumberShortcuts);
+ changed |= setShortcutEnabled(SHORTCUT_DIRECT_CALL,
+ showNumberShortcuts || mIsQuerySipAddress);
changed |= setShortcutEnabled(SHORTCUT_SEND_SMS_MESSAGE, showNumberShortcuts);
changed |= setShortcutEnabled(SHORTCUT_MAKE_VIDEO_CALL,
showNumberShortcuts && CallUtil.isVideoEnabled(getContext()));
diff --git a/src/com/android/dialer/list/SearchFragment.java b/src/com/android/dialer/list/SearchFragment.java
index c314478..f86c0e5 100644
--- a/src/com/android/dialer/list/SearchFragment.java
+++ b/src/com/android/dialer/list/SearchFragment.java
@@ -37,6 +37,7 @@
import com.android.contacts.common.list.ContactListItemView;
import com.android.contacts.common.list.OnPhoneNumberPickerActionListener;
import com.android.contacts.common.list.PhoneNumberPickerFragment;
+import com.android.contacts.common.util.PermissionsUtil;
import com.android.contacts.common.util.ViewUtil;
import com.android.contacts.commonbind.analytics.AnalyticsUtil;
import com.android.dialer.dialpad.DialpadFragment.ErrorDialogFragment;
@@ -287,4 +288,14 @@
listView.getPaddingEnd(),
listView.getPaddingBottom());
}
+
+ @Override
+ protected void startLoading() {
+ if (PermissionsUtil.hasContactsPermissions(getActivity())) {
+ super.startLoading();
+ } else if (TextUtils.isEmpty(getQueryString())) {
+ // Clear out any existing call shortcuts.
+ getAdapter().setQueryString(null);
+ }
+ }
}
diff --git a/src/com/android/dialer/list/SpeedDialFragment.java b/src/com/android/dialer/list/SpeedDialFragment.java
index e72b250..541cdf6 100644
--- a/src/com/android/dialer/list/SpeedDialFragment.java
+++ b/src/com/android/dialer/list/SpeedDialFragment.java
@@ -23,7 +23,6 @@
import android.app.LoaderManager;
import android.content.CursorLoader;
import android.content.Loader;
-import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.Rect;
import android.net.Uri;
@@ -43,12 +42,12 @@
import android.widget.FrameLayout.LayoutParams;
import android.widget.ImageView;
import android.widget.ListView;
-import android.widget.RelativeLayout;
import com.android.contacts.common.ContactPhotoManager;
import com.android.contacts.common.ContactTileLoaderFactory;
import com.android.contacts.common.list.ContactTileView;
import com.android.contacts.common.list.OnPhoneNumberPickerActionListener;
+import com.android.contacts.common.util.PermissionsUtil;
import com.android.dialer.R;
import com.android.dialer.util.DialerUtils;
@@ -194,7 +193,9 @@
Trace.beginSection(TAG + " onResume");
super.onResume();
- getLoaderManager().getLoader(LOADER_ID_CONTACT_TILE).forceLoad();
+ if (PermissionsUtil.hasContactsPermissions(getActivity())) {
+ getLoaderManager().getLoader(LOADER_ID_CONTACT_TILE).forceLoad();
+ }
Trace.endSection();
}
@@ -286,7 +287,11 @@
// Use initLoader() instead of restartLoader() to refraining unnecessary reload.
// This method call implicitly assures ContactTileLoaderListener's onLoadFinished() will
// be called, on which we'll check if "all" contacts should be reloaded again or not.
- getLoaderManager().initLoader(LOADER_ID_CONTACT_TILE, null, mContactTileLoaderListener);
+ if (PermissionsUtil.hasContactsPermissions(activity)) {
+ getLoaderManager().initLoader(LOADER_ID_CONTACT_TILE, null, mContactTileLoaderListener);
+ } else {
+ setEmptyViewVisibility(true);
+ }
}
/**
diff --git a/src/com/android/dialer/voicemail/VoicemailPlaybackFragment.java b/src/com/android/dialer/voicemail/VoicemailPlaybackFragment.java
deleted file mode 100644
index 8aa0197..0000000
--- a/src/com/android/dialer/voicemail/VoicemailPlaybackFragment.java
+++ /dev/null
@@ -1,466 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.dialer.voicemail;
-
-import static com.android.dialer.CallDetailActivity.EXTRA_VOICEMAIL_START_PLAYBACK;
-import static com.android.dialer.CallDetailActivity.EXTRA_VOICEMAIL_URI;
-
-import android.app.Activity;
-import android.app.Fragment;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.database.ContentObserver;
-import android.database.Cursor;
-import android.media.AudioManager;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.PowerManager;
-import android.provider.VoicemailContract;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageButton;
-import android.widget.SeekBar;
-import android.widget.TextView;
-
-import com.android.common.io.MoreCloseables;
-import com.android.contacts.commonbind.analytics.AnalyticsUtil;
-import com.android.dialer.R;
-import com.android.dialer.util.AsyncTaskExecutors;
-import com.android.ex.variablespeed.MediaPlayerProxy;
-import com.android.ex.variablespeed.VariableSpeed;
-
-import com.google.common.base.Preconditions;
-
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
-
-import javax.annotation.concurrent.GuardedBy;
-import javax.annotation.concurrent.NotThreadSafe;
-
-/**
- * Displays and plays back a single voicemail.
- * <p>
- * When the Activity containing this Fragment is created, voicemail playback
- * will begin immediately. The Activity is expected to be started via an intent
- * containing a suitable voicemail uri to playback.
- * <p>
- * This class is not thread-safe, it is thread-confined. All calls to all public
- * methods on this class are expected to come from the main ui thread.
- */
-@NotThreadSafe
-public class VoicemailPlaybackFragment extends Fragment {
- private static final String TAG = VoicemailPlaybackFragment.class.getSimpleName();
- private static final int NUMBER_OF_THREADS_IN_POOL = 2;
- private static final String[] HAS_CONTENT_PROJECTION = new String[] {
- VoicemailContract.Voicemails.HAS_CONTENT,
- };
-
- private VoicemailPlaybackPresenter mPresenter;
- private static int mMediaPlayerRefCount = 0;
- private static MediaPlayerProxy mMediaPlayerInstance;
- private static ScheduledExecutorService mScheduledExecutorService;
- private View mPlaybackLayout;
-
- private PowerManager.WakeLock mProximityWakeLock;
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- mPlaybackLayout = inflater.inflate(R.layout.playback_layout, null);
- return mPlaybackLayout;
- }
-
- @Override
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
- Bundle arguments = getArguments();
- Preconditions.checkNotNull(arguments, "fragment must be started with arguments");
- Uri voicemailUri = arguments.getParcelable(EXTRA_VOICEMAIL_URI);
- Preconditions.checkNotNull(voicemailUri, "fragment must contain EXTRA_VOICEMAIL_URI");
- boolean startPlayback = arguments.getBoolean(EXTRA_VOICEMAIL_START_PLAYBACK, false);
-
- PowerManager powerManager =
- (PowerManager) getActivity().getSystemService(Context.POWER_SERVICE);
- if (powerManager.isWakeLockLevelSupported(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK)) {
- mProximityWakeLock = powerManager.newWakeLock(
- PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, TAG);
- } else {
- mProximityWakeLock = null;
- }
-
- mPresenter = new VoicemailPlaybackPresenter(
- createPlaybackViewImpl(),
- getMediaPlayerInstance(),
- voicemailUri,
- getScheduledExecutorServiceInstance(),
- startPlayback,
- AsyncTaskExecutors.createAsyncTaskExecutor(),
- mProximityWakeLock);
- mPresenter.onCreate(savedInstanceState);
- }
-
- @Override
- public void onSaveInstanceState(Bundle outState) {
- mPresenter.onSaveInstanceState(outState);
- super.onSaveInstanceState(outState);
- }
-
- @Override
- public void onStart() {
- super.onStart();
- AnalyticsUtil.sendScreenView(this);
- }
-
- @Override
- public void onViewStateRestored(Bundle savedInstanceState) {
- mPresenter.onRestoreInstanceState(savedInstanceState);
- super.onViewStateRestored(savedInstanceState);
- }
-
- @Override
- public void onDestroy() {
- shutdownMediaPlayer();
- mPresenter.onDestroy();
- super.onDestroy();
- }
-
- @Override
- public void onPause() {
- releaseProximitySensor(false /* waitForFarState */);
- mPresenter.onPause();
- super.onPause();
- }
-
- private PlaybackViewImpl createPlaybackViewImpl() {
- return new PlaybackViewImpl(new ActivityReference(), getActivity().getApplicationContext(),
- mPlaybackLayout);
- }
-
- private static synchronized MediaPlayerProxy getMediaPlayerInstance() {
- ++mMediaPlayerRefCount;
- if (mMediaPlayerInstance == null) {
- mMediaPlayerInstance = VariableSpeed.createVariableSpeed(
- getScheduledExecutorServiceInstance());
- }
- return mMediaPlayerInstance;
- }
-
- private static synchronized ScheduledExecutorService getScheduledExecutorServiceInstance() {
- if (mScheduledExecutorService == null) {
- mScheduledExecutorService = Executors.newScheduledThreadPool(
- NUMBER_OF_THREADS_IN_POOL);
- }
- return mScheduledExecutorService;
- }
-
- private static synchronized void shutdownMediaPlayer() {
- --mMediaPlayerRefCount;
- if (mMediaPlayerRefCount > 0) {
- return;
- }
- if (mScheduledExecutorService != null) {
- mScheduledExecutorService.shutdown();
- mScheduledExecutorService = null;
- }
- if (mMediaPlayerInstance != null) {
- mMediaPlayerInstance.release();
- mMediaPlayerInstance = null;
- }
- }
-
- private void acquireProximitySensor() {
- if (mProximityWakeLock == null) {
- return;
- }
- if (!mProximityWakeLock.isHeld()) {
- Log.i(TAG, "Acquiring proximity wake lock");
- mProximityWakeLock.acquire();
- } else {
- Log.i(TAG, "Proximity wake lock already acquired");
- }
- }
-
- private void releaseProximitySensor(boolean waitForFarState) {
- if (mProximityWakeLock == null) {
- return;
- }
- if (mProximityWakeLock.isHeld()) {
- Log.i(TAG, "Releasing proximity wake lock");
- int flags = waitForFarState ? PowerManager.RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY : 0;
- mProximityWakeLock.release(flags);
- } else {
- Log.i(TAG, "Proximity wake lock already released");
- }
- }
-
- /**
- * Formats a number of milliseconds as something that looks like {@code 00:05}.
- * <p>
- * We always use four digits, two for minutes two for seconds. In the very unlikely event
- * that the voicemail duration exceeds 99 minutes, the display is capped at 99 minutes.
- */
- private static String formatAsMinutesAndSeconds(int millis) {
- int seconds = millis / 1000;
- int minutes = seconds / 60;
- seconds -= minutes * 60;
- if (minutes > 99) {
- minutes = 99;
- }
- return String.format("%02d:%02d", minutes, seconds);
- }
-
- /**
- * An object that can provide us with an Activity.
- * <p>
- * Fragments suffer the drawback that the Activity they belong to may sometimes be null. This
- * can happen if the Fragment is detached, for example. In that situation a call to
- * {@link Fragment#getString(int)} will throw and {@link IllegalStateException}. Also, calling
- * {@link Fragment#getActivity()} is dangerous - it may sometimes return null. And thus blindly
- * calling a method on the result of getActivity() is dangerous too.
- * <p>
- * To work around this, I have made the {@link PlaybackViewImpl} class static, so that it does
- * not have access to any Fragment methods directly. Instead it uses an application Context for
- * things like accessing strings, accessing system services. It only uses the Activity when it
- * absolutely needs it - and does so through this class. This makes it easy to see where we have
- * to check for null properly.
- */
- private final class ActivityReference {
- /** Gets this Fragment's Activity: <b>may be null</b>. */
- public final Activity get() {
- return getActivity();
- }
- }
-
- /** Methods required by the PlaybackView for the VoicemailPlaybackPresenter. */
- private final class PlaybackViewImpl implements VoicemailPlaybackPresenter.PlaybackView {
- private final ActivityReference mActivityReference;
- private final Context mApplicationContext;
- private final SeekBar mPlaybackSeek;
- private final ImageButton mStartStopButton;
- private final ImageButton mPlaybackSpeakerphone;
- private final TextView mPlaybackPosition;
-
- public PlaybackViewImpl(ActivityReference activityReference, Context applicationContext,
- View playbackLayout) {
- Preconditions.checkNotNull(activityReference);
- Preconditions.checkNotNull(applicationContext);
- Preconditions.checkNotNull(playbackLayout);
- mActivityReference = activityReference;
- mApplicationContext = applicationContext;
- mPlaybackSeek = (SeekBar) playbackLayout.findViewById(R.id.playback_seek);
- mStartStopButton = (ImageButton) playbackLayout.findViewById(
- R.id.playback_start_stop);
- mPlaybackSpeakerphone = (ImageButton) playbackLayout.findViewById(
- R.id.playback_speakerphone);
- mPlaybackPosition =
- (TextView) playbackLayout.findViewById(R.id.playback_position_text);
- }
-
- @Override
- public void finish() {
- Activity activity = mActivityReference.get();
- if (activity != null) {
- activity.finish();
- }
- }
-
- @Override
- public void runOnUiThread(Runnable runnable) {
- Activity activity = mActivityReference.get();
- if (activity != null) {
- activity.runOnUiThread(runnable);
- }
- }
-
- @Override
- public Context getDataSourceContext() {
- return mApplicationContext;
- }
-
- @Override
- public void setStartStopListener(View.OnClickListener listener) {
- mStartStopButton.setOnClickListener(listener);
- }
-
- @Override
- public void setSpeakerphoneListener(View.OnClickListener listener) {
- mPlaybackSpeakerphone.setOnClickListener(listener);
- }
-
- @Override
- public void setPositionSeekListener(SeekBar.OnSeekBarChangeListener listener) {
- mPlaybackSeek.setOnSeekBarChangeListener(listener);
- }
-
- @Override
- public void playbackStarted() {
- mStartStopButton.setImageResource(R.drawable.ic_hold_pause);
- }
-
- @Override
- public void playbackStopped() {
- mStartStopButton.setImageResource(R.drawable.ic_play);
- }
-
- @Override
- public void enableProximitySensor() {
- // Only change the state if the activity is still around.
- Activity activity = mActivityReference.get();
- if (activity != null) {
- acquireProximitySensor();
- }
- }
-
- @Override
- public void disableProximitySensor() {
- // Only change the state if the activity is still around.
- Activity activity = mActivityReference.get();
- if (activity != null) {
- releaseProximitySensor(true /* waitForFarState */);
- }
- }
-
- @Override
- public void registerContentObserver(Uri uri, ContentObserver observer) {
- mApplicationContext.getContentResolver().registerContentObserver(uri, false, observer);
- }
-
- @Override
- public void unregisterContentObserver(ContentObserver observer) {
- mApplicationContext.getContentResolver().unregisterContentObserver(observer);
- }
-
- @Override
- public void setClipPosition(int clipPositionInMillis, int clipLengthInMillis) {
- int seekBarPosition = Math.max(0, clipPositionInMillis);
- int seekBarMax = Math.max(seekBarPosition, clipLengthInMillis);
- if (mPlaybackSeek.getMax() != seekBarMax) {
- mPlaybackSeek.setMax(seekBarMax);
- }
- mPlaybackSeek.setProgress(seekBarPosition);
- mPlaybackPosition.setText(formatAsMinutesAndSeconds(seekBarMax - seekBarPosition));
- }
-
- private String getString(int resId) {
- return mApplicationContext.getString(resId);
- }
-
- @Override
- public void setIsBuffering() {
- disableUiElements();
- mPlaybackPosition.setText(getString(R.string.voicemail_buffering));
- }
-
- @Override
- public void setIsFetchingContent() {
- disableUiElements();
- mPlaybackPosition.setText(getString(R.string.voicemail_fetching_content));
- }
-
- @Override
- public void setFetchContentTimeout() {
- disableUiElements();
- mPlaybackPosition.setText(getString(R.string.voicemail_fetching_timout));
- }
-
- @Override
- public int getDesiredClipPosition() {
- return mPlaybackSeek.getProgress();
- }
-
- @Override
- public void disableUiElements() {
- mStartStopButton.setEnabled(false);
- mPlaybackSpeakerphone.setEnabled(false);
- mPlaybackSeek.setProgress(0);
- mPlaybackSeek.setEnabled(false);
- }
-
- @Override
- public void playbackError(Exception e) {
- disableUiElements();
- mPlaybackPosition.setText(getString(R.string.voicemail_playback_error));
- Log.e(TAG, "Could not play voicemail", e);
- }
-
- @Override
- public void enableUiElements() {
- mStartStopButton.setEnabled(true);
- mPlaybackSpeakerphone.setEnabled(true);
- mPlaybackSeek.setEnabled(true);
- }
-
- @Override
- public void sendFetchVoicemailRequest(Uri voicemailUri) {
- Intent intent = new Intent(VoicemailContract.ACTION_FETCH_VOICEMAIL, voicemailUri);
- mApplicationContext.sendBroadcast(intent);
- }
-
- @Override
- public boolean queryHasContent(Uri voicemailUri) {
- ContentResolver contentResolver = mApplicationContext.getContentResolver();
- Cursor cursor = contentResolver.query(
- voicemailUri, HAS_CONTENT_PROJECTION, null, null, null);
- try {
- if (cursor != null && cursor.moveToNext()) {
- return cursor.getInt(cursor.getColumnIndexOrThrow(
- VoicemailContract.Voicemails.HAS_CONTENT)) == 1;
- }
- } finally {
- MoreCloseables.closeQuietly(cursor);
- }
- return false;
- }
-
- private AudioManager getAudioManager() {
- return (AudioManager) mApplicationContext.getSystemService(Context.AUDIO_SERVICE);
- }
-
- @Override
- public boolean isSpeakerPhoneOn() {
- return getAudioManager().isSpeakerphoneOn();
- }
-
- @Override
- public void setSpeakerPhoneOn(boolean on) {
- getAudioManager().setSpeakerphoneOn(on);
- if (on) {
- mPlaybackSpeakerphone.setImageResource(R.drawable.ic_speakerphone_on);
- // Speaker is now on, tapping button will turn it off.
- mPlaybackSpeakerphone.setContentDescription(
- mApplicationContext.getString(R.string.voicemail_speaker_off));
- } else {
- mPlaybackSpeakerphone.setImageResource(R.drawable.ic_speakerphone_off);
- // Speaker is now off, tapping button will turn it on.
- mPlaybackSpeakerphone.setContentDescription(
- mApplicationContext.getString(R.string.voicemail_speaker_on));
- }
- }
-
- @Override
- public void setVolumeControlStream(int streamType) {
- Activity activity = mActivityReference.get();
- if (activity != null) {
- activity.setVolumeControlStream(streamType);
- }
- }
- }
-}
diff --git a/src/com/android/dialer/voicemail/VoicemailPlaybackLayout.java b/src/com/android/dialer/voicemail/VoicemailPlaybackLayout.java
new file mode 100644
index 0000000..0e9ff3b
--- /dev/null
+++ b/src/com/android/dialer/voicemail/VoicemailPlaybackLayout.java
@@ -0,0 +1,350 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.dialer.voicemail;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.content.Context;
+import android.media.MediaPlayer;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.PowerManager;
+import android.provider.VoicemailContract;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.SeekBar;
+import android.widget.SeekBar.OnSeekBarChangeListener;
+import android.widget.TextView;
+
+import com.android.common.io.MoreCloseables;
+import com.android.dialer.R;
+
+import com.google.common.base.Preconditions;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.ScheduledExecutorService;
+
+import javax.annotation.concurrent.GuardedBy;
+import javax.annotation.concurrent.NotThreadSafe;
+import javax.annotation.concurrent.ThreadSafe;
+
+/**
+ * Displays and plays a single voicemail.
+ * <p>
+ * This class is not thread-safe, it is thread-confined. All calls to all public
+ * methods on this class are expected to come from the main ui thread.
+ */
+@NotThreadSafe
+public class VoicemailPlaybackLayout extends LinearLayout
+ implements VoicemailPlaybackPresenter.PlaybackView {
+ private static final String TAG = VoicemailPlaybackLayout.class.getSimpleName();
+
+ /**
+ * Controls the animation of the playback slider.
+ */
+ @ThreadSafe
+ private final class PositionUpdater implements Runnable {
+
+ /** Update rate for the slider, 30fps. */
+ private static final int SLIDER_UPDATE_PERIOD_MILLIS = 1000 / 30;
+
+ private final MediaPlayer mMediaPlayer;
+ private final int mDuration;
+ private final ScheduledExecutorService mExecutorService;
+ private final Object mLock = new Object();
+ @GuardedBy("mLock") private ScheduledFuture<?> mScheduledFuture;
+
+ public PositionUpdater(
+ MediaPlayer mediaPlayer,
+ int duration,
+ ScheduledExecutorService executorService) {
+ mMediaPlayer = mediaPlayer;
+ mDuration = duration;
+ mExecutorService = executorService;
+ }
+
+ @Override
+ public void run() {
+ post(new Runnable() {
+ @Override
+ public void run() {
+ int currentPosition = 0;
+ synchronized (mLock) {
+ if (mScheduledFuture == null) {
+ // This task has been canceled. Just stop now.
+ return;
+ }
+ currentPosition = mMediaPlayer.getCurrentPosition();
+ }
+ setClipPosition(currentPosition, mDuration);
+ }
+ });
+ }
+
+ public void startUpdating() {
+ synchronized (mLock) {
+ if (mScheduledFuture != null) {
+ mScheduledFuture.cancel(false);
+ mScheduledFuture = null;
+ }
+ mScheduledFuture = mExecutorService.scheduleAtFixedRate(
+ this, 0, SLIDER_UPDATE_PERIOD_MILLIS, TimeUnit.MILLISECONDS);
+ }
+ }
+
+ public void stopUpdating() {
+ synchronized (mLock) {
+ if (mScheduledFuture != null) {
+ mScheduledFuture.cancel(false);
+ mScheduledFuture = null;
+ }
+ }
+ }
+ }
+
+ /**
+ * Handle state changes when the user manipulates the seek bar.
+ */
+ private final OnSeekBarChangeListener seekBarChangeListener = new OnSeekBarChangeListener() {
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ if (mPresenter != null) {
+ mPresenter.pausePlaybackForSeeking();
+ }
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ if (mPresenter != null) {
+ mPresenter.resumePlaybackAfterSeeking(seekBar.getProgress());
+ }
+ }
+
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ setClipPosition(seekBar.getProgress(), seekBar.getMax());
+ }
+ };
+
+ /**
+ * Click listener to toggle speakerphone.
+ */
+ private final View.OnClickListener speakerphoneListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (mPresenter != null) {
+ onSpeakerphoneOn(!mPresenter.isSpeakerphoneOn());
+ }
+ }
+ };
+
+ /**
+ * Click listener to play or pause voicemail playback.
+ */
+ private final View.OnClickListener startStopButtonListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ if (mPresenter == null) {
+ return;
+ }
+ if (mIsPlaying) {
+ mPresenter.pausePlayback();
+ } else {
+ mPresenter.resumePlayback();
+ }
+ }
+ };
+
+ private Context mContext;
+ private VoicemailPlaybackPresenter mPresenter;
+
+ private boolean mIsPlaying = false;
+
+ private SeekBar mPlaybackSeek;
+ private ImageButton mStartStopButton;
+ private ImageButton mPlaybackSpeakerphone;
+ private TextView mPlaybackPosition;
+
+ private PositionUpdater mPositionUpdater;
+
+ public VoicemailPlaybackLayout(Context context) {
+ this(context, null);
+ }
+
+ public VoicemailPlaybackLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ mContext = context;
+ LayoutInflater inflater =
+ (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ inflater.inflate(R.layout.playback_layout, this);
+ }
+
+ @Override
+ public void setPresenter(VoicemailPlaybackPresenter presenter) {
+ mPresenter = presenter;
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+
+ mPlaybackSeek = (SeekBar) findViewById(R.id.playback_seek);
+ mStartStopButton = (ImageButton) findViewById(R.id.playback_start_stop);
+ mPlaybackSpeakerphone = (ImageButton) findViewById(R.id.playback_speakerphone);
+ mPlaybackPosition = (TextView) findViewById(R.id.playback_position_text);
+
+ mPlaybackSeek.setOnSeekBarChangeListener(seekBarChangeListener);
+ mStartStopButton.setOnClickListener(startStopButtonListener);
+ mPlaybackSpeakerphone.setOnClickListener(speakerphoneListener);
+ }
+
+ @Override
+ public void onPlaybackStarted(
+ MediaPlayer mediaPlayer,
+ int duration,
+ ScheduledExecutorService executorService) {
+ mIsPlaying = true;
+
+ mStartStopButton.setImageResource(R.drawable.ic_hold_pause);
+
+ if (mPresenter != null) {
+ onSpeakerphoneOn(mPresenter.isSpeakerphoneOn());
+ }
+
+ mPositionUpdater = new PositionUpdater(mediaPlayer, duration, executorService);
+ mPositionUpdater.startUpdating();
+ }
+
+ @Override
+ public void onPlaybackStopped() {
+ mIsPlaying = false;
+
+ mStartStopButton.setImageResource(R.drawable.ic_play);
+
+ if (mPositionUpdater != null) {
+ mPositionUpdater.stopUpdating();
+ mPositionUpdater = null;
+ }
+ }
+
+ @Override
+ public void onPlaybackError(Exception e) {
+ if (mPositionUpdater != null) {
+ mPositionUpdater.stopUpdating();
+ }
+
+ disableUiElements();
+ mPlaybackPosition.setText(getString(R.string.voicemail_playback_error));
+
+ Log.e(TAG, "Could not play voicemail", e);
+ }
+
+
+ public void onSpeakerphoneOn(boolean on) {
+ if (mPresenter != null) {
+ mPresenter.setSpeakerphoneOn(on);
+ }
+
+ if (on) {
+ mPlaybackSpeakerphone.setImageResource(R.drawable.ic_speakerphone_on);
+ // Speaker is now on, tapping button will turn it off.
+ mPlaybackSpeakerphone.setContentDescription(
+ mContext.getString(R.string.voicemail_speaker_off));
+ } else {
+ mPlaybackSpeakerphone.setImageResource(R.drawable.ic_speakerphone_off);
+ // Speaker is now off, tapping button will turn it on.
+ mPlaybackSpeakerphone.setContentDescription(
+ mContext.getString(R.string.voicemail_speaker_on));
+ }
+ }
+
+ @Override
+ public void setClipPosition(int clipPositionInMillis, int clipLengthInMillis) {
+ int seekBarPosition = Math.max(0, clipPositionInMillis);
+ int seekBarMax = Math.max(seekBarPosition, clipLengthInMillis);
+ if (mPlaybackSeek.getMax() != seekBarMax) {
+ mPlaybackSeek.setMax(seekBarMax);
+ }
+ mPlaybackSeek.setProgress(seekBarPosition);
+ mPlaybackPosition.setText(formatAsMinutesAndSeconds(seekBarMax - seekBarPosition));
+ }
+
+ @Override
+ public void setIsBuffering() {
+ disableUiElements();
+ mPlaybackPosition.setText(getString(R.string.voicemail_buffering));
+ }
+
+ @Override
+ public void setIsFetchingContent() {
+ disableUiElements();
+ mPlaybackPosition.setText(getString(R.string.voicemail_fetching_content));
+ }
+
+ @Override
+ public void setFetchContentTimeout() {
+ disableUiElements();
+ mPlaybackPosition.setText(getString(R.string.voicemail_fetching_timout));
+ }
+
+ @Override
+ public int getDesiredClipPosition() {
+ return mPlaybackSeek.getProgress();
+ }
+
+ @Override
+ public void disableUiElements() {
+ mStartStopButton.setEnabled(false);
+ mPlaybackSpeakerphone.setEnabled(false);
+ mPlaybackSeek.setProgress(0);
+ mPlaybackSeek.setEnabled(false);
+ }
+
+ @Override
+ public void enableUiElements() {
+ mStartStopButton.setEnabled(true);
+ mPlaybackSpeakerphone.setEnabled(true);
+ mPlaybackSeek.setEnabled(true);
+ }
+
+ private String getString(int resId) {
+ return mContext.getString(resId);
+ }
+
+ /**
+ * Formats a number of milliseconds as something that looks like {@code 00:05}.
+ * <p>
+ * We always use four digits, two for minutes two for seconds. In the very unlikely event
+ * that the voicemail duration exceeds 99 minutes, the display is capped at 99 minutes.
+ */
+ private String formatAsMinutesAndSeconds(int millis) {
+ int seconds = millis / 1000;
+ int minutes = seconds / 60;
+ seconds -= minutes * 60;
+ if (minutes > 99) {
+ minutes = 99;
+ }
+ return String.format("%02d:%02d", minutes, seconds);
+ }
+}
diff --git a/src/com/android/dialer/voicemail/VoicemailPlaybackPresenter.java b/src/com/android/dialer/voicemail/VoicemailPlaybackPresenter.java
index 029f5bd..e8b0460 100644
--- a/src/com/android/dialer/voicemail/VoicemailPlaybackPresenter.java
+++ b/src/com/android/dialer/voicemail/VoicemailPlaybackPresenter.java
@@ -16,8 +16,12 @@
package com.android.dialer.voicemail;
+import android.app.Activity;
import android.content.Context;
+import android.content.ContentResolver;
+import android.content.Intent;
import android.database.ContentObserver;
+import android.database.Cursor;
import android.media.AudioManager;
import android.media.AudioManager.OnAudioFocusChangeListener;
import android.media.MediaPlayer;
@@ -26,97 +30,85 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.PowerManager;
+import android.provider.VoicemailContract;
+import android.util.Log;
import android.view.View;
import android.widget.SeekBar;
import com.android.dialer.R;
import com.android.dialer.util.AsyncTaskExecutor;
-import com.android.ex.variablespeed.MediaPlayerProxy;
-import com.android.ex.variablespeed.SingleThreadedMediaPlayerProxy;
+import com.android.dialer.util.AsyncTaskExecutors;
+import com.android.common.io.MoreCloseables;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
+import java.io.IOException;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
-import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.NotThreadSafe;
import javax.annotation.concurrent.ThreadSafe;
/**
- * Contains the controlling logic for a voicemail playback ui.
+ * Contains the controlling logic for a voicemail playback UI.
* <p>
- * Specifically right now this class is used to control the
- * {@link com.android.dialer.voicemail.VoicemailPlaybackFragment}.
+ * This controls a single {@link com.android.dialer.voicemail.VoicemailPlaybackLayout}. A single
+ * instance can be reused for different such layouts, using {@link #setVoicemailPlaybackView}.
* <p>
- * This class is not thread safe. The thread policy for this class is
- * thread-confinement, all calls into this class from outside must be done from
- * the main ui thread.
+ * This class is not thread safe. The thread policy for this class is thread-confinement, all calls
+ * into this class from outside must be done from the main UI thread.
*/
@NotThreadSafe
@VisibleForTesting
-public class VoicemailPlaybackPresenter implements OnAudioFocusChangeListener {
- /** The stream used to playback voicemail. */
- private static final int PLAYBACK_STREAM = AudioManager.STREAM_VOICE_CALL;
+public class VoicemailPlaybackPresenter
+ implements OnAudioFocusChangeListener, MediaPlayer.OnPreparedListener,
+ MediaPlayer.OnCompletionListener, MediaPlayer.OnErrorListener {
+
+ private static final String TAG = VoicemailPlaybackPresenter.class.getSimpleName();
/** Contract describing the behaviour we need from the ui we are controlling. */
public interface PlaybackView {
- Context getDataSourceContext();
- void runOnUiThread(Runnable runnable);
- void setStartStopListener(View.OnClickListener listener);
- void setPositionSeekListener(SeekBar.OnSeekBarChangeListener listener);
- void setSpeakerphoneListener(View.OnClickListener listener);
- void setIsBuffering();
- void setClipPosition(int clipPositionInMillis, int clipLengthInMillis);
int getDesiredClipPosition();
- void playbackStarted();
- void playbackStopped();
- void playbackError(Exception e);
- boolean isSpeakerPhoneOn();
- void setSpeakerPhoneOn(boolean on);
- void finish();
- void setIsFetchingContent();
void disableUiElements();
void enableUiElements();
- void sendFetchVoicemailRequest(Uri voicemailUri);
- boolean queryHasContent(Uri voicemailUri);
+ void onPlaybackError(Exception e);
+ void onPlaybackStarted(MediaPlayer mediaPlayer, int duration,
+ ScheduledExecutorService executorService);
+ void onPlaybackStopped();
+ void setClipPosition(int clipPositionInMillis, int clipLengthInMillis);
void setFetchContentTimeout();
- void registerContentObserver(Uri uri, ContentObserver observer);
- void unregisterContentObserver(ContentObserver observer);
- void enableProximitySensor();
- void disableProximitySensor();
- void setVolumeControlStream(int streamType);
+ void setIsBuffering();
+ void setIsFetchingContent();
+ void setPresenter(VoicemailPlaybackPresenter presenter);
}
/** The enumeration of {@link AsyncTask} objects we use in this class. */
public enum Tasks {
CHECK_FOR_CONTENT,
CHECK_CONTENT_AFTER_CHANGE,
- PREPARE_MEDIA_PLAYER,
- RESET_PREPARE_START_MEDIA_PLAYER,
}
- /** Update rate for the slider, 30fps. */
- private static final int SLIDER_UPDATE_PERIOD_MILLIS = 1000 / 30;
- /** Time our ui will wait for content to be fetched before reporting not available. */
+ private static final String[] HAS_CONTENT_PROJECTION = new String[] {
+ VoicemailContract.Voicemails.HAS_CONTENT,
+ };
+
+ private static final int PLAYBACK_STREAM = AudioManager.STREAM_VOICE_CALL;
+ private static final int NUMBER_OF_THREADS_IN_POOL = 2;
+ // Time to wait for content to be fetched before timing out.
private static final long FETCH_CONTENT_TIMEOUT_MS = 20000;
- /**
- * If present in the saved instance bundle, we should not resume playback on
- * create.
- */
- private static final String IS_PLAYING_STATE_KEY = VoicemailPlaybackPresenter.class.getName()
- + ".IS_PLAYING_STATE_KEY";
- /**
- * If present in the saved instance bundle, indicates where to set the
- * playback slider.
- */
- private static final String CLIP_POSITION_KEY = VoicemailPlaybackPresenter.class.getName()
- + ".CLIP_POSITION_KEY";
+
+ // If present in the saved instance bundle, we should not resume playback on create.
+ private static final String IS_PLAYING_STATE_KEY =
+ VoicemailPlaybackPresenter.class.getName() + ".IS_PLAYING_STATE_KEY";
+ // If present in the saved instance bundle, indicates where to set the playback slider.
+ private static final String CLIP_POSITION_KEY =
+ VoicemailPlaybackPresenter.class.getName() + ".CLIP_POSITION_KEY";
/**
* The most recently calculated duration.
@@ -127,45 +119,108 @@
*/
private final AtomicInteger mDuration = new AtomicInteger(0);
- private final PlaybackView mView;
- private final MediaPlayerProxy mPlayer;
- private final PositionUpdater mPositionUpdater;
+ private Context mContext;
+ private MediaPlayer mMediaPlayer;
+ private PlaybackView mView;
- /** Voicemail uri to play. */
- private final Uri mVoicemailUri;
- /** Start playing in onCreate iff this is true. */
- private final boolean mStartPlayingImmediately;
- /** Used to run async tasks that need to interact with the ui. */
+ private Uri mVoicemailUri;
+ private int mPosition;
+ private boolean mIsPrepared;
+ private boolean mIsPlaying;
+ private boolean mShouldResumePlaybackAfterSeeking;
+
+ // Used to run async tasks that need to interact with the UI.
private final AsyncTaskExecutor mAsyncTaskExecutor;
-
+ private static ScheduledExecutorService mScheduledExecutorService;
/**
* Used to handle the result of a successful or time-out fetch result.
* <p>
* This variable is thread-contained, accessed only on the ui thread.
*/
private FetchResultHandler mFetchResultHandler;
- private PowerManager.WakeLock mWakeLock;
- private AsyncTask<Void, ?, ?> mPrepareTask;
- private int mPosition;
- private boolean mPlaying;
+ private PowerManager.WakeLock mProximityWakeLock;
private AudioManager mAudioManager;
- public VoicemailPlaybackPresenter(PlaybackView view, MediaPlayerProxy player,
- Uri voicemailUri, ScheduledExecutorService executorService,
- boolean startPlayingImmediately, AsyncTaskExecutor asyncTaskExecutor,
- PowerManager.WakeLock wakeLock) {
- mView = view;
- mPlayer = player;
- mVoicemailUri = voicemailUri;
- mStartPlayingImmediately = startPlayingImmediately;
- mAsyncTaskExecutor = asyncTaskExecutor;
- mPositionUpdater = new PositionUpdater(executorService, SLIDER_UPDATE_PERIOD_MILLIS);
- mWakeLock = wakeLock;
+ public VoicemailPlaybackPresenter(Activity activity) {
+ mContext = activity;
+ mAsyncTaskExecutor = AsyncTaskExecutors.createAsyncTaskExecutor();
+ mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+
+ PowerManager powerManager =
+ (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+ if (powerManager.isWakeLockLevelSupported(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK)) {
+ mProximityWakeLock = powerManager.newWakeLock(
+ PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, TAG);
+ }
+
+ if (mMediaPlayer == null) {
+ mMediaPlayer = new MediaPlayer();
+ mMediaPlayer.setOnPreparedListener(this);
+ mMediaPlayer.setOnErrorListener(this);
+ mMediaPlayer.setOnCompletionListener(this);
+ }
+
+ activity.setVolumeControlStream(PLAYBACK_STREAM);
}
- public void onCreate(Bundle bundle) {
- mView.setVolumeControlStream(PLAYBACK_STREAM);
- checkThatWeHaveContent();
+ /**
+ * Specify the view which this presenter controls and the voicemail for playback.
+ */
+ public void setPlaybackView(
+ PlaybackView view, Uri voicemailUri, boolean startPlayingImmediately) {
+ mView = view;
+ mVoicemailUri = voicemailUri;
+
+ mView.setPresenter(this);
+
+ if (!mMediaPlayer.isPlaying()) {
+ setPosition(0, startPlayingImmediately);
+ mIsPrepared = false;
+ }
+ checkForContent();
+ }
+
+ public void onPause(boolean isFinishing) {
+ // Do not pause for orientation changes.
+ if (mMediaPlayer.isPlaying() && isFinishing) {
+ pausePlayback(mMediaPlayer.getCurrentPosition(), mIsPlaying);
+ }
+
+ disableProximitySensor(false /* waitForFarState */);
+ }
+
+ public void onDestroy(boolean isFinishing) {
+ // Do not release for orientation changes.
+ if (mIsPrepared && isFinishing) {
+ mMediaPlayer.release();
+ mIsPrepared = false;
+ }
+
+ if (mScheduledExecutorService != null) {
+ mScheduledExecutorService.shutdown();
+ mScheduledExecutorService = null;
+ }
+
+ if (mFetchResultHandler != null) {
+ mFetchResultHandler.destroy();
+ mFetchResultHandler = null;
+ }
+
+ disableProximitySensor(false /* waitForFarState */);
+ }
+
+ public void onSaveInstanceState(Bundle outState) {
+ outState.putInt(CLIP_POSITION_KEY, mView.getDesiredClipPosition());
+ outState.putBoolean(IS_PLAYING_STATE_KEY, mIsPlaying);
+ }
+
+ public void onRestoreInstanceState(Bundle inState) {
+ if (inState != null) {
+ int position = inState.getInt(CLIP_POSITION_KEY, 0);
+ boolean isPlaying = inState.getBoolean(IS_PLAYING_STATE_KEY, false);
+ // Playback will be automatically resumed, if appropriate, in onPrepared().
+ setPosition(position, isPlaying);
+ }
}
/**
@@ -174,30 +229,44 @@
* This method will be called once, after the fragment has been created, before we know if the
* voicemail we've been asked to play has any content available.
* <p>
- * This method will notify the user through the ui that we are fetching the content, then check
- * to see if the content field in the db is set. If set, we proceed to
- * {@link #postSuccessfullyFetchedContent()} method. If not set, we will make a request to fetch
- * the content asynchronously via {@link #makeRequestForContent()}.
+ * Notify the user that we are fetching the content, then check to see if the content field in
+ * the DB is set. If set, we proceed to {@link #prepareToPlayContent()} method. If not set, make
+ * a request to fetch the content asynchronously via {@link #requestContent()}.
*/
- private void checkThatWeHaveContent() {
+ private void checkForContent() {
mView.setIsFetchingContent();
mAsyncTaskExecutor.submit(Tasks.CHECK_FOR_CONTENT, new AsyncTask<Void, Void, Boolean>() {
@Override
public Boolean doInBackground(Void... params) {
- return mView.queryHasContent(mVoicemailUri);
+ return queryHasContent(mVoicemailUri);
}
@Override
public void onPostExecute(Boolean hasContent) {
if (hasContent) {
- postSuccessfullyFetchedContent();
+ prepareToPlayContent();
} else {
- makeRequestForContent();
+ requestContent();
}
}
});
}
+ private boolean queryHasContent(Uri voicemailUri) {
+ ContentResolver contentResolver = mContext.getContentResolver();
+ Cursor cursor = contentResolver.query(
+ voicemailUri, HAS_CONTENT_PROJECTION, null, null, null);
+ try {
+ if (cursor != null && cursor.moveToNext()) {
+ return cursor.getInt(cursor.getColumnIndexOrThrow(
+ VoicemailContract.Voicemails.HAS_CONTENT)) == 1;
+ }
+ } finally {
+ MoreCloseables.closeQuietly(cursor);
+ }
+ return false;
+ }
+
/**
* Makes a broadcast request to ask that a voicemail source fetch this content.
* <p>
@@ -207,17 +276,22 @@
* will trigger a broadcast to request that the content be downloaded. It will add a listener to
* the content resolver so that it will be notified when the has_content field changes. It will
* also set a timer. If the has_content field changes to true within the allowed time, we will
- * proceed to {@link #postSuccessfullyFetchedContent()}. If the has_content field does not
+ * proceed to {@link #prepareToPlayContent()}. If the has_content field does not
* become true within the allowed time, we will update the ui to reflect the fact that content
* was not available.
*/
- private void makeRequestForContent() {
- Handler handler = new Handler();
+ private void requestContent() {
Preconditions.checkState(mFetchResultHandler == null, "mFetchResultHandler should be null");
+
+ Handler handler = new Handler();
mFetchResultHandler = new FetchResultHandler(handler);
- mView.registerContentObserver(mVoicemailUri, mFetchResultHandler);
+ mContext.getContentResolver().registerContentObserver(
+ mVoicemailUri, false, mFetchResultHandler);
handler.postDelayed(mFetchResultHandler.getTimeoutRunnable(), FETCH_CONTENT_TIMEOUT_MS);
- mView.sendFetchVoicemailRequest(mVoicemailUri);
+
+ // Send voicemail fetch request.
+ Intent intent = new Intent(VoicemailContract.ACTION_FETCH_VOICEMAIL, mVoicemailUri);
+ mContext.sendBroadcast(intent);
}
@ThreadSafe
@@ -237,14 +311,14 @@
@Override
public void run() {
if (mResultStillPending.getAndSet(false)) {
- mView.unregisterContentObserver(FetchResultHandler.this);
+ mContext.getContentResolver().unregisterContentObserver(FetchResultHandler.this);
mView.setFetchContentTimeout();
}
}
public void destroy() {
if (mResultStillPending.getAndSet(false)) {
- mView.unregisterContentObserver(FetchResultHandler.this);
+ mContext.getContentResolver().unregisterContentObserver(FetchResultHandler.this);
mHandler.removeCallbacks(this);
}
}
@@ -255,15 +329,16 @@
new AsyncTask<Void, Void, Boolean>() {
@Override
public Boolean doInBackground(Void... params) {
- return mView.queryHasContent(mVoicemailUri);
+ return queryHasContent(mVoicemailUri);
}
@Override
public void onPostExecute(Boolean hasContent) {
if (hasContent) {
if (mResultStillPending.getAndSet(false)) {
- mView.unregisterContentObserver(FetchResultHandler.this);
- postSuccessfullyFetchedContent();
+ mContext.getContentResolver().unregisterContentObserver(
+ FetchResultHandler.this);
+ prepareToPlayContent();
}
}
}
@@ -275,378 +350,205 @@
* Prepares the voicemail content for playback.
* <p>
* This method will be called once we know that our voicemail has content (according to the
- * content provider). This method will try to prepare the data source through the media player.
- * If preparing the media player works, we will call through to
- * {@link #postSuccessfulPrepareActions()}. If preparing the media player fails (perhaps the
- * file the content provider points to is actually missing, perhaps it is of an unknown file
- * format that we can't play, who knows) then we will show an error on the ui.
+ * content provider). this method asynchronously tries to prepare the data source through the
+ * media player. If preparation is successful, the media player will {@link #onPrepared()},
+ * and it will call {@link #onError()} otherwise.
*/
- private void postSuccessfullyFetchedContent() {
+ private void prepareToPlayContent() {
mView.setIsBuffering();
- mAsyncTaskExecutor.submit(Tasks.PREPARE_MEDIA_PLAYER,
- new AsyncTask<Void, Void, Exception>() {
- @Override
- public Exception doInBackground(Void... params) {
- try {
- mPlayer.reset();
- mPlayer.setDataSource(mView.getDataSourceContext(), mVoicemailUri);
- mPlayer.setAudioStreamType(PLAYBACK_STREAM);
- mPlayer.prepare();
- mDuration.set(mPlayer.getDuration());
- return null;
- } catch (Exception e) {
- return e;
- }
- }
- @Override
- public void onPostExecute(Exception exception) {
- if (exception == null) {
- postSuccessfulPrepareActions();
- } else {
- mView.playbackError(exception);
- }
- }
- });
+ try {
+ mMediaPlayer.reset();
+ mMediaPlayer.setDataSource(mContext, mVoicemailUri);
+ mMediaPlayer.setAudioStreamType(PLAYBACK_STREAM);
+ mMediaPlayer.prepareAsync();
+ } catch (IOException e) {
+ handleError(e);
+ }
}
/**
- * Enables the ui, and optionally starts playback immediately.
- * <p>
- * This will be called once we have successfully prepared the media player, and will optionally
- * playback immediately.
+ * Once the media player is prepared, enables the UI and adopts the appropriate playback state.
*/
- private void postSuccessfulPrepareActions() {
+ @Override
+ public void onPrepared(MediaPlayer mp) {
+ mIsPrepared = true;
+
mView.enableUiElements();
- mView.setPositionSeekListener(new PlaybackPositionListener());
- mView.setStartStopListener(new StartStopButtonListener());
- mView.setSpeakerphoneListener(new SpeakerphoneListener());
- mPlayer.setOnErrorListener(new MediaPlayerErrorListener());
- mPlayer.setOnCompletionListener(new MediaPlayerCompletionListener());
- mView.setSpeakerPhoneOn(mView.isSpeakerPhoneOn());
- if (mPlaying) {
- resetPrepareStartPlaying(mPosition);
+
+ if (mIsPlaying) {
+ resumePlayback();
} else {
- stopPlaybackAtPosition(mPosition, mDuration.get());
- if ((mPosition == 0) && (mStartPlayingImmediately)) {
- resetPrepareStartPlaying(0);
- }
+ pausePlayback();
}
}
- public void onSaveInstanceState(Bundle outState) {
- outState.putInt(CLIP_POSITION_KEY, mView.getDesiredClipPosition());
- outState.putBoolean(IS_PLAYING_STATE_KEY, mPlaying);
+ /**
+ * Invoked if preparing the media player fails, for example, if file is missing or the voicemail
+ * is an unknown file format that can't be played.
+ */
+ @Override
+ public boolean onError(MediaPlayer mp, int what, int extra) {
+ handleError(new IllegalStateException("MediaPlayer error listener invoked"));
+ return true;
}
- public void onRestoreInstanceState(Bundle inState) {
- int position = 0;
- boolean isPlaying = false;
- if (inState != null) {
- position = inState.getInt(CLIP_POSITION_KEY, 0);
- isPlaying = inState.getBoolean(IS_PLAYING_STATE_KEY, false);
+ private void handleError(Exception e) {
+ if (mIsPrepared) {
+ mMediaPlayer.release();
+ mIsPrepared = false;
}
- setPositionAndPlayingStatus(position, isPlaying) ;
+
+ mView.onPlaybackError(e);
+ setPosition(0, false);
}
- private void setPositionAndPlayingStatus(int position, boolean isPlaying) {
- mPosition = position;
- mPlaying = isPlaying;
- }
-
- public void onDestroy() {
- if (mPrepareTask != null) {
- mPrepareTask.cancel(false);
- mPrepareTask = null;
- }
- mPlayer.release();
- if (mFetchResultHandler != null) {
- mFetchResultHandler.destroy();
- mFetchResultHandler = null;
- }
- mPositionUpdater.stopUpdating();
- if (mWakeLock.isHeld()) {
- mWakeLock.release();
- }
- }
-
- private class MediaPlayerErrorListener implements MediaPlayer.OnErrorListener {
- @Override
- public boolean onError(MediaPlayer mp, int what, int extra) {
- mView.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- handleError(new IllegalStateException("MediaPlayer error listener invoked"));
- }
- });
- return true;
- }
- }
-
- private class MediaPlayerCompletionListener implements MediaPlayer.OnCompletionListener {
- @Override
- public void onCompletion(final MediaPlayer mp) {
- mView.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- handleCompletion(mp);
- }
- });
- }
- }
-
- private class AsyncPrepareTask extends AsyncTask<Void, Void, Exception> {
- private int mClipPositionInMillis;
-
- AsyncPrepareTask(int clipPositionInMillis) {
- mClipPositionInMillis = clipPositionInMillis;
- }
-
- @Override
- public Exception doInBackground(Void... params) {
- try {
- mPlayer.reset();
- mPlayer.setDataSource(mView.getDataSourceContext(), mVoicemailUri);
- mPlayer.setAudioStreamType(PLAYBACK_STREAM);
- mPlayer.prepare();
- return null;
- } catch (Exception e) {
- return e;
- }
- }
-
- @Override
- public void onPostExecute(Exception exception) {
- mPrepareTask = null;
- if (exception == null) {
- final int duration = mPlayer.getDuration();
- mDuration.set(duration);
- int startPosition =
- constrain(mClipPositionInMillis, 0, duration);
- mPlayer.seekTo(startPosition);
- mView.setClipPosition(startPosition, duration);
- try {
- // Grab audio focus here
- int result = getAudioManager().requestAudioFocus(
- VoicemailPlaybackPresenter.this,
- PLAYBACK_STREAM,
- AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
-
- if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
- throw new RejectedExecutionException("Could not capture audio focus.");
- }
- // Can throw RejectedExecutionException
- mPlayer.start();
- setPositionAndPlayingStatus(mPlayer.getCurrentPosition(), true);
- mView.playbackStarted();
- if (!mWakeLock.isHeld()) {
- mWakeLock.acquire();
- }
- // Only enable if we are not currently using the speaker phone.
- if (!mView.isSpeakerPhoneOn()) {
- mView.enableProximitySensor();
- }
- // Can throw RejectedExecutionException
- mPositionUpdater.startUpdating(startPosition, duration);
- } catch (RejectedExecutionException e) {
- handleError(e);
- }
- } else {
- handleError(exception);
- }
- }
- }
-
- private AudioManager getAudioManager() {
- if (mAudioManager == null) {
- mAudioManager = (AudioManager)
- mView.getDataSourceContext().getSystemService(Context.AUDIO_SERVICE);
- }
- return mAudioManager;
+ /**
+ * After done playing the voicemail clip, reset the clip position to the start.
+ */
+ @Override
+ public void onCompletion(MediaPlayer mediaPlayer) {
+ pausePlayback(0, false);
}
@Override
public void onAudioFocusChange(int focusChange) {
boolean lostFocus = focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT ||
focusChange == AudioManager.AUDIOFOCUS_LOSS;
- // Note: the below logic is the same as in {@code StartStopButtonListener}.
- if (mPlayer.isPlaying() && lostFocus) {
- setPositionAndPlayingStatus(mPlayer.getCurrentPosition(), false);
- stopPlaybackAtPosition(mPlayer.getCurrentPosition(), mDuration.get());
- } else if (!mPlayer.isPlaying() && focusChange == AudioManager.AUDIOFOCUS_GAIN) {
- setPositionAndPlayingStatus(mPosition, true);
- postSuccessfullyFetchedContent();
- }
- }
-
-
- private void resetPrepareStartPlaying(final int clipPositionInMillis) {
- if (mPrepareTask != null) {
- mPrepareTask.cancel(false);
- mPrepareTask = null;
- }
- mPrepareTask = mAsyncTaskExecutor.submit(Tasks.RESET_PREPARE_START_MEDIA_PLAYER,
- new AsyncPrepareTask(clipPositionInMillis));
- }
-
- private void handleError(Exception e) {
- mView.playbackError(e);
- mPositionUpdater.stopUpdating();
- mPlayer.release();
- setPositionAndPlayingStatus(0, false);
- }
-
- public void handleCompletion(MediaPlayer mediaPlayer) {
- stopPlaybackAtPosition(0, mDuration.get());
- }
-
- private void stopPlaybackAtPosition(int clipPosition, int duration) {
- getAudioManager().abandonAudioFocus(this);
- mPositionUpdater.stopUpdating();
- mView.playbackStopped();
- if (mWakeLock.isHeld()) {
- mWakeLock.release();
- }
- // Always disable on stop.
- mView.disableProximitySensor();
- mView.setClipPosition(clipPosition, duration);
- if (mPlayer.isPlaying()) {
- mPlayer.pause();
- }
- }
-
- private class PlaybackPositionListener implements SeekBar.OnSeekBarChangeListener {
- private boolean mShouldResumePlaybackAfterSeeking;
-
- @Override
- public void onStartTrackingTouch(SeekBar arg0) {
- if (mPlayer.isPlaying()) {
- mShouldResumePlaybackAfterSeeking = true;
- stopPlaybackAtPosition(mPlayer.getCurrentPosition(), mDuration.get());
- } else {
- mShouldResumePlaybackAfterSeeking = false;
- }
- }
-
- @Override
- public void onStopTrackingTouch(SeekBar arg0) {
- if (mPlayer.isPlaying()) {
- setPositionAndPlayingStatus(mPlayer.getCurrentPosition(), false);
- stopPlaybackAtPosition(mPlayer.getCurrentPosition(), mDuration.get());
- } else {
- setPositionAndPlayingStatus(mView.getDesiredClipPosition(),
- mShouldResumePlaybackAfterSeeking);
- }
-
- if (mShouldResumePlaybackAfterSeeking) {
- postSuccessfullyFetchedContent();
- }
- }
-
- @Override
- public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
- mView.setClipPosition(seekBar.getProgress(), seekBar.getMax());
- }
- }
-
- private class SpeakerphoneListener implements View.OnClickListener {
- @Override
- public void onClick(View v) {
- boolean previousState = mView.isSpeakerPhoneOn();
- mView.setSpeakerPhoneOn(!previousState);
- if (mPlayer.isPlaying() && previousState) {
- // If we are currently playing and we are disabling the speaker phone, enable the
- // sensor.
- mView.enableProximitySensor();
- } else {
- // If we are not currently playing, disable the sensor.
- mView.disableProximitySensor();
- }
- }
- }
-
- private class StartStopButtonListener implements View.OnClickListener {
- @Override
- public void onClick(View arg0) {
- if (mPlayer.isPlaying()) {
- setPositionAndPlayingStatus(mPlayer.getCurrentPosition(), false);
- stopPlaybackAtPosition(mPlayer.getCurrentPosition(), mDuration.get());
- } else {
- setPositionAndPlayingStatus(mPosition, true);
- postSuccessfullyFetchedContent();
- }
+ if (mMediaPlayer.isPlaying() && lostFocus) {
+ pausePlayback();
+ } else if (!mMediaPlayer.isPlaying() && focusChange == AudioManager.AUDIOFOCUS_GAIN) {
+ resumePlayback();
}
}
/**
- * Controls the animation of the playback slider.
+ * Sets the position and playing state for when playback is resumed.
*/
- @ThreadSafe
- private final class PositionUpdater implements Runnable {
- private final ScheduledExecutorService mExecutorService;
- private final int mPeriodMillis;
- private final Object mLock = new Object();
- @GuardedBy("mLock") private ScheduledFuture<?> mScheduledFuture;
- private final Runnable mSetClipPostitionRunnable = new Runnable() {
- @Override
- public void run() {
- int currentPosition = 0;
- synchronized (mLock) {
- if (mScheduledFuture == null) {
- // This task has been canceled. Just stop now.
- return;
- }
- currentPosition = mPlayer.getCurrentPosition();
- }
- mView.setClipPosition(currentPosition, mDuration.get());
+ private void setPosition(int position, boolean isPlaying) {
+ mPosition = position;
+ mIsPlaying = isPlaying;
+ }
+
+ /**
+ * Resumes voicemail playback at the clip position stored by the presenter.
+ */
+ public void resumePlayback() {
+ final int duration = mMediaPlayer.getDuration();
+ mDuration.set(duration);
+
+ // Clamp the start position between 0 and the duration.
+ int startPosition = Math.max(0, Math.min(mPosition, duration));
+ mMediaPlayer.seekTo(startPosition);
+ setPosition(startPosition, true);
+
+ try {
+ // Grab audio focus here
+ int result = mAudioManager.requestAudioFocus(
+ VoicemailPlaybackPresenter.this,
+ PLAYBACK_STREAM,
+ AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
+
+ if (result != AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
+ throw new RejectedExecutionException("Could not capture audio focus.");
}
- };
- public PositionUpdater(ScheduledExecutorService executorService, int periodMillis) {
- mExecutorService = executorService;
- mPeriodMillis = periodMillis;
- }
+ // Can throw RejectedExecutionException
+ mMediaPlayer.start();
- @Override
- public void run() {
- mView.runOnUiThread(mSetClipPostitionRunnable);
- }
-
- public void startUpdating(int beginPosition, int endPosition) {
- synchronized (mLock) {
- if (mScheduledFuture != null) {
- mScheduledFuture.cancel(false);
- mScheduledFuture = null;
- }
- mScheduledFuture = mExecutorService.scheduleAtFixedRate(this, 0, mPeriodMillis,
- TimeUnit.MILLISECONDS);
- }
- }
-
- public void stopUpdating() {
- synchronized (mLock) {
- if (mScheduledFuture != null) {
- mScheduledFuture.cancel(false);
- mScheduledFuture = null;
- }
- }
+ mView.onPlaybackStarted(mMediaPlayer, duration, getScheduledExecutorServiceInstance());
+ enableProximitySensor();
+ } catch (RejectedExecutionException e) {
+ handleError(e);
}
}
- public void onPause() {
- if (mPlayer.isPlaying()) {
- stopPlaybackAtPosition(mPlayer.getCurrentPosition(), mDuration.get());
+ public void pausePlayback() {
+ pausePlayback(mMediaPlayer.getCurrentPosition(), false);
+ }
+
+ /**
+ * {@link isPlaying} may be set to {@code true} so voicemail playback can be resumed after a
+ * rotation.
+ */
+ private void pausePlayback(int position, boolean isPlaying) {
+ setPosition(position, isPlaying);
+
+ if (mMediaPlayer.isPlaying()) {
+ mMediaPlayer.pause();
}
- if (mPrepareTask != null) {
- mPrepareTask.cancel(false);
- mPrepareTask = null;
+
+ mAudioManager.abandonAudioFocus(this);
+ mView.onPlaybackStopped();
+
+ // Always disable the proximity sensor on stop.
+ disableProximitySensor(true /* waitForFarState */);
+
+ int duration = mDuration.get();
+ mView.setClipPosition(position, duration);
+ }
+
+ /**
+ * Pauses playback when the user starts seeking the position, and notes whether the voicemail is
+ * playing to know whether to resume playback once the user selects a new position.
+ */
+ public void pausePlaybackForSeeking() {
+ mShouldResumePlaybackAfterSeeking = mMediaPlayer.isPlaying();
+ pausePlayback();
+ }
+
+ public void resumePlaybackAfterSeeking(int desiredPosition) {
+ setPosition(desiredPosition, mShouldResumePlaybackAfterSeeking);
+ if (mShouldResumePlaybackAfterSeeking) {
+ resumePlayback();
}
- if (mWakeLock.isHeld()) {
- mWakeLock.release();
+ mShouldResumePlaybackAfterSeeking = false;
+ }
+
+ private void enableProximitySensor() {
+ if (mProximityWakeLock == null || isSpeakerphoneOn() || !mMediaPlayer.isPlaying()) {
+ return;
+ }
+
+ if (!mProximityWakeLock.isHeld()) {
+ Log.i(TAG, "Acquiring proximity wake lock");
+ mProximityWakeLock.acquire();
+ } else {
+ Log.i(TAG, "Proximity wake lock already acquired");
}
}
- private static int constrain(int amount, int low, int high) {
- return amount < low ? low : (amount > high ? high : amount);
+ private void disableProximitySensor(boolean waitForFarState) {
+ if (mProximityWakeLock == null) {
+ return;
+ }
+ if (mProximityWakeLock.isHeld()) {
+ Log.i(TAG, "Releasing proximity wake lock");
+ int flags = waitForFarState ? PowerManager.RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY : 0;
+ mProximityWakeLock.release(flags);
+ } else {
+ Log.i(TAG, "Proximity wake lock already released");
+ }
}
+
+ public void setSpeakerphoneOn(boolean on) {
+ mAudioManager.setSpeakerphoneOn(on);
+ if (on) {
+ disableProximitySensor(false /* waitForFarState */);
+ } else {
+ enableProximitySensor();
+ }
+ }
+
+ public boolean isSpeakerphoneOn() {
+ return mAudioManager.isSpeakerphoneOn();
+ }
+
+ private static synchronized ScheduledExecutorService getScheduledExecutorServiceInstance() {
+ if (mScheduledExecutorService == null) {
+ mScheduledExecutorService = Executors.newScheduledThreadPool(NUMBER_OF_THREADS_IN_POOL);
+ }
+ return mScheduledExecutorService;
+ }
+
}
diff --git a/tests/assets/README.txt b/tests/assets/README.txt
new file mode 100644
index 0000000..6cea058
--- /dev/null
+++ b/tests/assets/README.txt
@@ -0,0 +1,3 @@
+quick_test_recording.mp3 is copyright 2011 by Hugo Hudson and is licensed under a
+Creative Commons Attribution 3.0 Unported License:
+ http://creativecommons.org/licenses/by/3.0/
diff --git a/tests/assets/quick_test_recording.mp3 b/tests/assets/quick_test_recording.mp3
new file mode 100644
index 0000000..ad7cb9c
--- /dev/null
+++ b/tests/assets/quick_test_recording.mp3
Binary files differ
diff --git a/tests/src/com/android/dialer/CallDetailActivityTest.java b/tests/src/com/android/dialer/CallDetailActivityTest.java
index aca8f29..97b1b09 100644
--- a/tests/src/com/android/dialer/CallDetailActivityTest.java
+++ b/tests/src/com/android/dialer/CallDetailActivityTest.java
@@ -18,7 +18,6 @@
import static com.android.dialer.calllog.CallLogAsyncTaskUtil.Tasks.GET_CALL_DETAILS;
import static com.android.dialer.voicemail.VoicemailPlaybackPresenter.Tasks.CHECK_FOR_CONTENT;
-import static com.android.dialer.voicemail.VoicemailPlaybackPresenter.Tasks.PREPARE_MEDIA_PLAYER;
import android.content.ContentResolver;
import android.content.ContentUris;
@@ -34,12 +33,12 @@
import android.view.Menu;
import android.widget.TextView;
+import com.android.dialer.calllog.CallLogAsyncTaskUtil;
import com.android.dialer.util.AsyncTaskExecutors;
import com.android.dialer.util.FakeAsyncTaskExecutor;
import com.android.contacts.common.test.IntegrationTestUtils;
import com.android.dialer.util.LocaleTestUtils;
import com.android.internal.view.menu.ContextMenuBuilder;
-import com.google.common.io.Closeables;
import java.io.IOException;
import java.io.InputStream;
@@ -91,37 +90,37 @@
cleanUpUri();
mTestUtils = null;
AsyncTaskExecutors.setFactoryForTest(null);
+ CallLogAsyncTaskUtil.resetForTest();
super.tearDown();
}
public void testInitialActivityStartsWithFetchingVoicemail() throws Throwable {
- setActivityIntentForTestVoicemailEntry();
+ setActivityIntentForRealFileVoicemailEntry();
startActivityUnderTest();
- // When the activity first starts, we will show "Fetching voicemail" on the screen.
+ // When the activity first starts, we will show "Loading voicemail" on the screen.
// The duration should not be visible.
- assertHasOneTextViewContaining("Fetching voicemail");
+ assertHasOneTextViewContaining("Loading voicemail");
assertZeroTextViewsContaining("00:00");
}
- public void testWhenCheckForContentCompletes_UiShowsBuffering() throws Throwable {
- setActivityIntentForTestVoicemailEntry();
+ public void testWhenCheckForContentCompletes() throws Throwable {
+ setActivityIntentForRealFileVoicemailEntry();
startActivityUnderTest();
// There is a background check that is testing to see if we have the content available.
- // Once that task completes, we shouldn't be showing the fetching message, we should
- // be showing "Buffering".
+ // Once that task completes, we shouldn't be showing the fetching message.
mFakeAsyncTaskExecutor.runTask(CHECK_FOR_CONTENT);
- assertHasOneTextViewContaining("Buffering");
- assertZeroTextViewsContaining("Fetching voicemail");
+
+ // The voicemail async call may or may not return before we check the asserts.
+ assertHasOneTextViewContaining("Buffering", "00:00");
+ assertZeroTextViewsContaining("Loading voicemail");
}
public void testInvalidVoicemailShowsErrorMessage() throws Throwable {
setActivityIntentForTestVoicemailEntry();
startActivityUnderTest();
mFakeAsyncTaskExecutor.runTask(CHECK_FOR_CONTENT);
- // There should be exactly one background task ready to prepare the media player.
- // Preparing the media player will have thrown an IOException since the file doesn't exist.
+ // The media player will have thrown an IOException since the file doesn't exist.
// This should have put a failed to play message on screen, buffering is gone.
- mFakeAsyncTaskExecutor.runTask(PREPARE_MEDIA_PLAYER);
assertHasOneTextViewContaining("Couldn't play voicemail");
assertZeroTextViewsContaining("Buffering");
}
@@ -129,7 +128,7 @@
public void testOnResumeDoesNotCreateManyFragments() throws Throwable {
// There was a bug where every time the activity was resumed, a new fragment was created.
// Before the fix, this was failing reproducibly with at least 3 "Buffering" views.
- setActivityIntentForTestVoicemailEntry();
+ setActivityIntentForRealFileVoicemailEntry();
startActivityUnderTest();
mFakeAsyncTaskExecutor.runTask(CHECK_FOR_CONTENT);
getInstrumentation().runOnMainSync(new Runnable() {
@@ -141,7 +140,7 @@
getInstrumentation().callActivityOnResume(mActivityUnderTest);
}
});
- assertHasOneTextViewContaining("Buffering");
+ assertHasOneTextViewContaining("Buffering", "00:00");
}
/**
@@ -192,7 +191,6 @@
setActivityIntentForRealFileVoicemailEntry();
startActivityUnderTest();
mFakeAsyncTaskExecutor.runTask(CHECK_FOR_CONTENT);
- mFakeAsyncTaskExecutor.runTask(PREPARE_MEDIA_PLAYER);
mTestUtils.clickButton(mActivityUnderTest, R.id.playback_speakerphone);
mTestUtils.clickButton(mActivityUnderTest, R.id.playback_start_stop);
Thread.sleep(2000);
@@ -219,6 +217,7 @@
values.put(VoicemailContract.Voicemails.HAS_CONTENT, 1);
values.put(VoicemailContract.Voicemails._DATA, VOICEMAIL_FILE_LOCATION);
mVoicemailUri = contentResolver.insert(VoicemailContract.Voicemails.CONTENT_URI, values);
+
Uri callLogUri = ContentUris.withAppendedId(CallLog.Calls.CONTENT_URI_WITH_VOICEMAIL,
ContentUris.parseId(mVoicemailUri));
Intent intent = new Intent(Intent.ACTION_VIEW, callLogUri);
@@ -237,15 +236,9 @@
mVoicemailUri = getContentResolver().insert(
VoicemailContract.Voicemails.buildSourceUri(packageName), values);
AssetManager assets = getAssets();
- OutputStream outputStream = null;
- InputStream inputStream = null;
- try {
- inputStream = assets.open(TEST_ASSET_NAME);
- outputStream = getContentResolver().openOutputStream(mVoicemailUri);
+ try (InputStream inputStream = assets.open(TEST_ASSET_NAME);
+ OutputStream outputStream = getContentResolver().openOutputStream(mVoicemailUri)) {
copyBetweenStreams(inputStream, outputStream);
- } finally {
- Closeables.closeQuietly(outputStream);
- Closeables.closeQuietly(inputStream);
}
Uri callLogUri = ContentUris.withAppendedId(CallLog.Calls.CONTENT_URI_WITH_VOICEMAIL,
ContentUris.parseId(mVoicemailUri));
@@ -257,9 +250,7 @@
public void copyBetweenStreams(InputStream in, OutputStream out) throws IOException {
byte[] buffer = new byte[1024];
int bytesRead;
- int total = 0;
- while ((bytesRead = in.read(buffer)) != -1) {
- total += bytesRead;
+ while ((bytesRead = in.read(buffer)) > 0) {
out.write(buffer, 0, bytesRead);
}
}
@@ -289,6 +280,14 @@
return views.get(0);
}
+ private void assertHasOneTextViewContaining(String text1, String text2) throws Throwable {
+ assertNotNull(mActivityUnderTest);
+ List<TextView> view1s = mTestUtils.getTextViewsWithString(mActivityUnderTest, text1);
+ List<TextView> view2s = mTestUtils.getTextViewsWithString(mActivityUnderTest, text2);
+ assertEquals("There should have been one TextView with text '" + text1 + "' or text '"
+ + text2 + "' but found " + view1s + view2s, 1, view1s.size() + view2s.size());
+ }
+
private void assertZeroTextViewsContaining(String text) throws Throwable {
assertNotNull(mActivityUnderTest);
List<TextView> views = mTestUtils.getTextViewsWithString(mActivityUnderTest, text);
@@ -304,6 +303,7 @@
// This is because it seems that we can have onResume, onPause, onResume during the course
// of a single unit test.
mFakeAsyncTaskExecutor.runAllTasks(GET_CALL_DETAILS);
+ CallLogAsyncTaskUtil.resetForTest();
}
private AssetManager getAssets() {