Merge "Import translations. DO NOT MERGE" into mnc-dr2-dev am: 01fd2ff631 -s ours
am: 43ef2dd855 -s ours
* commit '43ef2dd855976c621fb8e88e4c9c5483cffbb5f3':
Import translations. DO NOT MERGE
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 56a594b..5b0f624 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -20,6 +20,10 @@
coreApp="true"
android:sharedUserId="android.uid.system">
+ <uses-sdk
+ android:minSdkVersion="23"
+ android:targetSdkVersion="23" />
+
<protected-broadcast android:name="android.intent.action.SHOW_MISSED_CALLS_NOTIFICATION" />
@@ -72,7 +76,9 @@
android:allowBackup="false"
android:supportsRtl="true"
android:process="system"
- android:usesCleartextTraffic="false">
+ android:usesCleartextTraffic="false"
+ android:forceDeviceEncrypted="true"
+ android:encryptionAware="true">
<!-- CALL vs CALL_PRIVILEGED vs CALL_EMERGENCY
We have three different intents through which a call can be initiated each with its
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index b9aa6b9..0e0b5b0 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -16,7 +16,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"Foon"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"Foonoproepbestuur"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"Bel"</string>
<string name="unknown" msgid="6878797917991465859">"Onbekend"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Gemiste oproep"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Gemiste oproepe"</string>
@@ -35,11 +36,17 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Vinnige antwoord"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Boodskap gestuur na <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"Net noodoproepe word deur die toesteleienaar toegelaat"</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"Oproeprekeninge"</string>
+ <!-- no translation found for outgoing_call_not_allowed_user_restriction (6872406278300131364) -->
+ <skip />
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Hierdie program kan nie uitgaande oproepe maak sonder die foon se toestemming nie."</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"Voer \'n geldige nommer in om \'n oproep te maak."</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"Oproep kan nie op die oomblik bygevoeg word nie."</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"Deaktiveer TTY-modus om video-oproepe te maak."</string>
<string name="no_vm_number" msgid="4164780423805688336">"Vermiste stemboodskapnommer"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"Geen stemboodskapnommer is op die SIM-kaart gestoor nie."</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"Voeg nommer by"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"Verander verstekbellerprogram?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"Gebruik <xliff:g id="NEW_APP">%1$s</xliff:g> eerder as <xliff:g id="CURRENT_APP">%2$s</xliff:g> as jou verstekbellerprogram?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"Gebruik <xliff:g id="NEW_APP">%s</xliff:g> as jou verstekbellerprogram?"</string>
</resources>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index 758b795..853729d 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -16,7 +16,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"ስልክ"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"የስልክ ጥሪ አስተዳደር"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"ስልክ"</string>
<string name="unknown" msgid="6878797917991465859">"ያልታወቀ"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"ያመለጠጥሪ"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"ያመለጡ ጥሪዎች"</string>
@@ -35,11 +36,17 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"ፈጣን ምላሽ"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"ለ <xliff:g id="PHONE_NUMBER">%s</xliff:g> የተላከ መልዕክት"</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"የአስቸኳይ አደጋ ጥሪዎች ብቻ ናቸው በባለቤቱ የተፈቀዱት"</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"የመደወያ መለያዎች"</string>
+ <!-- no translation found for outgoing_call_not_allowed_user_restriction (6872406278300131364) -->
+ <skip />
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"ይህ መተግበሪያ ያለስልኩ ፈቃድ ወጪ ጥሪዎችን ማድረግ አይችልም።"</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"አንድ ጥሪ ለማድረግ የሚሰራ ቁጥር ያስገቡ።"</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"ጥሪ በዚህ ጊዜ ላይ ሊታከል አይችልም።"</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"የቪዲዮ ጥሪዎችን ለማድረግ እባክዎ የTTY ሁነታን ያሰናክሉ።"</string>
<string name="no_vm_number" msgid="4164780423805688336">"የድምፅመልዕክት ቁጥርአመለጠ"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"በSIM ካርዱ ላይምንም የድምፅመልዕክት ቁጥር አልከተቀመጠም።"</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"ቁጥር አክል"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"ነባሪ መደወያ መተግበሪያ ይለወጥ?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"<xliff:g id="NEW_APP">%1$s</xliff:g>ን በ<xliff:g id="CURRENT_APP">%2$s</xliff:g> ምትክ እንደ የእርስዎ ነባሪ መደወያ መተግበሪያ ይጠቀም?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"<xliff:g id="NEW_APP">%s</xliff:g>ን እንደ የእርስዎ ነባሪ መደወያ መተግበሪያ ይጠቀም?"</string>
</resources>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index 3d31dc6..ae8b985 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -16,7 +16,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"الهاتف"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"إدارة المكالمات الهاتفية"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"الهاتف"</string>
<string name="unknown" msgid="6878797917991465859">"غير معروف"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"مكالمة فائتة"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"المكالمات الفائتة"</string>
@@ -35,11 +36,16 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"رد سريع"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"تم إرسال الرسالة إلى <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"مسموح لمالك الجهاز بمكالمات الطوارئ فقط"</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"حسابات الاتصال"</string>
+ <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"مسموح بمكالمات الطوارئ فقط."</string>
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"يتعذر على هذا التطبيق إجراء مكالمات صادرة بدون إذن من الهاتف."</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"لإجراء مكالمة، أدخل رقمًا صالحًا."</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"لا يمكن إضافة مكالمة في الوقت الحالي."</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"يُرجى تعطيل وضع TTY لإجراء مكالمات فيديو."</string>
<string name="no_vm_number" msgid="4164780423805688336">"رقم البريد الصوتي مفقود"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"لم يتم تخزين رقم بريد صوتي على شريحة SIM."</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"إضافة رقم"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"هل تريد تغيير التطبيق الافتراضي لبرنامج الاتصال؟"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"هل تريد استخدام <xliff:g id="NEW_APP">%1$s</xliff:g> بدلاً من <xliff:g id="CURRENT_APP">%2$s</xliff:g> تطبيقًا افتراضيًا لبرنامج الاتصال؟"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"هل تريد استخدام <xliff:g id="NEW_APP">%s</xliff:g> تطبيقًا افتراضيًا لبرنامج الاتصال؟"</string>
</resources>
diff --git a/res/values-az-rAZ/strings.xml b/res/values-az-rAZ/strings.xml
new file mode 100644
index 0000000..2082e05
--- /dev/null
+++ b/res/values-az-rAZ/strings.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2013 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"Telefon Zənglərin İdarə Olunması"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"Telefon"</string>
+ <string name="unknown" msgid="6878797917991465859">"Naməlum"</string>
+ <string name="notification_missedCallTitle" msgid="7554385905572364535">"Buraxılmış zəng"</string>
+ <string name="notification_missedCallsTitle" msgid="1361677948941502522">"Buraxılmış zənglər"</string>
+ <string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> buraxılmış zənglər"</string>
+ <string name="notification_missedCallTicker" msgid="504686252427747209">"<xliff:g id="MISSED_CALL_FROM">%s</xliff:g> tərəfindən zəng buraxılıb"</string>
+ <string name="notification_missedCall_call_back" msgid="2684890353590890187">"Geriyə zəng"</string>
+ <string name="notification_missedCall_message" msgid="3049928912736917988">"Mesaj"</string>
+ <string name="accessibility_call_muted" msgid="2776111226185342220">"Səssiz zəng edin."</string>
+ <string name="accessibility_speakerphone_enabled" msgid="1988512040421036359">"Spikerfon aktivdir."</string>
+ <string name="respond_via_sms_canned_response_1" msgid="2461606462788380215">"İndi danışmaq olmur. Nə olub?"</string>
+ <string name="respond_via_sms_canned_response_2" msgid="4074450431532859214">"Özüm zəng edəcəm."</string>
+ <string name="respond_via_sms_canned_response_3" msgid="3496079065723960450">"Özüm sonra zəng edəcəm."</string>
+ <string name="respond_via_sms_canned_response_4" msgid="1698989243040062190">"İndi danışa bilmirəm. Sonra zəng edin."</string>
+ <string name="respond_via_sms_setting_title" msgid="3754000371039709383">"Tez cavablar"</string>
+ <string name="respond_via_sms_setting_title_2" msgid="6104662227299493906">"Tez cavablara düzəliş edin"</string>
+ <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
+ <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Tez cavab"</string>
+ <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Mesaj <xliff:g id="PHONE_NUMBER">%s</xliff:g> nömrəsinə göndərildi."</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"Hesabların çağırılması"</string>
+ <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Yalnız təcili zənglərə icazə verilir."</string>
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Bu proqram Telefon icazəsi olmadan zəng edə bilməz."</string>
+ <string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"Zəngi yerləşdirmək üçün düzgün nömrə daxil edin."</string>
+ <string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"Hazırda çağrı edilə bilməz."</string>
+ <string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"Video zəng etmək üçün TTY Rejimini deaktiv edin."</string>
+ <string name="no_vm_number" msgid="4164780423805688336">"Səsli poçt nömrəsi çatışmır"</string>
+ <string name="no_vm_number_msg" msgid="1300729501030053828">"SIM kartda heç bir səsli poçt nömrəsi yoxdur."</string>
+ <string name="add_vm_number_str" msgid="4676479471644687453">"Nömrə əlavə edin"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"Defolt nömrə yığan tətbiqi dəyişdirilsin?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"Defolt nömrə yığan tətbiqi kimi <xliff:g id="CURRENT_APP">%2$s</xliff:g> əvəzinə <xliff:g id="NEW_APP">%1$s</xliff:g> işlədilsin?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"Defolt nömrə yığan tətbiqi kimi <xliff:g id="NEW_APP">%s</xliff:g> işlədilsin?"</string>
+</resources>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index 63d32b1..49005ff 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -16,7 +16,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"Телефон"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"Управление на телефонните обаждания"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"Телефон"</string>
<string name="unknown" msgid="6878797917991465859">"Неизвестен номер"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Пропуснато обаждане"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Пропуснати обаждания"</string>
@@ -31,15 +32,20 @@
<string name="respond_via_sms_canned_response_3" msgid="3496079065723960450">"Ще ви се обадя по-късно."</string>
<string name="respond_via_sms_canned_response_4" msgid="1698989243040062190">"Сега не мога да говоря. По-късно?"</string>
<string name="respond_via_sms_setting_title" msgid="3754000371039709383">"Бързи отговори"</string>
- <string name="respond_via_sms_setting_title_2" msgid="6104662227299493906">"Редактиране на бързи отговори"</string>
+ <string name="respond_via_sms_setting_title_2" msgid="6104662227299493906">"Редакт. на бързи отговори"</string>
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Бърз отговор"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"До <xliff:g id="PHONE_NUMBER">%s</xliff:g> бе изпратено съобщение."</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"Собственикът на устройството разрешава само спешни обаждания"</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"Профили за обаждане"</string>
+ <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Разрешени са само спешни обаждания."</string>
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Това приложение не може да извършва изходящи обаждания без разрешението за телефон."</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"За да извършите обаждане, въведете валиден номер."</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"Понастоящем обаждането не може да бъде добавено."</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"Моля, деактивирайте режима на TTY, за да извършвате видеообаждания."</string>
<string name="no_vm_number" msgid="4164780423805688336">"Липсващ номер на гласова поща"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"На SIM картата няма съхранен номер за гласова поща."</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"Добавяне на номер"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"Да се промени ли основното приложение за дайлер?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"<xliff:g id="NEW_APP">%1$s</xliff:g> да се използва ли вместо <xliff:g id="CURRENT_APP">%2$s</xliff:g> като основното ви приложение за дайлер?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"Да се използва ли <xliff:g id="NEW_APP">%s</xliff:g> като основното ви приложение за дайлер?"</string>
</resources>
diff --git a/res/values-bn-rBD/strings.xml b/res/values-bn-rBD/strings.xml
index 6922524..ff406db 100644
--- a/res/values-bn-rBD/strings.xml
+++ b/res/values-bn-rBD/strings.xml
@@ -16,7 +16,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"ফোন"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"ফোন কল ব্যবস্থাপনা"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"ফোন"</string>
<string name="unknown" msgid="6878797917991465859">"অজানা"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"মিসড কল"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"মিসড কলগুলি"</string>
@@ -35,11 +36,17 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"দ্রুত প্রতিক্রিয়া"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"<xliff:g id="PHONE_NUMBER">%s</xliff:g> এ বার্তা পাঠানো হয়েছে৷"</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"ডিভাইসের মালিক শুধুমাত্র জরুরি কলগুলিতে অনুমতি দিয়েছেন"</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"কলিং অ্যাকাউন্টগুলি"</string>
+ <!-- no translation found for outgoing_call_not_allowed_user_restriction (6872406278300131364) -->
+ <skip />
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"এই অ্যাপ্লিকেশানটি ফোনের অনুমতি ছাড়া আউটগোয়িং কলগুলি করতে পারবে না।"</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"কোনো কল স্থাপন করতে, একটি বৈধ নম্বর লিখুন৷"</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"এই মুহূর্তে কল যোগ করা যাবে না৷"</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"অনুগ্রহ করে ভিডিও কলগুলি করতে TTY মোড অক্ষম করুন৷"</string>
<string name="no_vm_number" msgid="4164780423805688336">"ভয়েসমেল নম্বর অনুপস্থিত"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"সিম কার্ডটিতে কোনো ভয়েসমেল নম্বর সংরক্ষিত নেই৷"</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"একটি নম্বর যোগ করুন"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"ডিফল্ট ডায়ালার অ্যাপ পরিবর্তন করবেন?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"আপনার ডিফল্ট ডায়লার অ্যাপ হিসেবে <xliff:g id="CURRENT_APP">%2$s</xliff:g> পরিবর্তে <xliff:g id="NEW_APP">%1$s</xliff:g> ব্যবহার করবেন?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"আপনার ডিফল্ট ডায়লার অ্যাপ হিসেবে <xliff:g id="NEW_APP">%s</xliff:g> ব্যবহার করবেন?"</string>
</resources>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index e3cf515..f297dab 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -16,7 +16,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"Telèfon"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"Gestió de trucades telefòniques"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"Telèfon"</string>
<string name="unknown" msgid="6878797917991465859">"Desconegut"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Trucada perduda"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Trucades perdudes"</string>
@@ -35,11 +36,17 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Resposta ràpida"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Missatge enviat a <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"El propietari del dispositiu només permet les trucades d\'emergència."</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"Comptes de trucades"</string>
+ <!-- no translation found for outgoing_call_not_allowed_user_restriction (6872406278300131364) -->
+ <skip />
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Aquesta aplicació no pot fer trucades sortints sense el permís del telèfon."</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"Per realitzar una trucada, introdueix un número vàlid."</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"En aquest moment no es pot afegir la trucada."</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"Desactiva el mode de TTY per fer videotrucades."</string>
- <string name="no_vm_number" msgid="4164780423805688336">"Falta el número de correu de veu"</string>
- <string name="no_vm_number_msg" msgid="1300729501030053828">"No hi ha cap número de correu de veu emmagatzemat a la targeta SIM."</string>
+ <string name="no_vm_number" msgid="4164780423805688336">"Falta el número de la bústia de veu"</string>
+ <string name="no_vm_number_msg" msgid="1300729501030053828">"No hi ha cap número de bústia de veu emmagatzemat a la targeta SIM."</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"Afegeix número"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"Vols canviar l\'aplicació de marcador predeterminada?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"Vols fer servir <xliff:g id="NEW_APP">%1$s</xliff:g> en lloc de <xliff:g id="CURRENT_APP">%2$s</xliff:g> com a aplicació de marcador predeterminada?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"Vols fer servir <xliff:g id="NEW_APP">%s</xliff:g> com a aplicació de marcador predeterminada?"</string>
</resources>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index 259cca9..56c5c76 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -16,7 +16,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"Telefon"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"Správa telefonních hovorů"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"Telefon"</string>
<string name="unknown" msgid="6878797917991465859">"Neznámý volající"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Zmeškaný hovor"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Zmeškané hovory"</string>
@@ -35,11 +36,17 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Rychlá odpověď"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Zpráva byla odeslána na číslo <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"Vlastník zařízení povolil pouze tísňová volání."</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"Účty pro volání"</string>
+ <!-- no translation found for outgoing_call_not_allowed_user_restriction (6872406278300131364) -->
+ <skip />
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Tato aplikace nemůže provádět odchozí hovory bez oprávnění k použití telefonu."</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"Chcete-li uskutečnit hovor, zadejte platné telefonní číslo."</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"Hovor aktuálně nelze přidat."</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"Chcete-li vést videohovory, vypněte režim TTY."</string>
<string name="no_vm_number" msgid="4164780423805688336">"Chybí číslo hlasové schránky"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"Na SIM kartě není uloženo žádné číslo hlasové schránky."</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"Přidat číslo"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"Změnit výchozí aplikaci vytáčení?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"Chcete aplikaci <xliff:g id="NEW_APP">%1$s</xliff:g> použít jako výchozí aplikaci vytáčení místo aplikace <xliff:g id="CURRENT_APP">%2$s</xliff:g>?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"Chcete aplikaci <xliff:g id="NEW_APP">%s</xliff:g> použít jako výchozí aplikaci vytáčení?"</string>
</resources>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index 83be7ed..5d8fa36 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -16,7 +16,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"Telefon"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"Opkaldsstyring"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"Opkald"</string>
<string name="unknown" msgid="6878797917991465859">"Ukendt"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Ubesvarede opkald"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Ubesvarede opkald"</string>
@@ -35,11 +36,17 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Hurtigt svar"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Meddelelsen er sendt til <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"Enhedens ejer tillader kun nødopkald."</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"Opkaldskonti"</string>
+ <!-- no translation found for outgoing_call_not_allowed_user_restriction (6872406278300131364) -->
+ <skip />
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Denne app kan ikke foretage udgående opkald uden opkaldstilladelse."</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"Indtast et gyldigt nummer for at foretage et opkald."</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"Opkaldet kan ikke tilføjes på nuværende tidspunkt."</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"Deaktiver TTY-tilstanden for at foretage videoopkald."</string>
<string name="no_vm_number" msgid="4164780423805688336">"Telefonsvarernummer mangler"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"Der er ikke gemt noget telefonsvarernummer på SIM-kortet."</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"Tilføj nummer"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"Vil du skifte standardappen til opkald?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"Brug <xliff:g id="NEW_APP">%1$s</xliff:g> i stedet for <xliff:g id="CURRENT_APP">%2$s</xliff:g> som din standardapp til opkald?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"Brug <xliff:g id="NEW_APP">%s</xliff:g> som din standardapp til opkald?"</string>
</resources>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index 29aecb2..fc8e9ae 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -16,7 +16,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"Telefon"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"Anrufverwaltung"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"Telefon"</string>
<string name="unknown" msgid="6878797917991465859">"Unbekannt"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Entgangener Anruf"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Entgangene Anrufe"</string>
@@ -35,11 +36,17 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Kurzantwort"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Nachricht an <xliff:g id="PHONE_NUMBER">%s</xliff:g> gesendet"</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"Der Geräteinhaber hat nur Notrufe zugelassen."</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"Anrufkonten"</string>
+ <!-- no translation found for outgoing_call_not_allowed_user_restriction (6872406278300131364) -->
+ <skip />
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Diese App darf ohne die Berechtigung \"Standard-App für Telefonie\" keine ausgehenden Anrufe tätigen."</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"Geben Sie eine gültige Nummer ein."</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"Der Anruf kann momentan nicht hinzugefügt werden."</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"Bitte deaktivieren Sie den TTY-Modus, um Videoanrufe tätigen zu können."</string>
<string name="no_vm_number" msgid="4164780423805688336">"Fehlende Mailbox-Nummer"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"Auf der SIM-Karte ist keine Mailbox-Nummer gespeichert."</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"Nummer hinzufügen"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"Standard-App für Telefonie ändern?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"<xliff:g id="NEW_APP">%1$s</xliff:g> statt <xliff:g id="CURRENT_APP">%2$s</xliff:g> als Standard-App für Telefonie verwenden?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"<xliff:g id="NEW_APP">%s</xliff:g> als Standard-App für Telefonie verwenden?"</string>
</resources>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index a4249bb..1d0d773 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -16,7 +16,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"Τηλέφωνο"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"Διαχείριση τηλεφωνικών κλήσεων"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"Τηλέφωνο"</string>
<string name="unknown" msgid="6878797917991465859">"Άγνωστος"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Αναπάντητη κλήση"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Αναπάντητες κλήσεις"</string>
@@ -31,15 +32,21 @@
<string name="respond_via_sms_canned_response_3" msgid="3496079065723960450">"Θα σου τηλεφωνήσω αργότερα."</string>
<string name="respond_via_sms_canned_response_4" msgid="1698989243040062190">"Δεν μπορώ τώρα. Πάρε με αργότερα."</string>
<string name="respond_via_sms_setting_title" msgid="3754000371039709383">"Γρήγορες απαντήσεις"</string>
- <string name="respond_via_sms_setting_title_2" msgid="6104662227299493906">"Επεξεργασία γρήγορων απαντήσεων"</string>
+ <string name="respond_via_sms_setting_title_2" msgid="6104662227299493906">"Επεξεργασία"</string>
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Γρήγορη απάντηση"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Το μήνυμα εστάλη στο <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"Από τον κάτοχο της συσκευής επιτρέπονται μόνο κλήσεις έκτακτης ανάγκης"</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"Λογαριασμοί κλήσης"</string>
+ <!-- no translation found for outgoing_call_not_allowed_user_restriction (6872406278300131364) -->
+ <skip />
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Αυτή η εφαρμογή δεν μπορεί να πραγματοποιήσει εξερχόμενες κλήσεις χωρίς την άδεια \"Τηλέφωνο\"."</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"Για να πραγματοποιήσετε μια κλήση, εισαγάγετε έναν έγκυρο αριθμό."</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"Δεν είναι δυνατή η προσθήκη κλήσης αυτήν τη στιγμή."</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"Απενεργοποιήστε τη λειτουργία TTY για να πραγματοποιήσετε βιντεοκλήσεις."</string>
<string name="no_vm_number" msgid="4164780423805688336">"Λείπει ο αριθμός αυτόματου τηλεφωνητή"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"Δεν έχει αποθηκευτεί αριθμός για τον αυτόματο τηλεφωνητή στην κάρτα SIM."</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"Προσθήκη αριθμού"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"Αλλαγή της προεπιλεγμένης εφαρμογής Dialer;"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"Χρήση της εφαρμογής <xliff:g id="NEW_APP">%1$s</xliff:g> αντί για την εφαρμογή <xliff:g id="CURRENT_APP">%2$s</xliff:g> ως προεπιλεγμένης εφαρμογής Dialer;"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"Χρήση της εφαρμογής <xliff:g id="NEW_APP">%s</xliff:g> ως προεπιλεγμένης εφαρμογής Dialer;"</string>
</resources>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index 97d4c9c..78e374e 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -16,7 +16,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"Phone"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"Phone Call Management"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"Telephone"</string>
<string name="unknown" msgid="6878797917991465859">"Unknown"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Missed call"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Missed calls"</string>
@@ -35,11 +36,16 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Quick response"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Message sent to <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"Only emergency calls are allowed by the device owner"</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"Calling accounts"</string>
+ <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Only emergency calls are allowed."</string>
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"This application cannot make outgoing calls without Phone permission."</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"To place a call, enter a valid number."</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"Call cannot be added at this time."</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"Please disable TTY Mode to make video calls."</string>
<string name="no_vm_number" msgid="4164780423805688336">"Missing voicemail number"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"No voicemail number is stored on the SIM card."</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"Add number"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"Change default Dialer app?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"Use <xliff:g id="NEW_APP">%1$s</xliff:g> instead of <xliff:g id="CURRENT_APP">%2$s</xliff:g> as your default dialler app?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"Use <xliff:g id="NEW_APP">%s</xliff:g> as your default dialler app?"</string>
</resources>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index 97d4c9c..78e374e 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -16,7 +16,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"Phone"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"Phone Call Management"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"Telephone"</string>
<string name="unknown" msgid="6878797917991465859">"Unknown"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Missed call"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Missed calls"</string>
@@ -35,11 +36,16 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Quick response"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Message sent to <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"Only emergency calls are allowed by the device owner"</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"Calling accounts"</string>
+ <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Only emergency calls are allowed."</string>
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"This application cannot make outgoing calls without Phone permission."</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"To place a call, enter a valid number."</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"Call cannot be added at this time."</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"Please disable TTY Mode to make video calls."</string>
<string name="no_vm_number" msgid="4164780423805688336">"Missing voicemail number"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"No voicemail number is stored on the SIM card."</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"Add number"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"Change default Dialer app?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"Use <xliff:g id="NEW_APP">%1$s</xliff:g> instead of <xliff:g id="CURRENT_APP">%2$s</xliff:g> as your default dialler app?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"Use <xliff:g id="NEW_APP">%s</xliff:g> as your default dialler app?"</string>
</resources>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index 97d4c9c..78e374e 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -16,7 +16,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"Phone"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"Phone Call Management"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"Telephone"</string>
<string name="unknown" msgid="6878797917991465859">"Unknown"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Missed call"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Missed calls"</string>
@@ -35,11 +36,16 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Quick response"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Message sent to <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"Only emergency calls are allowed by the device owner"</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"Calling accounts"</string>
+ <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Only emergency calls are allowed."</string>
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"This application cannot make outgoing calls without Phone permission."</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"To place a call, enter a valid number."</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"Call cannot be added at this time."</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"Please disable TTY Mode to make video calls."</string>
<string name="no_vm_number" msgid="4164780423805688336">"Missing voicemail number"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"No voicemail number is stored on the SIM card."</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"Add number"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"Change default Dialer app?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"Use <xliff:g id="NEW_APP">%1$s</xliff:g> instead of <xliff:g id="CURRENT_APP">%2$s</xliff:g> as your default dialler app?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"Use <xliff:g id="NEW_APP">%s</xliff:g> as your default dialler app?"</string>
</resources>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index 3b6bd73..4285c28 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -16,17 +16,18 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"Teléfono"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"Administración de llamadas telefónicas"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"Teléfono"</string>
<string name="unknown" msgid="6878797917991465859">"Desconocida"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Llamada perdida"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Llamadas perdidas"</string>
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> llamadas perdidas"</string>
<string name="notification_missedCallTicker" msgid="504686252427747209">"Se perdieron las llamadas de <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
- <string name="notification_missedCall_call_back" msgid="2684890353590890187">"Devolver llamada"</string>
+ <string name="notification_missedCall_call_back" msgid="2684890353590890187">"Llamar"</string>
<string name="notification_missedCall_message" msgid="3049928912736917988">"Mensaje"</string>
<string name="accessibility_call_muted" msgid="2776111226185342220">"Llamada silenciada"</string>
<string name="accessibility_speakerphone_enabled" msgid="1988512040421036359">"Altavoz habilitado"</string>
- <string name="respond_via_sms_canned_response_1" msgid="2461606462788380215">"No puedo hablar ahora. ¿Qué pasa?"</string>
+ <string name="respond_via_sms_canned_response_1" msgid="2461606462788380215">"No puedo hablar ahora. ¿Todo bien?"</string>
<string name="respond_via_sms_canned_response_2" msgid="4074450431532859214">"Te llamo enseguida."</string>
<string name="respond_via_sms_canned_response_3" msgid="3496079065723960450">"Te llamo más tarde."</string>
<string name="respond_via_sms_canned_response_4" msgid="1698989243040062190">"No puedo hablar ahora. ¿Me llamas más tarde?"</string>
@@ -35,11 +36,17 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Respuesta rápida"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Mensaje enviado a <xliff:g id="PHONE_NUMBER">%s</xliff:g>"</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"El propietario del dispositivo solo permite las llamadas de emergencia."</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"Cuentas telefónicas"</string>
+ <!-- no translation found for outgoing_call_not_allowed_user_restriction (6872406278300131364) -->
+ <skip />
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Esta aplicación no puede realizar llamadas salientes sin permiso del teléfono."</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"Para realizar una llamada, ingresa un número válido."</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"No se puede agregar la llamada en este momento."</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"Inhabilita el modo TTY para realizar una videollamada."</string>
<string name="no_vm_number" msgid="4164780423805688336">"Falta el número de correo de voz"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"No hay un número de correo de voz almacenado en la tarjeta SIM."</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"Agregar número"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"¿Quieres cambiar la aplicación Marcador predeterminada?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"¿Quieres usar <xliff:g id="NEW_APP">%1$s</xliff:g> en lugar de <xliff:g id="CURRENT_APP">%2$s</xliff:g> como la aplicación de marcado predeterminada?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"¿Quieres usar <xliff:g id="NEW_APP">%s</xliff:g> como la aplicación de marcado predeterminada?"</string>
</resources>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index 7287095..071ddba 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -16,7 +16,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"Teléfono"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"Gestión de llamadas del teléfono"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"Teléfono"</string>
<string name="unknown" msgid="6878797917991465859">"Desconocido"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Llamada perdida"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Llamadas perdidas"</string>
@@ -35,11 +36,17 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Respuestas rápidas"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Mensaje enviado a <xliff:g id="PHONE_NUMBER">%s</xliff:g>"</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"El propietario del dispositivo solo permite llamadas de emergencia"</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"Cuentas de llamadas"</string>
+ <!-- no translation found for outgoing_call_not_allowed_user_restriction (6872406278300131364) -->
+ <skip />
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Esta aplicación no puede hacer llamadas sin permiso del teléfono."</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"Para realizar una llamada, introduce un número válido."</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"No se puede añadir la llamada en este momento."</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"Inhabilita el modo TTY para hacer videollamadas."</string>
<string name="no_vm_number" msgid="4164780423805688336">"Falta el número del buzón de voz."</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"No se ha almacenado ningún número de buzón de voz en la tarjeta SIM."</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"Añadir número"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"¿Cambiar el marcador predeterminado?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"¿Quieres utilizar <xliff:g id="NEW_APP">%1$s</xliff:g> en lugar de <xliff:g id="CURRENT_APP">%2$s</xliff:g> como marcador predeterminado?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"¿Quieres utilizar <xliff:g id="NEW_APP">%s</xliff:g> como marcador predeterminado?"</string>
</resources>
diff --git a/res/values-et-rEE/strings.xml b/res/values-et-rEE/strings.xml
index 47627f8..b6090f3 100644
--- a/res/values-et-rEE/strings.xml
+++ b/res/values-et-rEE/strings.xml
@@ -16,7 +16,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"Telefon"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"Telefonikõnede haldus"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"Telefon"</string>
<string name="unknown" msgid="6878797917991465859">"Tundmatu"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Vastamata kõne"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Vastamata kõned"</string>
@@ -35,11 +36,17 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Kiirvastus"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Sõnum on saadetud numbrile <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"Seadme omanik lubab ainult hädaabikõnesid"</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"Kõnekontod"</string>
+ <!-- no translation found for outgoing_call_not_allowed_user_restriction (6872406278300131364) -->
+ <skip />
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"See rakendus ei saa ilma telefoni kasutamise loata välja helistada."</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"Helistamiseks sisestage kehtiv number."</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"Kõnet ei saa praegu lisada."</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"Videokõnede tegemiseks keelake TTY-režiim."</string>
<string name="no_vm_number" msgid="4164780423805688336">"Puudub kõnepostinumber"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"SIM-kaardile pole salvestatud ühtegi kõnepostinumbrit."</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"Lisa number"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"Kas muuta helistamise vaikerakendust?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"Kas kasutada rakendust <xliff:g id="NEW_APP">%1$s</xliff:g> rakenduse <xliff:g id="CURRENT_APP">%2$s</xliff:g> asemel helistamise vaikerakendusena?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"Kas kasutada rakendust <xliff:g id="NEW_APP">%s</xliff:g> helistamise vaikerakendusena?"</string>
</resources>
diff --git a/res/values-eu-rES/strings.xml b/res/values-eu-rES/strings.xml
index e35b3fe..654f115 100644
--- a/res/values-eu-rES/strings.xml
+++ b/res/values-eu-rES/strings.xml
@@ -16,7 +16,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"Telefonoa"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"Telefono-deien kudeaketa"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"Telefonoa"</string>
<string name="unknown" msgid="6878797917991465859">"Ezezaguna"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Dei galdua"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Dei galduak"</string>
@@ -35,11 +36,17 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Erantzun bizkorra"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Mezua bidali da <xliff:g id="PHONE_NUMBER">%s</xliff:g> zenbakira."</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"Gailuaren jabeak larrialdi-deiak bakarrik egitea onartzen du"</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"Deiak egiteko kontuak"</string>
+ <!-- no translation found for outgoing_call_not_allowed_user_restriction (6872406278300131364) -->
+ <skip />
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Aplikazioak irteerako deiak egin ahal izan ditzan, telefonoaren eginbidea erabiltzeko baimena behar du."</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"Deitzeko, idatzi balio duen zenbaki bat."</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"Une honetan ezin da deirik gehitu."</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"Desgaitu TTY modua bideo-deiak egiteko."</string>
<string name="no_vm_number" msgid="4164780423805688336">"Erantzungailuaren zenbakia falta da"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"Ez da erantzungailuaren zenbakirik gorde SIM txartelean."</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"Gehitu zenbakia"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"Telefono-aplikazio lehenetsia aldatu nahi duzu?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"<xliff:g id="CURRENT_APP">%2$s</xliff:g> aplikazioaren ordez <xliff:g id="NEW_APP">%1$s</xliff:g> erabili nahi duzu telefono-aplikazio lehenetsi gisa?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"<xliff:g id="NEW_APP">%s</xliff:g> telefono-aplikazio lehenetsi gisa erabili nahi duzu?"</string>
</resources>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index 61d4ae1..25e380a 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -16,7 +16,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"تلفن"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"مدیریت تماس تلفنی"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"تلفن"</string>
<string name="unknown" msgid="6878797917991465859">"ناشناس"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"تماس بی پاسخ"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"تماسهای بی پاسخ"</string>
@@ -35,11 +36,17 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"پاسخ سریع"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"پیام به <xliff:g id="PHONE_NUMBER">%s</xliff:g> ارسال شد."</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"مالک دستگاه فقط تماسهای اضطراری را مجاز کرده است"</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"حسابهای تماس"</string>
+ <!-- no translation found for outgoing_call_not_allowed_user_restriction (6872406278300131364) -->
+ <skip />
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"این برنامه نمیتواند بدون اجازه تلفن، تماسهای خروجی برقرار کند."</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"برای برقراری تماس، یک شماره معتبر وارد کنید."</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"در این زمان نمیتوان تماسی اضافه کرد."</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"لطفاً حالت TTY را برای برقراری تماسهای ویدیویی غیرفعال کنید."</string>
<string name="no_vm_number" msgid="4164780423805688336">"عدم وجود شماره پست صوتی"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"شماره پست صوتی در سیم کارت ذخیره نشده است."</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"افزودن شماره"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"برنامه شمارهگیر پیشفرض تغییر کند؟"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"از <xliff:g id="NEW_APP">%1$s</xliff:g> به جای <xliff:g id="CURRENT_APP">%2$s</xliff:g> به عنوان برنامه شمارهگیر پیشفرض استفاده شود؟"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"از <xliff:g id="NEW_APP">%s</xliff:g> به عنوان برنامه شمارهگیر پیشفرض استفاده شود؟"</string>
</resources>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index 1ff2b0b..f253000 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -16,30 +16,37 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"Puhelin"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"Puhelujen hallinta"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"Puhelin"</string>
<string name="unknown" msgid="6878797917991465859">"Tuntematon"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Vastaamatta jäänyt puhelu"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Vastaamattomat puhelut"</string>
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> vastaamatonta puhelua"</string>
<string name="notification_missedCallTicker" msgid="504686252427747209">"Vastaamatta jäänyt puhelu numerosta <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
- <string name="notification_missedCall_call_back" msgid="2684890353590890187">"Soita takaisin"</string>
+ <string name="notification_missedCall_call_back" msgid="2684890353590890187">"Soita"</string>
<string name="notification_missedCall_message" msgid="3049928912736917988">"Viesti"</string>
<string name="accessibility_call_muted" msgid="2776111226185342220">"Puhelu mykistetty."</string>
<string name="accessibility_speakerphone_enabled" msgid="1988512040421036359">"Kaiutin käytössä."</string>
- <string name="respond_via_sms_canned_response_1" msgid="2461606462788380215">"En voi puhua nyt. Mikä hätänä?"</string>
- <string name="respond_via_sms_canned_response_2" msgid="4074450431532859214">"Soitan sinulle heti takaisin."</string>
+ <string name="respond_via_sms_canned_response_1" msgid="2461606462788380215">"En voi vastata - mitä asiaa?"</string>
+ <string name="respond_via_sms_canned_response_2" msgid="4074450431532859214">"Soitan sinulle pian."</string>
<string name="respond_via_sms_canned_response_3" msgid="3496079065723960450">"Soitan sinulle myöhemmin."</string>
- <string name="respond_via_sms_canned_response_4" msgid="1698989243040062190">"En voi puhua nyt. Soita myöhemmin?"</string>
+ <string name="respond_via_sms_canned_response_4" msgid="1698989243040062190">"En voi vastata. Soitatko myöhemmin?"</string>
<string name="respond_via_sms_setting_title" msgid="3754000371039709383">"Pikavastaukset"</string>
<string name="respond_via_sms_setting_title_2" msgid="6104662227299493906">"Muokkaa pikavastausta"</string>
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Pikavastaukset"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Viesti lähetetty numeroon <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"Laitteen omistaja on sallinut vain hätäpuhelut"</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"Puhelutilit"</string>
+ <!-- no translation found for outgoing_call_not_allowed_user_restriction (6872406278300131364) -->
+ <skip />
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Tämä sovellus ei voi soittaa puheluita ilman Puhelin-lupaa."</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"Soita antamalla kelvollinen numero."</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"Puhelua ei voi lisätä juuri nyt."</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"Poista TTY-tila käytöstä, jos haluat soittaa videopuheluita."</string>
<string name="no_vm_number" msgid="4164780423805688336">"Puhelinvastaajan numero puuttuu"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"SIM-kortille ei ole tallennettu puhelinvastaajan numeroa."</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"Lisää numero"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"Vaihdetaanko oletuspuhelusovellus?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"Asetetaanko oletuspuhelusovellukseksi <xliff:g id="NEW_APP">%1$s</xliff:g> sovelluksen <xliff:g id="CURRENT_APP">%2$s</xliff:g> sijaan?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"Asetetaanko <xliff:g id="NEW_APP">%s</xliff:g> oletuspuhelusovellukseksi?"</string>
</resources>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index 1aa0bdb..bffa229 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -16,7 +16,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"Téléphone"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"Gestion des appels téléphoniques"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"Téléphone"</string>
<string name="unknown" msgid="6878797917991465859">"Inconnu"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Appel manqué"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Appels manqués"</string>
@@ -35,11 +36,17 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Réponse rapide"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Message envoyé à <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"Seuls les appels d\'urgence sont autorisés par le propriétaire de l\'appareil"</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"Comptes d\'appel"</string>
+ <!-- no translation found for outgoing_call_not_allowed_user_restriction (6872406278300131364) -->
+ <skip />
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Cette application ne peut pas faire d\'appels sans l\'autorisation de l\'application Téléphone."</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"Pour faire un appel, entrez un numéro valide."</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"Impossible d\'ajouter l\'appel pour le moment."</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"Veuillez désactiver le mode ATS pour faire un appel vidéo."</string>
<string name="no_vm_number" msgid="4164780423805688336">"Numéro de messagerie vocale manquant"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"Aucun numéro de messagerie vocale n\'est enregistré sur la carte SIM."</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"Ajouter un numéro"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"Changer l\'application de composition par défaut?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"Utiliser <xliff:g id="NEW_APP">%1$s</xliff:g> au lieu de <xliff:g id="CURRENT_APP">%2$s</xliff:g> comme application de composition par défaut?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"Utiliser <xliff:g id="NEW_APP">%s</xliff:g> comme application de composition par défaut?"</string>
</resources>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index f5f8afd..e8ee088 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -16,7 +16,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"Téléphone"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"Gestion des appels téléphoniques"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"Téléphone"</string>
<string name="unknown" msgid="6878797917991465859">"Inconnu"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Appel manqué"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Appels manqués"</string>
@@ -31,15 +32,21 @@
<string name="respond_via_sms_canned_response_3" msgid="3496079065723960450">"Je t\'appellerai plus tard."</string>
<string name="respond_via_sms_canned_response_4" msgid="1698989243040062190">"Peux pas parler. On se rappelle ?"</string>
<string name="respond_via_sms_setting_title" msgid="3754000371039709383">"Réponses rapides"</string>
- <string name="respond_via_sms_setting_title_2" msgid="6104662227299493906">"Modifier les réponses rapides"</string>
+ <string name="respond_via_sms_setting_title_2" msgid="6104662227299493906">"Modifier réponses rapides"</string>
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Réponse rapide"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Message envoyé à <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"Le propriétaire de l\'appareil n\'autorise que les appels d\'urgence."</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"Comptes téléphoniques"</string>
+ <!-- no translation found for outgoing_call_not_allowed_user_restriction (6872406278300131364) -->
+ <skip />
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Cette application ne peut pas passer d\'appels sortants sans l\'autorisation de l\'application Téléphone."</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"Pour émettre un appel, veuillez saisir un numéro valide."</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"Impossible d\'ajouter un appel pour le moment."</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"Veuillez désactiver le mode TTY pour passer des appels vidéo."</string>
<string name="no_vm_number" msgid="4164780423805688336">"Numéro de messagerie vocale manquant"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"Aucun numéro de messagerie vocale n\'est enregistré sur la carte SIM."</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"Ajouter un numéro"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"Modifier l\'application de clavier par défaut ?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"Utiliser <xliff:g id="NEW_APP">%1$s</xliff:g> et non plus <xliff:g id="CURRENT_APP">%2$s</xliff:g> comme application de clavier par défaut ?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"Utiliser <xliff:g id="NEW_APP">%s</xliff:g> comme application de clavier par défaut ?"</string>
</resources>
diff --git a/res/values-gl-rES/strings.xml b/res/values-gl-rES/strings.xml
index 628caa9..f64da11 100644
--- a/res/values-gl-rES/strings.xml
+++ b/res/values-gl-rES/strings.xml
@@ -16,7 +16,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"Teléfono"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"Xestión de chamadas do teléfono"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"Teléfono"</string>
<string name="unknown" msgid="6878797917991465859">"Descoñecido"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Chamada perdida"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Chamadas perdidas"</string>
@@ -35,11 +36,17 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Resposta rápida"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Mensaxe enviada ao <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"O propietario do dispositivo só permite as chamadas de emerxencia"</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"Contas de chamadas"</string>
+ <!-- no translation found for outgoing_call_not_allowed_user_restriction (6872406278300131364) -->
+ <skip />
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Esta aplicación non pode facer chamadas saíntes sen permiso do teléfono."</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"Para realizar unha chamada, introduce un número válido."</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"Neste momento non se pode engadir a chamada."</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"Desactiva o modo TTY para realizar videochamadas."</string>
<string name="no_vm_number" msgid="4164780423805688336">"Falta o número de correo de voz"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"Non hai ningún número de correo de voz almacenado na tarxeta SIM."</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"Engadir número"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"Queres cambiar a aplicación predeterminada do marcador?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"Queres usar <xliff:g id="NEW_APP">%1$s</xliff:g> en lugar de <xliff:g id="CURRENT_APP">%2$s</xliff:g> como a túa aplicación de marcador predeterminada?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"Queres usar <xliff:g id="NEW_APP">%s</xliff:g> como a túa aplicación de marcador predeterminada?"</string>
</resources>
diff --git a/res/values-gu-rIN/strings.xml b/res/values-gu-rIN/strings.xml
index 8d0ad60..12e163c 100644
--- a/res/values-gu-rIN/strings.xml
+++ b/res/values-gu-rIN/strings.xml
@@ -16,7 +16,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"ફોન"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"ફોન કૉલ સંચાલન"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"ફોન"</string>
<string name="unknown" msgid="6878797917991465859">"અજાણ્યું"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"છૂટેલો કૉલ"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"છૂટેલા કૉલ્સ"</string>
@@ -35,11 +36,17 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"ઝડપી પ્રતિસાદ"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"<xliff:g id="PHONE_NUMBER">%s</xliff:g> પર સંદેશ મોકલ્યો."</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"ઉપકરણના માલિક દ્વારા ફક્ત કટોકટીના કૉલ્સને મંજૂરી અપાયેલ છે"</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"કૉલિંગ એકાઉન્ટ્સ"</string>
+ <!-- no translation found for outgoing_call_not_allowed_user_restriction (6872406278300131364) -->
+ <skip />
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"ફોન પરવાનગી વિના આ એપ્લિકેશન આઉટગોઇંગ કૉલ્સ કરી શકતી નથી."</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"કૉલ કરવા માટે, માન્ય નંબર દાખલ કરો."</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"આ સમયે કૉલ ઉમેરી શકાતો નથી."</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"કૃપા કરીને વિડિઓ કૉલ્સ કરવા માટે TTY મોડ અક્ષમ કરો."</string>
<string name="no_vm_number" msgid="4164780423805688336">"વૉઇસમેઇલ નંબર ખૂટે છે"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"SIM કાર્ડ પર કોઈ વૉઇસમેઇલ નંબર સંગ્રહિત નથી."</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"નંબર ઉમેરો"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"ડિફોલ્ટ ડાયલર એપ્લિકેશન બદલીએ?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"તમારી ડિફોલ્ટ ડાયલર એપ્લિકેશન તરીકે <xliff:g id="CURRENT_APP">%2$s</xliff:g> ને બદલે <xliff:g id="NEW_APP">%1$s</xliff:g> નો ઉપયોગ કરીએ?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"તમારી ડિફોલ્ટ ડાયલર એપ્લિકેશન તરીકે <xliff:g id="NEW_APP">%s</xliff:g> નો ઉપયોગ કરીએ?"</string>
</resources>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index 760fe63..09c654b 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -16,7 +16,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"फ़ोन"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"फ़ोन कॉल प्रबंधन"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"फ़ोन"</string>
<string name="unknown" msgid="6878797917991465859">"अज्ञात"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"छूटी कॉल"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"छूटी कॉल"</string>
@@ -35,11 +36,16 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"झटपट उत्तर"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"<xliff:g id="PHONE_NUMBER">%s</xliff:g> को संदेश भेजा गया."</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"डिवाइस स्वामी द्वारा केवल आपातकालीन कॉल करने की अनुमति है"</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"कॉलिंग खाते"</string>
+ <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"केवल आपातकालीन कॉल की अनुमति है."</string>
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"यह ऐप्लिकेशन फ़ोन अनुमति के बिना आउटगोइंग कॉल नहीं कर सकता."</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"कॉल करने के लिए, मान्य नंबर डालें."</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"इस समय कॉल नहीं जोड़ा जा सकता."</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"कृपया वीडियो कॉल करने के लिए TTY मोड अक्षम करें."</string>
<string name="no_vm_number" msgid="4164780423805688336">"गुम वॉयस मेल नंबर"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"सिम कार्ड पर कोई वॉयस मेल नंबर संग्रहीत नहीं है."</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"नंबर जोड़ें"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"डिफ़ॉल्ट डायलर ऐप को बदलें?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"अपने डिफ़ॉल्ट डायलर ऐप के रूप में <xliff:g id="CURRENT_APP">%2$s</xliff:g> के बजाय <xliff:g id="NEW_APP">%1$s</xliff:g> का उपयोग करें?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"अपने डिफ़ॉल्ट डायलर ऐप के रूप में <xliff:g id="NEW_APP">%s</xliff:g> का उपयोग करें?"</string>
</resources>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index 5242229..cf9a5e9 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -16,7 +16,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"Telefon"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"Upravljanje telefonskim pozivima"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"Telefon"</string>
<string name="unknown" msgid="6878797917991465859">"Nepoznato"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Propušteni poziv"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Propušteni pozivi"</string>
@@ -29,17 +30,23 @@
<string name="respond_via_sms_canned_response_1" msgid="2461606462788380215">"Sada ne mogu razgovarati. Što ima?"</string>
<string name="respond_via_sms_canned_response_2" msgid="4074450431532859214">"Nazvat ću vas odmah."</string>
<string name="respond_via_sms_canned_response_3" msgid="3496079065723960450">"Zvat ću vas kasnije."</string>
- <string name="respond_via_sms_canned_response_4" msgid="1698989243040062190">"Sada ne mogu razgovarati. Nazovite me kasnije?"</string>
+ <string name="respond_via_sms_canned_response_4" msgid="1698989243040062190">"Sada ne mogu razgovarati. Nazovite me kasnije."</string>
<string name="respond_via_sms_setting_title" msgid="3754000371039709383">"Brzi odgovori"</string>
<string name="respond_via_sms_setting_title_2" msgid="6104662227299493906">"Uređivanje brzih odgovora"</string>
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Brzi odgovor"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Poruka poslana na broj <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"Vlasnik uređaja dopušta samo hitne pozive"</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"Računi za pozivanje"</string>
+ <!-- no translation found for outgoing_call_not_allowed_user_restriction (6872406278300131364) -->
+ <skip />
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Ova aplikacija ne može uspostavljati odlazne pozive bez dopuštenja za telefon."</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"Unesite važeći broj da biste uspostavili poziv."</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"Poziv trenutačno nije moguć."</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"Onemogućite TTY način da biste omogućili videopozive."</string>
<string name="no_vm_number" msgid="4164780423805688336">"Nedostaje broj govorne pošte"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"Na SIM kartici nije spremljen broj govorne pošte."</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"Dodaj broj"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"Želite li promijeniti zadanu aplikaciju brojčanika?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"Želite li upotrebljavati <xliff:g id="NEW_APP">%1$s</xliff:g> umjesto aplikacije <xliff:g id="CURRENT_APP">%2$s</xliff:g> kao zadanu aplikaciju brojčanika?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"Želite li upotrebljavati <xliff:g id="NEW_APP">%s</xliff:g> kao zadanu aplikaciju brojčanika?"</string>
</resources>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index 69fb335..4796321 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -16,7 +16,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"Telefon"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"Híváskezelés"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"Telefon"</string>
<string name="unknown" msgid="6878797917991465859">"Ismeretlen"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Nem fogadott hívás"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Nem fogadott hívások"</string>
@@ -35,11 +36,16 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Gyors válasz"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Üzenet elküldve ide: <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"Az eszköz tulajdonosa csak a segélyhívásokat engedélyezte"</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"Telefonos fiókok"</string>
+ <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Csak vészhívás engedélyezett."</string>
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Az alkalmazásból nem lehet kimenő hívást kezdeményezni a Telefon (Phone) engedély nélkül."</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"Hívásindításhoz adjon meg egy érvényes számot."</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"Jelenleg nem lehet videohívást hozzáadni."</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"Videohívások indításához kapcsolja ki a TTY módot."</string>
<string name="no_vm_number" msgid="4164780423805688336">"Hiányzik a hangposta száma"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"Nincs hangpostaszám a SIM kártyán."</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"Szám hozzáadása"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"Módosítja az alapértelmezett tárcsázó alkalmazást?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"Legyen a(z) <xliff:g id="NEW_APP">%1$s</xliff:g> az alapértelmezett tárcsázó alkalmazás a(z) <xliff:g id="CURRENT_APP">%2$s</xliff:g> helyett?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"Legyen a(z) <xliff:g id="NEW_APP">%s</xliff:g> az alapértelmezett tárcsázó alkalmazás?"</string>
</resources>
diff --git a/res/values-hy-rAM/strings.xml b/res/values-hy-rAM/strings.xml
index c38c6fc..05a7746 100644
--- a/res/values-hy-rAM/strings.xml
+++ b/res/values-hy-rAM/strings.xml
@@ -16,7 +16,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"Հեռախոս"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"Հեռախոսային զանգերի կառավարում"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"Հեռախոս"</string>
<string name="unknown" msgid="6878797917991465859">"Անհայտ"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Բաց թողնված զանգ"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Բաց թողնված զանգեր"</string>
@@ -35,11 +36,17 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Արագ պատասխան"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Հաղորդագրությունն ուղարկվել է <xliff:g id="PHONE_NUMBER">%s</xliff:g>-ին:"</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"Սարքի սեփականատերը թույլատրում է միայն արտակարգ իրավիճակի զանգերը"</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"Հաշիվներ զանգերի համար"</string>
+ <!-- no translation found for outgoing_call_not_allowed_user_restriction (6872406278300131364) -->
+ <skip />
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Առանց Հեռախոսի թույլտվության այս ծրագիրը չի կարող ելքային զանգեր կատարել:"</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"Զանգ կատարելու համար մուտքագրեք ճիշտ համար:"</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"Հնարավոր չէ ևս մեկ զանգ ավելացնել այս պահին:"</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"Տեսազանգեր կատարելու համար անջատեք TTY ռեժիմը:"</string>
<string name="no_vm_number" msgid="4164780423805688336">"Բացակայում է ձայնային փոստի համարը"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"SIM քարտում ձայնային փոստի ոչ մի համար գրանցված չէ:"</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"Ավելացնել համար"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"Փոխե՞լ կանխադրված Համարհավաքի հավելվածը:"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"Օգտագործե՞լ <xliff:g id="NEW_APP">%1$s</xliff:g> հավելվածը <xliff:g id="CURRENT_APP">%2$s</xliff:g>-ի փոխարեն որպես համարհավաքի կանխադրված հավելված:"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"Դարձնե՞լ <xliff:g id="NEW_APP">%s</xliff:g> հավելվածը համարհավաքի կանխադրված հավելված:"</string>
</resources>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index 7ad8e00..59c3ca9 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -16,13 +16,14 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"Telepon"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"Pengelolaan Panggilan Telepon"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"Telepon"</string>
<string name="unknown" msgid="6878797917991465859">"Tidak diketahui"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Panggilan tak terjawab"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Panggilan tak terjawab"</string>
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> panggilan tak terjawab"</string>
<string name="notification_missedCallTicker" msgid="504686252427747209">"Panggilan tak terjawab dari <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
- <string name="notification_missedCall_call_back" msgid="2684890353590890187">"Hubungi kembali"</string>
+ <string name="notification_missedCall_call_back" msgid="2684890353590890187">"Telepon"</string>
<string name="notification_missedCall_message" msgid="3049928912736917988">"Pesan"</string>
<string name="accessibility_call_muted" msgid="2776111226185342220">"Panggilan disenyapkan."</string>
<string name="accessibility_speakerphone_enabled" msgid="1988512040421036359">"Pengeras suara ponsel diaktifkan."</string>
@@ -30,16 +31,21 @@
<string name="respond_via_sms_canned_response_2" msgid="4074450431532859214">"Saya segera telepon balik."</string>
<string name="respond_via_sms_canned_response_3" msgid="3496079065723960450">"Nanti saya telepon balik."</string>
<string name="respond_via_sms_canned_response_4" msgid="1698989243040062190">"Tak bisa bicara skrg. Tlp lg nanti?"</string>
- <string name="respond_via_sms_setting_title" msgid="3754000371039709383">"Tanggapan cepat"</string>
- <string name="respond_via_sms_setting_title_2" msgid="6104662227299493906">"Edit tanggapan cepat"</string>
+ <string name="respond_via_sms_setting_title" msgid="3754000371039709383">"Respons cepat"</string>
+ <string name="respond_via_sms_setting_title_2" msgid="6104662227299493906">"Edit respons cepat"</string>
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
- <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Tanggapan cepat"</string>
+ <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Respons cepat"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Pesan dikirim ke <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"Hanya panggilan darurat yang diizinkan oleh pemilik perangkat"</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"Akun pemanggil"</string>
+ <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Hanya panggilan darurat yang diizinkan."</string>
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Aplikasi ini tidak dapat melakukan panggilan keluar tanpa izin Telepon."</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"Untuk melakukan panggilan telepon, masukkan nomor yang valid."</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"Panggilan tidak dapat ditambahkan untuk saat ini."</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"Nonaktifkan Mode TTY untuk melakukan panggilan video."</string>
<string name="no_vm_number" msgid="4164780423805688336">"Nomor kotak pesan hilang"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"Tidak ada nomor kotak pesan tersimpan pada kartu SIM."</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"Tambahkan nomor"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"Ubah aplikasi Pemanggil default?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"Gunakan <xliff:g id="NEW_APP">%1$s</xliff:g>, bukan <xliff:g id="CURRENT_APP">%2$s</xliff:g> sebagai aplikasi pemanggil default?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"Gunakan <xliff:g id="NEW_APP">%s</xliff:g> sebagai aplikasi pemanggil default?"</string>
</resources>
diff --git a/res/values-is-rIS/strings.xml b/res/values-is-rIS/strings.xml
index 13827b9..59e37ac 100644
--- a/res/values-is-rIS/strings.xml
+++ b/res/values-is-rIS/strings.xml
@@ -16,7 +16,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"Sími"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"Símtalastjórnun"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"Sími"</string>
<string name="unknown" msgid="6878797917991465859">"Óþekkt"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Ósvarað símtal"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Ósvöruð símtöl"</string>
@@ -35,11 +36,17 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Snarsvar"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Skilaboð send til <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"Eigandi tækisins leyfir aðeins neyðarsímtöl"</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"Símtalareikningar"</string>
+ <!-- no translation found for outgoing_call_not_allowed_user_restriction (6872406278300131364) -->
+ <skip />
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Þetta forrit getur ekki hringt án heimildar í símanum."</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"Sláðu inn gilt númer til að hringja símtal."</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"Ekki er hægt að bæta símtali við sem stendur."</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"Slökktu á fjarritastillingu til að hringja myndsímtöl."</string>
<string name="no_vm_number" msgid="4164780423805688336">"Talhólfsnúmer vantar"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"Ekkert talhólfsnúmer er vistað á SIM-kortinu."</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"Bæta númeri við"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"Viltu skipta um sjálfgefið hringiforrit?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"Nota <xliff:g id="NEW_APP">%1$s</xliff:g> í stað <xliff:g id="CURRENT_APP">%2$s</xliff:g> sem sjálfgefið hringiforrit?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"Nota <xliff:g id="NEW_APP">%s</xliff:g> sem sjálfgefið hringiforrit?"</string>
</resources>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index 0505e37..aa42c41 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -16,7 +16,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"Telefono"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"Phone Call Management"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"Telefono"</string>
<string name="unknown" msgid="6878797917991465859">"Sconosciuto"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Chiamata senza risposta"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Chiamate senza risposta"</string>
@@ -35,11 +36,17 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Risposta rapida"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Messaggio inviato a <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"Il proprietario del dispositivo consente soltanto chiamate di emergenza"</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"Account di chiamata"</string>
+ <!-- no translation found for outgoing_call_not_allowed_user_restriction (6872406278300131364) -->
+ <skip />
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Non è possibile effettuare chiamate tramite questa applicazione senza l\'autorizzazione sul telefono."</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"Per effettuare una chiamata, inserisci un numero valido."</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"Al momento non è possibile aggiungere la chiamata."</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"Per poter fare videochiamate devi disattivare la modalità TTY."</string>
<string name="no_vm_number" msgid="4164780423805688336">"Numero segreteria mancante"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"Nessun numero di segreteria presente nella SIM."</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"Aggiungi numero"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"Modificare l\'app tastiera predefinita?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"Utilizzare <xliff:g id="NEW_APP">%1$s</xliff:g> invece di <xliff:g id="CURRENT_APP">%2$s</xliff:g> come app tastiera predefinita?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"Utilizzare <xliff:g id="NEW_APP">%s</xliff:g> come app tastiera predefinita?"</string>
</resources>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index d8bdb1e..cc82904 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -16,7 +16,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"טלפון"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"ניהול שיחות טלפון"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"טלפון"</string>
<string name="unknown" msgid="6878797917991465859">"לא ידוע"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"שיחה שלא נענתה"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"שיחות שלא נענו"</string>
@@ -35,11 +36,16 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"תגובה מהירה"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"הודעה נשלחה אל <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"בעלי המכשיר מתיר לבצע שיחות חירום בלבד"</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"חשבונות לביצוע שיחות"</string>
+ <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"ניתן לבצע רק שיחות חירום."</string>
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"לא ניתן לבצע שיחות יוצאות באמצעות האפליקציה הזו ללא ההרשאה \'טלפון\'."</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"כדי להתקשר, הזן מספר טלפון חוקי."</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"לא ניתן כעת להוסיף את השיחה."</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"השבת את מצב TTY כדי לבצע שיחות וידאו."</string>
<string name="no_vm_number" msgid="4164780423805688336">"חסר מספר של דואר קולי"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"בכרטיס ה-SIM לא מאוחסן מספר של דואר קולי."</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"הוסף מספר"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"האם לשנות את אפליקציית החייגן שבברירת מחדל?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"האם להשתמש ב-<xliff:g id="NEW_APP">%1$s</xliff:g> במקום ב-<xliff:g id="CURRENT_APP">%2$s</xliff:g> כאפליקציית החייגן שבברירת מחדל?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"האם להשתמש ב-<xliff:g id="NEW_APP">%s</xliff:g> כאפליקציית החייגן שבברירת מחדל?"</string>
</resources>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index 551f281..0a9506f 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -16,7 +16,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"電話"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"通話管理"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"電話"</string>
<string name="unknown" msgid="6878797917991465859">"通知不可能"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"不在着信"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"不在着信"</string>
@@ -35,11 +36,17 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"クイック返信"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"<xliff:g id="PHONE_NUMBER">%s</xliff:g>にメッセージを送信しました。"</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"端末の所有者に許可されているのは緊急通報のみです。"</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"通話アカウント"</string>
+ <!-- no translation found for outgoing_call_not_allowed_user_restriction (6872406278300131364) -->
+ <skip />
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"このアプリは、電話権限がないため発信できません。"</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"発信するには、有効な番号を入力してください。"</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"通話は現在追加できません。"</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"ビデオハングアウトを行うにはTTYモードを無効にしてください。"</string>
<string name="no_vm_number" msgid="4164780423805688336">"ボイスメール番号がありません"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"SIMカードにボイスメールの番号がありません。"</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"番号を追加"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"既定の電話アプリを変更しますか?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"<xliff:g id="NEW_APP">%1$s</xliff:g>を<xliff:g id="CURRENT_APP">%2$s</xliff:g>の代わりに既定の電話アプリとして使用しますか?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"<xliff:g id="NEW_APP">%s</xliff:g>を既定の電話アプリとして使用しますか?"</string>
</resources>
diff --git a/res/values-ka-rGE/strings.xml b/res/values-ka-rGE/strings.xml
index ac1c20b..50d9375 100644
--- a/res/values-ka-rGE/strings.xml
+++ b/res/values-ka-rGE/strings.xml
@@ -16,7 +16,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"ტელეფონი"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"ტელეფონის ზარების მართვა"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"ტელეფონი"</string>
<string name="unknown" msgid="6878797917991465859">"უცნობი"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"გამოტოვებული ზარი"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"გამოტოვებული ზარები"</string>
@@ -35,11 +36,17 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"სწრაფი პასუხი"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"შეტყობინება გაიგზავნა <xliff:g id="PHONE_NUMBER">%s</xliff:g>-თან."</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"მოწყობილობის მფლობელის მიერ ნებადართულია მხოლოდ საგანგებო ზარების განხორციელება"</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"დარეკვის ანგარიშები"</string>
+ <!-- no translation found for outgoing_call_not_allowed_user_restriction (6872406278300131364) -->
+ <skip />
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"ეს აპლიკაცია ტელეფონის ნებართვის გარეშე გამავალ ზარებს ვერ განახორციელებს."</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"ზარის განხორციელებისათვის, შეიყვანეთ მოქმედი ნომერი."</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"ამ ეტაპზე ზარის დამატება ვერ ხერხდება."</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"ვიდეოზარების განხორციელებისათვის, გთხოვთ, გამორთოთ TTY რეჟიმი"</string>
<string name="no_vm_number" msgid="4164780423805688336">"ხმოვანი ფოსტის ნომერი არ არის"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"SIM ბარათზე ხმოვანი ფოსტის ნომერი შენახული არ არის."</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"ნომრის დამატება"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"გსურთ ამკრეფის ნაგულისხმევი აპის შეცვლა?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"გსურთ ამკრეფის ნაგულისხმევ აპად <xliff:g id="CURRENT_APP">%2$s</xliff:g>-ის ნაცვლად <xliff:g id="NEW_APP">%1$s</xliff:g>-ის გამოყენება?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"გსურთ, გამოიყენოთ <xliff:g id="NEW_APP">%s</xliff:g>, როგორც ამკრეფის ნაგულისხმევი აპი?"</string>
</resources>
diff --git a/res/values-kk-rKZ/strings.xml b/res/values-kk-rKZ/strings.xml
index 89be55c..7c69704 100644
--- a/res/values-kk-rKZ/strings.xml
+++ b/res/values-kk-rKZ/strings.xml
@@ -16,13 +16,14 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"Телефон"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"Телефон қоңырауларын басқару"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"Телефон"</string>
<string name="unknown" msgid="6878797917991465859">"Белгісіз"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Қабылданбаған қоңырау"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Қабылданбаған қоңыраулар"</string>
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> қабылданбаған қоңыраулар"</string>
<string name="notification_missedCallTicker" msgid="504686252427747209">"<xliff:g id="MISSED_CALL_FROM">%s</xliff:g> қоңырауы қабылданбаған"</string>
- <string name="notification_missedCall_call_back" msgid="2684890353590890187">"Кері қоңырау шалу"</string>
+ <string name="notification_missedCall_call_back" msgid="2684890353590890187">"Қоңырау шалу"</string>
<string name="notification_missedCall_message" msgid="3049928912736917988">"Хабар"</string>
<string name="accessibility_call_muted" msgid="2776111226185342220">"Қоңырау үнсіздендірілген."</string>
<string name="accessibility_speakerphone_enabled" msgid="1988512040421036359">"Үндеткішті телефон қосылды."</string>
@@ -35,11 +36,16 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Жылдам жауап"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Хабар <xliff:g id="PHONE_NUMBER">%s</xliff:g> нөміріне жіберілді."</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"Құрылғы иесі тек жедел қоңырауларға рұқсат еткен"</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"Қоңырау шалу есептік жазбалары"</string>
+ <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Тек төтенше қоңырауларға рұқсат етілген."</string>
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"\"Телефон\" рұқсатынсыз бұл қолданба шығыс қоңырауларды соға алмайды."</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"Қоңырау шалу үшін жарамды нөмірді енгізіңіз."</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"Қоңырауды қазіргі уақытта қосу мүмкін емес."</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"Бейне қоңырау шалу үшін Телетайп режимін өшіріңіз."</string>
<string name="no_vm_number" msgid="4164780423805688336">"Дауыс хабарының нөмірі жоқ"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"SIM картасында ешқандай дауыс хабарының нөмірі сақталмаған."</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"Нөмір қосу"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"Әдепкі нөмір тергіш қолданбаны өзгерткіңіз келе ме?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"<xliff:g id="CURRENT_APP">%2$s</xliff:g> орнына <xliff:g id="NEW_APP">%1$s</xliff:g> қолданбасын әдепкі тергіш қолданба ретінде пайдалану қажет пе?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"<xliff:g id="NEW_APP">%s</xliff:g> қолданбасын әдепкі нөмір тергіш қолданба ретінде пайдалану қажет пе?"</string>
</resources>
diff --git a/res/values-km-rKH/strings.xml b/res/values-km-rKH/strings.xml
index 1a019e8..cc0a951 100644
--- a/res/values-km-rKH/strings.xml
+++ b/res/values-km-rKH/strings.xml
@@ -16,7 +16,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"ទូរស័ព្ទ"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"ការគ្រប់គ្រងការហៅទូរស័ព្ទ"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"ទូរស័ព្ទ"</string>
<string name="unknown" msgid="6878797917991465859">"មិនស្គាល់"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"ខកខានទទួល"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"ខកខានទទួល"</string>
@@ -35,11 +36,16 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"ឆ្លើយតបរហ័ស"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"បានផ្ញើសារទៅ <xliff:g id="PHONE_NUMBER">%s</xliff:g> ។"</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"ការហៅពេលមានអាសន្នដែលត្រូវបានអនុញ្ញាតដោយម្ចាស់ឧបករណ៍តែប៉ុណ្ណោះ។"</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"គណនីហៅទូរស័ព្ទ"</string>
+ <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"អនុញ្ញាតតែការហៅពេលមានអាសន្នប៉ុណ្ណោះ"</string>
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"កម្មវិធីនេះមិនអាចធ្វើការហៅចេញដោយគ្មានការអនុញ្ញាត ទូរស័ព្ទ បានទេ។"</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"ដើម្បីធ្វើការហៅ បញ្ចូលលេខដែលមានសុពលភាព។"</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"មិនអាចបន្ថែមនៅពេលនេះបានទេ។"</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"សូមបិទដំណើរការរបៀប TTY ដើម្បីធ្វើការហៅជាវីដេអូ។"</string>
<string name="no_vm_number" msgid="4164780423805688336">"បាត់ចំនួនសារជាសំឡេង"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"គ្មានចំនួនសារជាសំឡេងត្រូវបានរក្សាទុកនៅលើស៊ីមកាតទេ។"</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"បន្ថែមលេខ"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"ប្តូរកម្មវិធីហៅទូរស័ព្ទលំនាំដើម?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"ប្រើ <xliff:g id="NEW_APP">%1$s</xliff:g> ជំនួសឲ្យ <xliff:g id="CURRENT_APP">%2$s</xliff:g> ជាកម្មវិធីហៅទូរស័ព្ទលំនាំដើមរបស់អ្នក?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"ប្រើ <xliff:g id="NEW_APP">%s</xliff:g> ជាកម្មវិធីហៅទូរស័ព្ទលំនាំដើមរបស់អ្នក?"</string>
</resources>
diff --git a/res/values-kn-rIN/strings.xml b/res/values-kn-rIN/strings.xml
index e84afc9..37da2d4 100644
--- a/res/values-kn-rIN/strings.xml
+++ b/res/values-kn-rIN/strings.xml
@@ -16,7 +16,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"ಫೋನ್"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"ಫೋನ್ ಕರೆ ನಿರ್ವಹಣೆ"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"ಫೋನ್"</string>
<string name="unknown" msgid="6878797917991465859">"ಅಜ್ಞಾತ"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"ಮಿಸ್ಡ್ ಕಾಲ್"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"ತಪ್ಪಿದ ಕರೆಗಳು"</string>
@@ -26,20 +27,26 @@
<string name="notification_missedCall_message" msgid="3049928912736917988">"ಸಂದೇಶ"</string>
<string name="accessibility_call_muted" msgid="2776111226185342220">"ಕರೆಯನ್ನು ಮ್ಯೂಟ್ ಮಾಡಲಾಗಿದೆ."</string>
<string name="accessibility_speakerphone_enabled" msgid="1988512040421036359">"ಸ್ಪೀಕರ್ಫೋನ್ ಸಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ."</string>
- <string name="respond_via_sms_canned_response_1" msgid="2461606462788380215">"ಇದೀಗ ಮಾತನಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ. ಏನು ಸಮಾಚಾರ?"</string>
+ <string name="respond_via_sms_canned_response_1" msgid="2461606462788380215">"ಈಗ ಮಾತನಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ. ಏನು ವಿಷಯ?"</string>
<string name="respond_via_sms_canned_response_2" msgid="4074450431532859214">"ನಾನು ಮರಳಿ ನಿಮಗೆ ಕರೆ ಮಾಡುತ್ತೇನೆ."</string>
<string name="respond_via_sms_canned_response_3" msgid="3496079065723960450">"ನಾನು ನಂತರ ನಿಮಗೆ ಕರೆ ಮಾಡುತ್ತೇನೆ."</string>
- <string name="respond_via_sms_canned_response_4" msgid="1698989243040062190">"ಇದೀಗ ಮಾತನಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ. ನನಗೆ ನಂತರ ಕರೆ ಮಾಡುವಿರಾ?"</string>
+ <string name="respond_via_sms_canned_response_4" msgid="1698989243040062190">"ಈಗ ಮಾತನಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ. ನಂತರ ಮಾಡುವಿರಾ?"</string>
<string name="respond_via_sms_setting_title" msgid="3754000371039709383">"ತ್ವರಿತ ಪ್ರತಿಕ್ರಿಯೆಗಳು"</string>
- <string name="respond_via_sms_setting_title_2" msgid="6104662227299493906">"ತ್ವರಿತ ಪ್ರತಿಕ್ರಿಯೆಗಳನ್ನು ಸಂಪಾದಿಸಿ"</string>
+ <string name="respond_via_sms_setting_title_2" msgid="6104662227299493906">"ತ್ವರಿತ ಪ್ರತಿಕ್ರಿಯೆ ಸಂಪಾದಿಸಿ"</string>
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"ತ್ವರಿತ ಪ್ರತಿಕ್ರಿಯೆ"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"<xliff:g id="PHONE_NUMBER">%s</xliff:g> ಗೆ ಸಂದೇಶ ಕಳುಹಿಸಲಾಗಿದೆ."</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"ಸಾಧನದ ಮಾಲೀಕರಿಂದ ತುರ್ತು ಕರೆಗಳನ್ನು ಮಾಡಲು ಮಾತ್ರ ಅವಕಾಶವಿದೆ"</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"ಕರೆ ಮಾಡುವ ಖಾತೆಗಳು"</string>
+ <!-- no translation found for outgoing_call_not_allowed_user_restriction (6872406278300131364) -->
+ <skip />
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"ಈ ಅಪ್ಲಿಕೇಶನ್ ಫೋನ್ ಅನುಮತಿಯಿಲ್ಲದೆ ಹೊರಹೋಗುವ ಕರೆಗಳನ್ನು ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ."</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"ಕರೆಯನ್ನು ಮಾಡಲು, ಮಾನ್ಯವಾದ ಸಂಖ್ಯೆಯನ್ನು ನಮೂದಿಸಿ."</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"ಕರೆಯನ್ನು ಈ ಸಮಯದಲ್ಲಿ ಸೇರಿಸಲಾಗುವುದಿಲ್ಲ."</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"ವೀಡಿಯೊ ಕರೆಗಳನ್ನು ಮಾಡಲು TTY ಮೋಡ್ ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಿ."</string>
<string name="no_vm_number" msgid="4164780423805688336">"ಧ್ವನಿಮೇಲ್ ಸಂಖ್ಯೆಯು ಕಾಣೆಯಾಗಿದೆ"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"ಸಿಮ್ ಕಾರ್ಡ್ನಲ್ಲಿ ಯಾವುದೇ ಧ್ವನಿಮೇಲ್ ಸಂಖ್ಯೆಯನ್ನು ಸಂಗ್ರಹಿಸಿಲ್ಲ."</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"ಸಂಖ್ಯೆಯನ್ನು ಸೇರಿಸಿ"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"ಡೀಫಾಲ್ಟ್ ಡಯಲರ್ ಅಪ್ಲಿಕೇಶನ್ ಬದಲಾಯಿಸುವುದೇ?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"<xliff:g id="CURRENT_APP">%2$s</xliff:g> ಬದಲಿಗೆ <xliff:g id="NEW_APP">%1$s</xliff:g> ಅನ್ನು ನಿಮ್ಮ ಡೀಫಾಲ್ಟ್ ಡಯಲರ್ ಅಪ್ಲಿಕೇಶನ್ ಆಗಿ ಬಳಸುವುದೇ?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"<xliff:g id="NEW_APP">%s</xliff:g> ಅನ್ನು ನಿಮ್ಮ ಡೀಫಾಲ್ಟ್ ಡಯಲರ್ ಅಪ್ಲಿಕೇಶನ್ ಆಗಿ ಬಳಸುವುದೇ?"</string>
</resources>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index efb59cd..5080f15 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -16,7 +16,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"전화"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"통화 관리"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"전화"</string>
<string name="unknown" msgid="6878797917991465859">"알 수 없음"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"부재중 전화"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"부재중 통화"</string>
@@ -35,11 +36,17 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"빠른 응답"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"<xliff:g id="PHONE_NUMBER">%s</xliff:g>(으)로 메시지를 보냈습니다."</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"기기 소유자만 긴급 전화를 사용할 수 있습니다."</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"통화 계정"</string>
+ <!-- no translation found for outgoing_call_not_allowed_user_restriction (6872406278300131364) -->
+ <skip />
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"전화 권한이 없으므로 애플리케이션에서 발신 전화를 걸 수 없습니다."</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"전화를 걸려면 올바른 번호를 입력하세요."</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"현재 통화를 추가할 수 없습니다."</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"화상 통화를 하려면 TTY 모드를 중지하세요."</string>
<string name="no_vm_number" msgid="4164780423805688336">"음성사서함 번호 없음"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"SIM 카드에 저장된 음성사서함 번호가 없습니다."</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"번호 추가"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"기본 다이얼러 앱을 변경하시겠습니까?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"<xliff:g id="CURRENT_APP">%2$s</xliff:g> 대신 <xliff:g id="NEW_APP">%1$s</xliff:g>을(를) 기본 다이얼러 앱으로 사용하시겠습니까?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"<xliff:g id="NEW_APP">%s</xliff:g>을(를) 기본 다이얼러 앱으로 사용하시겠습니까?"</string>
</resources>
diff --git a/res/values-ky-rKG/strings.xml b/res/values-ky-rKG/strings.xml
index 6b5e9c3..a65db25 100644
--- a/res/values-ky-rKG/strings.xml
+++ b/res/values-ky-rKG/strings.xml
@@ -16,42 +16,37 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"Телефон"</string>
- <!-- no translation found for unknown (6878797917991465859) -->
- <skip />
- <!-- no translation found for notification_missedCallTitle (7554385905572364535) -->
- <skip />
- <!-- no translation found for notification_missedCallsTitle (1361677948941502522) -->
- <skip />
- <!-- no translation found for notification_missedCallsMsg (4575787816055205600) -->
- <skip />
- <!-- no translation found for notification_missedCallTicker (504686252427747209) -->
- <skip />
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"Телефон чалууларын башкаруу"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"Телефон"</string>
+ <string name="unknown" msgid="6878797917991465859">"Белгисиз"</string>
+ <string name="notification_missedCallTitle" msgid="7554385905572364535">"Кабыл алынбаган чалуу"</string>
+ <string name="notification_missedCallsTitle" msgid="1361677948941502522">"Кабыл алынбаган чалуулар"</string>
+ <string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> кабыл алынбаган чалуу"</string>
+ <string name="notification_missedCallTicker" msgid="504686252427747209">"<xliff:g id="MISSED_CALL_FROM">%s</xliff:g> дегенден кабыл алынбаган чалуу"</string>
<string name="notification_missedCall_call_back" msgid="2684890353590890187">"Кайра чалуу"</string>
<string name="notification_missedCall_message" msgid="3049928912736917988">"Билдирүү"</string>
<string name="accessibility_call_muted" msgid="2776111226185342220">"Чалуу үнсүз тартипте."</string>
<string name="accessibility_speakerphone_enabled" msgid="1988512040421036359">"Динамик иштеп жатат."</string>
- <!-- no translation found for respond_via_sms_canned_response_1 (2461606462788380215) -->
- <skip />
- <!-- no translation found for respond_via_sms_canned_response_2 (4074450431532859214) -->
- <skip />
- <!-- no translation found for respond_via_sms_canned_response_3 (3496079065723960450) -->
- <skip />
- <!-- no translation found for respond_via_sms_canned_response_4 (1698989243040062190) -->
- <skip />
+ <string name="respond_via_sms_canned_response_1" msgid="2461606462788380215">"Азыр сүйлөшө албайм. Эмне болду?"</string>
+ <string name="respond_via_sms_canned_response_2" msgid="4074450431532859214">"Кайра чалам."</string>
+ <string name="respond_via_sms_canned_response_3" msgid="3496079065723960450">"Кийинчерээк чалып коём."</string>
+ <string name="respond_via_sms_canned_response_4" msgid="1698989243040062190">"Сүйлөшө албайм. Кийин чаласызбы?"</string>
<string name="respond_via_sms_setting_title" msgid="3754000371039709383">"Тез жооптор"</string>
<string name="respond_via_sms_setting_title_2" msgid="6104662227299493906">"Тез жоопторду өзгөртүү"</string>
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Тез жооп"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"<xliff:g id="PHONE_NUMBER">%s</xliff:g> номуруна билдирүү жөнөтүлдү."</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"Түзмөк ээси шашылыш чалууларга гана уруксат берген"</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"Чалуу каттоо эсептери"</string>
+ <!-- no translation found for outgoing_call_not_allowed_user_restriction (6872406278300131364) -->
+ <skip />
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Бул колдонмо тийиштүү уруксатсыз чалууларды жасай албайт."</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"Чалуу үчүн, жарактуу номер киргизиңиз."</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"Бул жолу чалууну кошуу мүмкүн эмес."</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"Видео чалууларды аткаруу үчүн Телетайп түзмөк режимин өчүрүңүз."</string>
- <!-- no translation found for no_vm_number (4164780423805688336) -->
- <skip />
- <!-- no translation found for no_vm_number_msg (1300729501030053828) -->
- <skip />
- <!-- no translation found for add_vm_number_str (4676479471644687453) -->
- <skip />
+ <string name="no_vm_number" msgid="4164780423805688336">"Үн почтасынын номери жок болуп жатат"</string>
+ <string name="no_vm_number_msg" msgid="1300729501030053828">"SIM-картада сакталган үн почтасынын номери жок."</string>
+ <string name="add_vm_number_str" msgid="4676479471644687453">"Номер кошуу"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"Демейки номер тергич колдонмо өзгөрүлсүнбү?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"Демейки номер тергич колдонмо катары мурунку <xliff:g id="CURRENT_APP">%2$s</xliff:g> колдонмонун ордуна <xliff:g id="NEW_APP">%1$s</xliff:g> бул колдонмо колдонулсунбу?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"Демейки номер тергич колдонмо катары <xliff:g id="NEW_APP">%s</xliff:g> колдонулсунбу?"</string>
</resources>
diff --git a/res/values-lo-rLA/strings.xml b/res/values-lo-rLA/strings.xml
index 52479c3..6c41d65 100644
--- a/res/values-lo-rLA/strings.xml
+++ b/res/values-lo-rLA/strings.xml
@@ -16,7 +16,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"ໂທລະສັບ"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"ການຈັດການການໂທລະສັບ"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"ໂທລະສັບ"</string>
<string name="unknown" msgid="6878797917991465859">"ບໍ່ຮູ້ຈັກ"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"ສາຍທີ່ບໍ່ໄດ້ຮັບ"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"ສາຍທີ່ບໍ່ໄດ້ຮັບ"</string>
@@ -35,11 +36,17 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"ຕອບກັບດ່ວນ"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"ສົ່ງຂໍ້ຄວາມຫາ <xliff:g id="PHONE_NUMBER">%s</xliff:g> ແລ້ວ."</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"ພຽງແຕ່ການໂທສຸກເສີນທີ່ໄດ້ຮັບອະນຸຍາດຈາກເຈົ້າຂອງອຸປະກອນເທົ່ານັ້ນ"</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"ບັນຊີໂທ"</string>
+ <!-- no translation found for outgoing_call_not_allowed_user_restriction (6872406278300131364) -->
+ <skip />
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"ແອັບພລິເຄຊັນນີ້ບໍ່ສາມາດໂທອອກໄດ້ ໂດຍບໍ່ມີການອະນຸຍາດຂອງໂທລະສັບ."</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"ເພື່ອທີ່ຈະໂທ, ປ້ອນເບີໂທທີ່ໃຊ້ໄດ້ເຂົ້າໄປ."</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"ບໍ່ສາມາດເພີ່ມການໂທໄດ້ໃນເວລານີ້."</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"ກະລຸນາປິດໃຊ້ງານໂໝດ TTY ເພື່ອໂທວິດີໂອ."</string>
<string name="no_vm_number" msgid="4164780423805688336">"ບໍ່ມີເບີຂໍ້ຄວາມສຽງ"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"ບໍ່ມີເບີຂໍ້ຄວາມສຽງຖືກບັນທຶກໃນ SIM card."</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"ເພີ່ມໝາຍເລກ"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"ປ່ຽນແປງແອັບແຜ່ນກົດມາດຕະຖານ?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"ໃຊ້ <xliff:g id="NEW_APP">%1$s</xliff:g> ແທນ <xliff:g id="CURRENT_APP">%2$s</xliff:g> ເປັນແອັບແຜ່ນກົດມາດຕະຖານຂອງທ່ານ?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"ໃຊ້ <xliff:g id="NEW_APP">%s</xliff:g> ເປັນແອັບແຜ່ນກົດມາດຕະຖານຂອງທ່ານ?"</string>
</resources>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index 8b0f066..d0b47cc 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -16,7 +16,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"Telefonas"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"Telefono skambučių tvarkymas"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"Telefonas"</string>
<string name="unknown" msgid="6878797917991465859">"Nežinomas"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Praleistas skambutis"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Praleisti skambučiai"</string>
@@ -35,11 +36,16 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Greitas atsakas"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Pranešimas išsiųstas numeriu <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"Įrenginio savininkas leidžia skambinti tik pagalbos numeriais"</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"Skambinimo paskyros"</string>
+ <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Leidžiami tik skambučiai pagalbos numeriu."</string>
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Naudojant šią programą negalima skambinti be telefono leidimo."</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"Kad galėtumėte paskambinti, įveskite tinkamą numerį."</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"Šiuo metu dar vieno skambučio atlikti negalima."</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"Išjunkite TTY režimą, kad galėtumėte atlikti vaizdo skambučius."</string>
<string name="no_vm_number" msgid="4164780423805688336">"Trūksta balso pašto numerio"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"SIM kortelėje nėra išsaugoto balso pašto numerio."</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"Pridėti numerį"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"Pakeisti numatytąją numerio rinkiklio programą?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"Naudoti „<xliff:g id="NEW_APP">%1$s</xliff:g>“ vietoje „<xliff:g id="CURRENT_APP">%2$s</xliff:g>“ kaip numatytąją numerio rinkiklio programą?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"Naudoti „<xliff:g id="NEW_APP">%s</xliff:g>“ kaip numatytąją numerio rinkiklio programą?"</string>
</resources>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index 43f0027..bac3368 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -16,7 +16,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"Tālrunis"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"Tālruņa zvanu pārvaldība"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"Tālrunis"</string>
<string name="unknown" msgid="6878797917991465859">"Nezināms"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Neatbildēts zvans"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Neatbildētie zvani"</string>
@@ -35,11 +36,17 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Ātrā atbilde"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Ziņojums nosūt. uz šādu tālr. nr.: <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"Ierīces īpašnieks ļauj veikt tikai ārkārtas zvanus."</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"Zvanu konti"</string>
+ <!-- no translation found for outgoing_call_not_allowed_user_restriction (6872406278300131364) -->
+ <skip />
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Šajā lietojumprogrammā nevar veikt izejošos zvanus bez tālruņa atļaujas."</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"Lai veiktu zvanu, ievadiet derīgu numuru."</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"Šobrīd nevar pievienot zvanu."</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"Lai veiktu videozvanus, lūdzu, atspējojiet teksta tālruņa režīmu."</string>
<string name="no_vm_number" msgid="4164780423805688336">"Trūkst balss pasta numura"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"SIM kartē neviens balss pasta numurs nav saglabāts."</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"Pievienot numuru"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"Vai mainīt numura sastādītāja noklusējuma lietotni?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"Vai lietotnes <xliff:g id="CURRENT_APP">%2$s</xliff:g> vietā izmantot <xliff:g id="NEW_APP">%1$s</xliff:g> kā numura sastādītāja noklusējuma lietotni?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"Vai izmantot <xliff:g id="NEW_APP">%s</xliff:g> kā numura sastādītāja noklusējuma lietotni?"</string>
</resources>
diff --git a/res/values-mk-rMK/strings.xml b/res/values-mk-rMK/strings.xml
index 9654e0b..3c87169 100644
--- a/res/values-mk-rMK/strings.xml
+++ b/res/values-mk-rMK/strings.xml
@@ -16,7 +16,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"Телефон"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"Управување со телефонски повици"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"Телефон"</string>
<string name="unknown" msgid="6878797917991465859">"Непознато"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Пропуштен повик"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Пропуштени повици"</string>
@@ -35,11 +36,17 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Брз одговор"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Порака е испратена на <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"Дозволени се само итни повици од страна на сопственикот на уредот"</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"Сметки за повици"</string>
+ <!-- no translation found for outgoing_call_not_allowed_user_restriction (6872406278300131364) -->
+ <skip />
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Оваа апликација не може да прави појдовни повици без дозволата Телефон."</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"За да повикате, внесете важечки број."</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"Повикот не може да се додаде во моментов."</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"Оневозможете го режимот TTY за да остварите видеоповици."</string>
<string name="no_vm_number" msgid="4164780423805688336">"Недостасува број на говорна пошта"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"Нема мемориран број на говорна пошта на СИМ картичката."</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"Додај број"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"Смени ја стандардната апликација Бирач?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"Користи <xliff:g id="NEW_APP">%1$s</xliff:g> наместо <xliff:g id="CURRENT_APP">%2$s</xliff:g> како стандардна апликација за бирање?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"Користи <xliff:g id="NEW_APP">%s</xliff:g> како стандардна апликација за бирање?"</string>
</resources>
diff --git a/res/values-ml-rIN/strings.xml b/res/values-ml-rIN/strings.xml
index 38fc0e6..7bd5029 100644
--- a/res/values-ml-rIN/strings.xml
+++ b/res/values-ml-rIN/strings.xml
@@ -16,7 +16,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"ഫോണ്"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"ഫോൺ കോൾ മാനേജ്മെന്റ്"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"ഫോണ്"</string>
<string name="unknown" msgid="6878797917991465859">"അജ്ഞാതം"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"മിസ്ഡ് കോൾ"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"മിസ്ഡ് കോളുകൾ"</string>
@@ -31,15 +32,20 @@
<string name="respond_via_sms_canned_response_3" msgid="3496079065723960450">"ഞാൻ നിങ്ങളെ പിന്നീട് വിളിക്കാം."</string>
<string name="respond_via_sms_canned_response_4" msgid="1698989243040062190">"ഇപ്പോൾ സംസാരിക്കാനാകില്ല. എന്നെ പിന്നീട് വിളിക്കാമോ?"</string>
<string name="respond_via_sms_setting_title" msgid="3754000371039709383">"ദ്രുത പ്രതികരണങ്ങൾ"</string>
- <string name="respond_via_sms_setting_title_2" msgid="6104662227299493906">"ദ്രുത പ്രതികരണങ്ങൾ എഡിറ്റുചെയ്യുക"</string>
+ <string name="respond_via_sms_setting_title_2" msgid="6104662227299493906">"പ്രതികരണം എഡിറ്റുചെയ്യൂ"</string>
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"ദ്രുത പ്രതികരണം"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"<xliff:g id="PHONE_NUMBER">%s</xliff:g> എന്നതിലേക്ക് സന്ദേശമയച്ചു."</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"ഉപകരണ ഉടമ അടിയന്തിര കോളുകൾ മാത്രമേ അനുവദിച്ചിട്ടുള്ളൂ"</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"കോളിംഗ് അക്കൗണ്ട്"</string>
+ <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"അടിയന്തിര കോളുകൾ മാത്രമേ അനുവദിച്ചിട്ടുള്ളൂ."</string>
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"ഫോൺ അനുമതിയില്ലാതെ ഈ അപ്ലിക്കേഷന് ഔട്ട്ഗോയിംഗ് കോളുകൾ വിളിക്കാൻ കഴിയില്ല."</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"ഒരു കോൾ ചെയ്യുന്നതിന്, സാധുതയുള്ള നമ്പർ നൽകുക."</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"കോൾ ഇപ്പോൾ ചേർക്കാനാകില്ല."</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"വീഡിയോ കോളുകൾ നടത്താൻ TTY മോഡ് പ്രവർത്തനരഹിതമാക്കുക."</string>
<string name="no_vm_number" msgid="4164780423805688336">"വോയ്സ്മെയിൽ നമ്പർ കാണുന്നില്ല"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"സിം കാർഡിൽ വോയ്സ്മെയിൽ നമ്പറൊന്നും സംഭരിച്ചിട്ടില്ല."</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"നമ്പർ ചേർക്കുക"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"സ്ഥിര ഡയലർ ആപ്പ് മാറ്റണോ?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"<xliff:g id="CURRENT_APP">%2$s</xliff:g> എന്നതിനുപകരം <xliff:g id="NEW_APP">%1$s</xliff:g> എന്നതിനെ നിങ്ങളുടെ സ്ഥിര ഡയലർ ആപ്പ് ആയി ഉപയോഗിക്കണോ?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"<xliff:g id="NEW_APP">%s</xliff:g> എന്നതിനെ നിങ്ങളുടെ സ്ഥിര ഡയലർ ആപ്പ് ആയി ഉപയോഗിക്കണോ?"</string>
</resources>
diff --git a/res/values-mn-rMN/strings.xml b/res/values-mn-rMN/strings.xml
index 828f347..1ecabc4 100644
--- a/res/values-mn-rMN/strings.xml
+++ b/res/values-mn-rMN/strings.xml
@@ -16,7 +16,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"Утас"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"Гар утасны Дуудлагын Удирдлага"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"Утас"</string>
<string name="unknown" msgid="6878797917991465859">"Тодорхойгүй"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Аваагүй дуудлага"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Аваагүй дуудлагууд"</string>
@@ -35,11 +36,17 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Шуурхай хариу"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Зурвасыг <xliff:g id="PHONE_NUMBER">%s</xliff:g> руу илгээв."</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"Энэхүү төхөөрөмжийн эзэмшигч нь зөвхөн түргэн тусламжийн дуудлага хийх эрхтэй байна."</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"Дуудлагын эрхтэй бүртгэлүүд"</string>
+ <!-- no translation found for outgoing_call_not_allowed_user_restriction (6872406278300131364) -->
+ <skip />
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Энэ апп нь утасны зөвшөөрөлгүйгээр дуудлага хийх боломжгүй."</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"Дуудлага хийхийн тулд хүчин төгөлдөр дугаар оруулна уу."</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"Одоо дуудлага нэмэх боломжгүй."</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"Видео дуудлага хийхийн тулд TTY горимыг идэвхгүй болгоно уу."</string>
<string name="no_vm_number" msgid="4164780423805688336">"Дуут шуудангийн дугаар байхгүй"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"SIM карт дээр дуут шуудангийн дугаар хадгалагдаагүй байна."</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"Дугаар нэмэх"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"Үндсэн залгагч апп-ыг өөрчлөх үү?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"<xliff:g id="NEW_APP">%1$s</xliff:g>-ыг <xliff:g id="CURRENT_APP">%2$s</xliff:g>-ын оронд таны үндсэн залгагч апп болгон тохируулах уу?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"<xliff:g id="NEW_APP">%s</xliff:g>-ыг таны үндсэн залгагч апп болгон тохируулах уу?"</string>
</resources>
diff --git a/res/values-mr-rIN/strings.xml b/res/values-mr-rIN/strings.xml
index c7c795c..c361736 100644
--- a/res/values-mr-rIN/strings.xml
+++ b/res/values-mr-rIN/strings.xml
@@ -16,7 +16,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"फोन"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"फोन कॉल व्यवस्थापन"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"फोन"</string>
<string name="unknown" msgid="6878797917991465859">"अज्ञात"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"सुटलेला कॉल"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"सुटलेले कॉल"</string>
@@ -26,20 +27,25 @@
<string name="notification_missedCall_message" msgid="3049928912736917988">"संदेश"</string>
<string name="accessibility_call_muted" msgid="2776111226185342220">"कॉल नि.शब्द केला."</string>
<string name="accessibility_speakerphone_enabled" msgid="1988512040421036359">"स्पीकरफोन सक्षम केला."</string>
- <string name="respond_via_sms_canned_response_1" msgid="2461606462788380215">"आता बोलू शकत नाही. काय चालले आहे?"</string>
+ <string name="respond_via_sms_canned_response_1" msgid="2461606462788380215">"आत्ता बोलू शकत नाही. काय चालले आहे?"</string>
<string name="respond_via_sms_canned_response_2" msgid="4074450431532859214">"मी आपल्याला परत कॉल करेन."</string>
<string name="respond_via_sms_canned_response_3" msgid="3496079065723960450">"मी आपल्याला नंतर कॉल करेन."</string>
- <string name="respond_via_sms_canned_response_4" msgid="1698989243040062190">"आता बोलू शकत नाही. मला नंतर कॉल कराल?"</string>
+ <string name="respond_via_sms_canned_response_4" msgid="1698989243040062190">"आत्ता बोलू शकत नाही. नंतर कॉल करा?"</string>
<string name="respond_via_sms_setting_title" msgid="3754000371039709383">"द्रुत प्रतिसाद"</string>
<string name="respond_via_sms_setting_title_2" msgid="6104662227299493906">"द्रुत प्रतिसाद संपादित करा"</string>
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"द्रुत प्रतिसाद"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"संदेश <xliff:g id="PHONE_NUMBER">%s</xliff:g> वर पाठविला."</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"डिव्हाइस मालकाद्वारे केवळ आणीबाणी कॉलना अनुमती आहे"</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"कॉल करण्याची खाती"</string>
+ <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"फक्त आणीबाणी कॉल करण्याची परवानगी आहे."</string>
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"हा अनुप्रयोग फोन परवानगी शिवाय कॉल करू शकत नाही."</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"कॉल करण्यासाठी, एक वैध नंबर प्रविष्ट करा."</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"यावेळी कॉल जोडला जाऊ शकत नाही."</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"कृपया व्हिडिओ कॉल करण्यासाठी TTY मोड अक्षम करा."</string>
<string name="no_vm_number" msgid="4164780423805688336">"व्हॉइसमेल नंबर गहाळ"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"सिम कार्डवर कोणताही व्हॉइसमेल नंबर संचयित केला नाही."</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"नंबर जोडा"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"डीफॉल्ट डायलर अॅप बदलायचा?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"आपला डीफॉल्ट डायलर अॅप म्हणून <xliff:g id="CURRENT_APP">%2$s</xliff:g> ऐवजी <xliff:g id="NEW_APP">%1$s</xliff:g> वापरायचा?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"आपला डीफॉल्ट डायलर अॅप म्हणून <xliff:g id="NEW_APP">%s</xliff:g> वापरायचा?"</string>
</resources>
diff --git a/res/values-ms-rMY/strings.xml b/res/values-ms-rMY/strings.xml
index dc15ef3..d75cf36 100644
--- a/res/values-ms-rMY/strings.xml
+++ b/res/values-ms-rMY/strings.xml
@@ -16,7 +16,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"Telefon"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"Pengurusan Panggilan Telefon"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"Telefon"</string>
<string name="unknown" msgid="6878797917991465859">"Tidak diketahui"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Panggilan tidak dijawab"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Panggilan tidak dijawab"</string>
@@ -26,20 +27,26 @@
<string name="notification_missedCall_message" msgid="3049928912736917988">"Mesej"</string>
<string name="accessibility_call_muted" msgid="2776111226185342220">"Panggilan diredam."</string>
<string name="accessibility_speakerphone_enabled" msgid="1988512040421036359">"Telefon pembesar suara didayakan."</string>
- <string name="respond_via_sms_canned_response_1" msgid="2461606462788380215">"Tak boleh berckp skrg. Apa cerita?"</string>
+ <string name="respond_via_sms_canned_response_1" msgid="2461606462788380215">"Sedang sibuk. Ada apa?"</string>
<string name="respond_via_sms_canned_response_2" msgid="4074450431532859214">"Saya akn segera hubungi awak nanti."</string>
<string name="respond_via_sms_canned_response_3" msgid="3496079065723960450">"Saya akan hubungi awak kemudian."</string>
- <string name="respond_via_sms_canned_response_4" msgid="1698989243040062190">"Tdk blh berckp skg. Tel saya nanti?"</string>
+ <string name="respond_via_sms_canned_response_4" msgid="1698989243040062190">"Sedang sibuk. Telefon saya nanti?"</string>
<string name="respond_via_sms_setting_title" msgid="3754000371039709383">"Respons pantas"</string>
<string name="respond_via_sms_setting_title_2" msgid="6104662227299493906">"Edit respons pantas"</string>
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Respons pantas"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Mesej dihantar ke <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"Hanya panggilan kecemasan dibenarkan oleh pemilik peranti"</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"Akaun panggilan"</string>
+ <!-- no translation found for outgoing_call_not_allowed_user_restriction (6872406278300131364) -->
+ <skip />
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Aplikasi ini tidak boleh membuat panggilan keluar tanpa kebenaran Telefon."</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"Untuk membuat panggilan, masukkan nombor yang sah."</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"Panggilan tidak boleh ditambahkan pada masa ini."</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"Sila lumpuhkan Mod TTY untuk membuat panggilan video."</string>
<string name="no_vm_number" msgid="4164780423805688336">"Nombor mel suara tiada"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"Tidak ada nombor mel suara disimpan pada kad SIM."</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"Tambah nombor"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"Tukar apl Pendail lalai?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"Gunakan <xliff:g id="NEW_APP">%1$s</xliff:g> dan bukannya <xliff:g id="CURRENT_APP">%2$s</xliff:g> sebagai apl pendail lalai anda?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"Gunakan <xliff:g id="NEW_APP">%s</xliff:g> sebagai apl pendail lalai anda?"</string>
</resources>
diff --git a/res/values-my-rMM/strings.xml b/res/values-my-rMM/strings.xml
index 2bb5771..4b6a0d3 100644
--- a/res/values-my-rMM/strings.xml
+++ b/res/values-my-rMM/strings.xml
@@ -16,7 +16,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"ဖုန်း"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"ဖုန်းခေါ်ဆိုခြင်း စီမံမှု"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"ဖုန်း"</string>
<string name="unknown" msgid="6878797917991465859">"အကြောင်းအရာ မသိရှိ"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"လွဲသွားသော ဖုန်းခေါ်မှု"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"လွဲသွားသော ဖုန်းခေါ်မှုများ"</string>
@@ -35,11 +36,16 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"အမြန်တုံ့ပြန်ချက်"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"<xliff:g id="PHONE_NUMBER">%s</xliff:g> ထံ စာတိုပို့လိုက်ပါပြီ"</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"အရေးပေါ်ဖုန်းများကိုသာ ခေါ်ဆိုနိုင်ရန် စက်ကိရိယာပိုင်ရှင်က ခွင့်ပြုထား၏"</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"ခေါ်ဆိုသော အကောင့်များ"</string>
+ <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"အရေးပေါ်ခေါ်ဆိုမှုများသာ ခွင့်ပြုပါသည်။"</string>
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"ဤအပ္ပလီကေးရှင်းသည် ဖုန်းခွင့်ပြုချက်မရှိဘဲ အထွက်ခေါ်ဆိုမှု ပြုလုပ်၍မရပါ။"</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"ဖုန်းခေါ်ရန်အတွက်၊ သင့်လျော်သည့်နံပါတ် ရိုက်ထည့်ပါ။"</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"ဗွီဒီယိုခေါ်နေစဉ် ထပ်ခေါ်မရပါ။"</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"ဗီဒီယိုခေါ်ဆိုမှုများ ပြုလုပ်ရန် ကျေးဇူးပြု၍ TTY မုဒ်ကို ပိတ်ထားပါ။"</string>
<string name="no_vm_number" msgid="4164780423805688336">"အသံစာပို့စနစ် နံပါတ် ပျောက်နေပါသည်"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"ဆင်းမ်ကဒ်ပေါ်တွင် အသံစာပို့စနစ် နံပါတ် သိမ်းဆည်ထားခြင်း မရှိပါ"</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"နံပါတ်ထပ်ထည့်ရန်"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"စက်ရုံထုတ်ဖုန်းခေါ်အပ်ဖ်ကိုပြောင်းလိုပါသလား"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"<xliff:g id="CURRENT_APP">%2$s</xliff:g> အစား <xliff:g id="NEW_APP">%1$s</xliff:g> ကိုသင့်ရဲ့ စက်ရုံထုတ်ဖုန်းခေါ်အပ်ဖ်အဖြစ် သုံးလိုပါသလား"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"<xliff:g id="NEW_APP">%s</xliff:g> ကိုသင့်ရဲ့စက်ရုံထုတ်ဖုန်းခေါ်အပ်ဖ်အဖြစ် သုံးလိုပါသလား"</string>
</resources>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index c28a01d..7f37bf0 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -16,7 +16,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"Telefon"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"Administrering av telefonsamtaler"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"Telefon"</string>
<string name="unknown" msgid="6878797917991465859">"Ukjent"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Tapt anrop"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Tapte anrop"</string>
@@ -27,19 +28,25 @@
<string name="accessibility_call_muted" msgid="2776111226185342220">"Samtalelyd er kuttet."</string>
<string name="accessibility_speakerphone_enabled" msgid="1988512040421036359">"Høyttaler er aktivert."</string>
<string name="respond_via_sms_canned_response_1" msgid="2461606462788380215">"Kan ikke snakke nå. Hva skjer?"</string>
- <string name="respond_via_sms_canned_response_2" msgid="4074450431532859214">"Jeg ringer deg straks tilbake."</string>
+ <string name="respond_via_sms_canned_response_2" msgid="4074450431532859214">"Jeg ringer deg tilbake straks."</string>
<string name="respond_via_sms_canned_response_3" msgid="3496079065723960450">"Jeg ringer deg senere."</string>
- <string name="respond_via_sms_canned_response_4" msgid="1698989243040062190">"Ikke nå. Ringer du meg senere?"</string>
+ <string name="respond_via_sms_canned_response_4" msgid="1698989243040062190">"Kan ikke nå. Ring meg senere."</string>
<string name="respond_via_sms_setting_title" msgid="3754000371039709383">"Hurtigsvar"</string>
<string name="respond_via_sms_setting_title_2" msgid="6104662227299493906">"Rediger hurtigsvar"</string>
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Hurtigsvar"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Melding er sendt til <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"Eieren av enheten tillater bare nødanrop"</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"Ringekontoer"</string>
+ <!-- no translation found for outgoing_call_not_allowed_user_restriction (6872406278300131364) -->
+ <skip />
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Denne appen kan ikke ringe uten tillatelse fra telefonen."</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"Skriv inn et gyldig nummer for å plassere en samtale."</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"Anrop kan ikke legges til akkurat nå."</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"Slå av TTY-modus for å starte videosamtaler."</string>
- <string name="no_vm_number" msgid="4164780423805688336">"Mangler nummer til telefonsvarer"</string>
- <string name="no_vm_number_msg" msgid="1300729501030053828">"Det er ikke lagret noe telefonsvarernummer på SIM-kortet."</string>
+ <string name="no_vm_number" msgid="4164780423805688336">"Mangler nummer til talepostkasse"</string>
+ <string name="no_vm_number_msg" msgid="1300729501030053828">"Det er ikke lagret noe nummer for talepostkasse på SIM-kortet."</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"Legg til nummer"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"Vil du endre standard telefonapp?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"Vil du bruke <xliff:g id="NEW_APP">%1$s</xliff:g> i stedet for <xliff:g id="CURRENT_APP">%2$s</xliff:g> som standard telefonapp?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"Vil du bruke <xliff:g id="NEW_APP">%s</xliff:g> som standard telefonapp?"</string>
</resources>
diff --git a/res/values-ne-rNP/strings.xml b/res/values-ne-rNP/strings.xml
index 1cac5f6..3e9ebe5 100644
--- a/res/values-ne-rNP/strings.xml
+++ b/res/values-ne-rNP/strings.xml
@@ -16,7 +16,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"फोन"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"फोन कल व्यवस्थापन"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"फोन"</string>
<string name="unknown" msgid="6878797917991465859">"अज्ञात"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"छुटेका कल"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"छुटेका कल"</string>
@@ -35,11 +36,16 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"द्रुत प्रतिक्रिया"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"<xliff:g id="PHONE_NUMBER">%s</xliff:g> लाई सन्देश पठाइयो।"</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"केवल आपतकालीन कलहरू मात्र यन्त्र मालिकद्वारा अनुमति दिइएको छ"</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"कलिङ खाताहरू"</string>
+ <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"आपतकालीन कलहरू मात्र अनुमति दिइएका छन्।"</string>
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"यो अनुप्रयोगले फोनको अनुमति बिना बहिर्गमन कलहरू गर्न सक्दैन।"</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"एक कल गर्नको लागि, एक वैध नम्बर प्रविष्ट गर्नुहोस्।"</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"यस समयमा कल थप गर्न सकिँदैन।"</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"कृपया भिडियो कलहरू गर्न TTY मोड निष्क्रिय गर्नुहोस्।"</string>
<string name="no_vm_number" msgid="4164780423805688336">"भ्वाइसमेल नम्बर हराइरहेको छ"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"SIM कार्डमा कुनै पनि भ्वाइसमेल नम्बर भण्डारण भएको छैन।"</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"नम्बर थप्नुहोस्"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"पूर्वनिर्धारित डायलर अनुप्रयोग फेर्ने हो?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"<xliff:g id="CURRENT_APP">%2$s</xliff:g>को सट्टामा <xliff:g id="NEW_APP">%1$s</xliff:g>लाई पूर्वनिर्धारित डायलर अनुप्रयोगको रूपमा प्रयोग गर्ने हो?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"<xliff:g id="NEW_APP">%s</xliff:g>लाई तपाईँको पूर्वनिर्धारित डायलर अनुप्रयोगको रूपमा प्रयोग गर्ने हो?"</string>
</resources>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index b762e9a..01f7810 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -16,7 +16,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"Telefoon"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"Telefoonoproepbeheer"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"Telefoon"</string>
<string name="unknown" msgid="6878797917991465859">"Onbekend"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Gemiste oproep"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Gemiste oproepen"</string>
@@ -35,11 +36,16 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Snelle reactie"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Bericht verzonden naar <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"Alleen noodoproepen zijn toegestaan door de apparaateigenaar"</string>
- <string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"Als u wilt bellen, moet u een geldig nummer invoeren."</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"Oproepaccounts"</string>
+ <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Alleen noodoproepen zijn toegestaan."</string>
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Deze app kan geen uitgaande oproepen starten zonder telefoonrechten."</string>
+ <string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"Als je wilt bellen, moet je een geldig nummer invoeren."</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"Oproep kan momenteel niet worden toegevoegd."</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"Schakel de TTY-modus uit om videogesprekken te voeren."</string>
<string name="no_vm_number" msgid="4164780423805688336">"Voicemailnummer ontbreekt"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"Er is geen voicemailnummer op de SIM-kaart opgeslagen."</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"Nummer toevoegen"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"Standaard kiezerapp wijzigen?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"<xliff:g id="NEW_APP">%1$s</xliff:g> in plaats van <xliff:g id="CURRENT_APP">%2$s</xliff:g> gebruiken als standaard kiezerapp?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"<xliff:g id="NEW_APP">%s</xliff:g> gebruiken als standaard kiezerapp?"</string>
</resources>
diff --git a/res/values-pa-rIN/strings.xml b/res/values-pa-rIN/strings.xml
index 9e5cf0c..6e49d64 100644
--- a/res/values-pa-rIN/strings.xml
+++ b/res/values-pa-rIN/strings.xml
@@ -16,7 +16,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"ਫੋਨ"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"ਫ਼ੋਨ - ਕਾਲ ਪ੍ਰਬੰਧਨ"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"ਫ਼ੋਨ"</string>
<string name="unknown" msgid="6878797917991465859">"ਅਗਿਆਤ"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"ਮਿਸਡ ਕਾਲ"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"ਮਿਸਡ ਕਾਲਾਂ"</string>
@@ -35,11 +36,17 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"ਤਤਕਾਲ ਜਵਾਬ"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"ਸੁਨੇਹਾ <xliff:g id="PHONE_NUMBER">%s</xliff:g> ਨੂੰ ਭੇਜਿਆ ਗਿਆ।"</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"ਡਿਵਾਈਸ ਦੇ ਮਾਲਕ ਵੱਲੋਂ ਕੇਵਲ ਐਮਰਜੈਂਸੀ ਕਾਲਾਂ ਦੀ ਆਗਿਆ ਹੈ।"</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"ਕਾਲਿੰਗ ਖਾਤੇ"</string>
+ <!-- no translation found for outgoing_call_not_allowed_user_restriction (6872406278300131364) -->
+ <skip />
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"ਇਹ ਐਪਲੀਕੇਸ਼ਨ ਫੋਨ ਅਨੁਮਤੀ ਦੇ ਬਿਨਾਂ ਆਉਟਗੋਇੰਗ ਕਾਲਾਂ ਨਹੀਂ ਕਰ ਸਕਦੀ।"</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"ਇੱਕ ਕਾਲ ਕਰਨ ਲਈ, ਇੱਕ ਪ੍ਰਮਾਣਿਕ ਨੰਬਰ ਦਰਜ ਕਰੋ।"</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"ਇਸ ਵੇਲੇ ਕਾਲ ਨਹੀਂ ਜੋੜੀ ਸਕਦੀ।"</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"ਕਿਰਪਾ ਕਰਕੇ ਵੀਡੀਓ ਕਾਲਾਂ ਨੂੰ ਕਰਨ ਲਈ TTY ਮੋਡ ਅਸਮਰਥਿਤ ਕਰੋ."</string>
<string name="no_vm_number" msgid="4164780423805688336">"ਲੁਪਤ ਵੌਇਸਮੇਲ ਨੰਬਰ"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"SIM ਕਾਰਡ ਤੇ ਕੋਈ ਵੌਇਸਮੇਲ ਨੰਬਰ ਸਟੋਰ ਨਹੀਂ ਕੀਤਾ ਗਿਆ ਹੈ।"</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"ਨੰਬਰ ਜੋੜੋ"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"ਕੀ ਡਿਫ਼ੌਲਟ ਡਾਇਲਰ ਐਪ ਨੂੰ ਬਦਲਣਾ ਹੈ?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"ਕੀ <xliff:g id="CURRENT_APP">%2$s</xliff:g> ਦੀ ਬਜਾਏ <xliff:g id="NEW_APP">%1$s</xliff:g> ਨੂੰ ਆਪਣੀ ਡਿਫ਼ੌਲਟ ਡਾਇਲਰ ਐਪ ਦਾ ਉਪਯੋਗ ਕਰਨਾ ਹੈ?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"ਕੀ <xliff:g id="NEW_APP">%s</xliff:g> ਨੂੰ ਆਪਣੀ ਡਿਫ਼ੌਲਟ ਡਾਇਲਰ ਐਪ ਦਾ ਉਪਯੋਗ ਕਰਨਾ ਹੈ?"</string>
</resources>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index 515190c..4d44cb1 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -16,7 +16,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"Telefon"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"Telefon – zarządzanie połączeniami"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"Telefon"</string>
<string name="unknown" msgid="6878797917991465859">"Nieznany"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Nieodebrane połączenie"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Połączenia nieodebrane"</string>
@@ -35,11 +36,17 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Szybka odpowiedź"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Wiadomość wysłano na numer <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"Właściciel urządzenia zezwala tylko na połączenia alarmowe"</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"Konta telefoniczne"</string>
+ <!-- no translation found for outgoing_call_not_allowed_user_restriction (6872406278300131364) -->
+ <skip />
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Ta aplikacja nie może wykonywać połączeń bez uprawnienia Telefon."</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"Aby zadzwonić, wybierz prawidłowy numer."</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"Nie można w tej chwili dodać połączenia."</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"Wyłącz tryb TTY, by korzystać z rozmów wideo."</string>
<string name="no_vm_number" msgid="4164780423805688336">"Brakuje numeru poczty głosowej"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"Na karcie SIM nie ma zapisanego numeru poczty głosowej."</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"Dodaj numer"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"Zmienić domyślną aplikację telefonu?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"Zmienić <xliff:g id="CURRENT_APP">%2$s</xliff:g> na <xliff:g id="NEW_APP">%1$s</xliff:g> jako domyślną aplikację telefonu?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"Ustawić <xliff:g id="NEW_APP">%s</xliff:g> jako domyślną aplikację telefonu?"</string>
</resources>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index d16d6d1..f3ec4f8 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -16,7 +16,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"Telemóvel"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"Gestão de chamadas do telemóvel"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"Telefone"</string>
<string name="unknown" msgid="6878797917991465859">"Desconhecido"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Chamada não atendida"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Chamadas não atendidas"</string>
@@ -35,11 +36,16 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Resposta rápida"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Mensagem enviada para <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"Só são permitidas chamadas de emergência pelo proprietário do dispositivo"</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"Contas de chamadas"</string>
+ <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Apenas são permitidas chamadas de emergência."</string>
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Esta aplicação não pode fazer chamadas sem a autorização do telefone."</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"Para telefonar, introduza um número válido."</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"Não é possível adicionar a chamada neste momento."</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"Desative o modo teletipo para efetuar videochamadas."</string>
<string name="no_vm_number" msgid="4164780423805688336">"Número do correio de voz em falta"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"Não existe um número de correio de voz armazenado no cartão SIM."</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"Adicionar número"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"Alterar aplicação de Telefone predefinida?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"Utilizar <xliff:g id="NEW_APP">%1$s</xliff:g> em vez de <xliff:g id="CURRENT_APP">%2$s</xliff:g> como a aplicação de telefone predefinida?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"Utilizar <xliff:g id="NEW_APP">%s</xliff:g> como a aplicação de telefone predefinida?"</string>
</resources>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index 0c5f2dc..d167aff 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -16,7 +16,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"Telefone"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"Gerenciamento de chamadas telefônicas"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"Telefone"</string>
<string name="unknown" msgid="6878797917991465859">"Desconhecido"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Chamada perdida"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Chamadas perdidas"</string>
@@ -35,11 +36,17 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Resposta rápida"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Mensagem enviada para <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"Apenas chamadas de emergência são permitidas pelo proprietário do dispositivo"</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"Contas de chamadas"</string>
+ <!-- no translation found for outgoing_call_not_allowed_user_restriction (6872406278300131364) -->
+ <skip />
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Este aplicativo não pode fazer chamadas sem a permissão do smartphone."</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"Para realizar uma chamada, digite um número válido."</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"No momento, não é possível adicionar a chamada."</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"Desative o modo TTD para realizar vídeo chamadas."</string>
<string name="no_vm_number" msgid="4164780423805688336">"Número correio de voz ausente"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"Não há um número correio de voz armazenado no cartão SIM."</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"Adicionar número"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"Alterar o app discador padrão?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"Usar <xliff:g id="NEW_APP">%1$s</xliff:g> em vez de <xliff:g id="CURRENT_APP">%2$s</xliff:g> como seu app discador padrão?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"Usar <xliff:g id="NEW_APP">%s</xliff:g> como seu app discador padrão?"</string>
</resources>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index 090451e..c4464b8 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -16,13 +16,14 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"Telefon"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"Telefon – Gestionarea apelurilor"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"Telefon"</string>
<string name="unknown" msgid="6878797917991465859">"Necunoscut"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Apel nepreluat"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Apeluri nepreluate"</string>
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> (de) apeluri nepreluate"</string>
<string name="notification_missedCallTicker" msgid="504686252427747209">"Apel nepreluat de la <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
- <string name="notification_missedCall_call_back" msgid="2684890353590890187">"Sunaţi înapoi"</string>
+ <string name="notification_missedCall_call_back" msgid="2684890353590890187">"Sunați"</string>
<string name="notification_missedCall_message" msgid="3049928912736917988">"Mesaj"</string>
<string name="accessibility_call_muted" msgid="2776111226185342220">"Apel cu sunet dezactivat."</string>
<string name="accessibility_speakerphone_enabled" msgid="1988512040421036359">"Difuzor activat."</string>
@@ -31,15 +32,21 @@
<string name="respond_via_sms_canned_response_3" msgid="3496079065723960450">"Te sun mai târziu."</string>
<string name="respond_via_sms_canned_response_4" msgid="1698989243040062190">"Nu pot acum. Vorbim mai târziu?"</string>
<string name="respond_via_sms_setting_title" msgid="3754000371039709383">"Răspunsuri rapide"</string>
- <string name="respond_via_sms_setting_title_2" msgid="6104662227299493906">"Editaţi răspunsurile rapide"</string>
+ <string name="respond_via_sms_setting_title_2" msgid="6104662227299493906">"Editați răspunsurile rapide"</string>
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Răspuns rapid"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Mesajul a fost trimis la <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"Proprietarul dispozitivului permite numai apelurile de urgență"</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"Conturi pentru apelare"</string>
+ <!-- no translation found for outgoing_call_not_allowed_user_restriction (6872406278300131364) -->
+ <skip />
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Această aplicație nu poate efectua apeluri fără permisiunea Telefon."</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"Pentru a apela, introduceți un număr valid."</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"Apelul nu poate fi adăugat în acest moment."</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"Dezactivați modul TTY pentru a iniția apeluri video."</string>
<string name="no_vm_number" msgid="4164780423805688336">"Lipseşte numărul mesageriei vocale"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"Niciun număr de mesagerie vocală nu este stocat pe cardul SIM."</string>
- <string name="add_vm_number_str" msgid="4676479471644687453">"Adăugaţi numărul"</string>
+ <string name="add_vm_number_str" msgid="4676479471644687453">"Adăugați numărul"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"Schimbați aplicația Telefon prestabilită?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"Folosiți <xliff:g id="NEW_APP">%1$s</xliff:g> și nu <xliff:g id="CURRENT_APP">%2$s</xliff:g> ca aplicație de telefonie prestabilită?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"Folosiți <xliff:g id="NEW_APP">%s</xliff:g> ca aplicație de telefonie prestabilită?"</string>
</resources>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index 0145c3b..8d43a8c 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -16,7 +16,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"Телефон"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"Телефон – управление звонками"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"Телефон"</string>
<string name="unknown" msgid="6878797917991465859">"Неизвестный абонент"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Пропущенный вызов"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Пропущенные вызовы"</string>
@@ -35,11 +36,17 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Быстрый ответ"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Сообщение отправлено на номер <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"Владелец устройства разрешил только экстренные вызовы"</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"Аккаунты для звонков"</string>
+ <!-- no translation found for outgoing_call_not_allowed_user_restriction (6872406278300131364) -->
+ <skip />
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Приложение не может совершать звонки без соответствующего разрешения."</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"Недействительный номер."</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"Невозможно позвонить в данный момент"</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"Чтобы делать видеозвонки, отключите режим телетайпа"</string>
<string name="no_vm_number" msgid="4164780423805688336">"Не указан номер голосовой почты"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"На SIM-карте нет ни одного номера голосовой почты."</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"Добавить номер"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"Новое приложение для звонков"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"\"<xliff:g id="NEW_APP">%1$s</xliff:g>\" станет приложением для звонков по умолчанию вместо \"<xliff:g id="CURRENT_APP">%2$s</xliff:g>\". Продолжить?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"\"<xliff:g id="NEW_APP">%s</xliff:g>\" станет приложением для звонков по умолчанию. Продолжить?"</string>
</resources>
diff --git a/res/values-si-rLK/strings.xml b/res/values-si-rLK/strings.xml
index b8f7bc4..9c21e26 100644
--- a/res/values-si-rLK/strings.xml
+++ b/res/values-si-rLK/strings.xml
@@ -16,7 +16,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"දුරකථනය"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"දුරකථන ඇමතුම් කළමනාකරණය"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"දුරකථනය"</string>
<string name="unknown" msgid="6878797917991465859">"නොදනී"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"මඟ හැරුණු ඇමතුම"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"මඟ හැරුණු ඇමතුම්"</string>
@@ -35,11 +36,17 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"ක්ෂණික ප්රතිචාරය"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"<xliff:g id="PHONE_NUMBER">%s</xliff:g> හට පණිවිඩය යවන්න."</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"උපාංගයේ හිමිකරු විසින් හදිස්සි ඇමතුම් වලට පමණක් අවසර දෙයි"</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"ඇමතීමේ ගිණුම්"</string>
+ <!-- no translation found for outgoing_call_not_allowed_user_restriction (6872406278300131364) -->
+ <skip />
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"දුරකථන අවසරයෙන් තොරව මෙම යෙදුමට පිටතට යන ඇමතුම් සිදු කළ නොහැකිය."</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"ඇමතුමක් ලබාගැනීමට, වලංගු අංකයක් ලබාගන්න."</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"මේ වේලාවේ ඇමතුම එකතු කළ නොහැක."</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"වීඩියෝ ඇමතුම් ලබා ගැනීමට කරුණාකර TTY මෝඩය අබල කරන්න."</string>
<string name="no_vm_number" msgid="4164780423805688336">"හඬ තැපැල් අංකය නැත"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"SIM කාඩ් පතෙහි හඬ තැපැල් අංකයක් ආචිත වී නැත."</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"අංකයක් එක් කරන්න"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"සුපුරුදු දුරකථන යෙදුම වෙනස් කරන්න ද?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"<xliff:g id="CURRENT_APP">%2$s</xliff:g> වෙනුවට <xliff:g id="NEW_APP">%1$s</xliff:g> ඔබගේ සුපුරුදු දුරකථන යෙදුම ලෙස භාවිතා කරන්නද?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"<xliff:g id="NEW_APP">%s</xliff:g> ඔබගේ සුපුරුදු දුරකථන යෙදුම ලෙස භාවිතා කරන්නද?"</string>
</resources>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index 030d517..4f1a354 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -16,14 +16,15 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"Telefón"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"Správa telefónnych hovorov"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"Telefón"</string>
<string name="unknown" msgid="6878797917991465859">"Neznáme"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Zmeškaný hovor"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Zmeškané hovory"</string>
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"Zmeškané hovory: <xliff:g id="NUM_MISSED_CALLS">%s</xliff:g>."</string>
<string name="notification_missedCallTicker" msgid="504686252427747209">"Zmeškaný hovor od volajúceho <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
- <string name="notification_missedCall_call_back" msgid="2684890353590890187">"Zavolať späť"</string>
- <string name="notification_missedCall_message" msgid="3049928912736917988">"Správa"</string>
+ <string name="notification_missedCall_call_back" msgid="2684890353590890187">"Zavolať"</string>
+ <string name="notification_missedCall_message" msgid="3049928912736917988">"Napísať"</string>
<string name="accessibility_call_muted" msgid="2776111226185342220">"Zvuk hovoru bol vypnutý."</string>
<string name="accessibility_speakerphone_enabled" msgid="1988512040421036359">"Reproduktor je povolený."</string>
<string name="respond_via_sms_canned_response_1" msgid="2461606462788380215">"Teraz nemôžem hovoriť, o čo ide?"</string>
@@ -35,11 +36,16 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Rýchla odpoveď"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Správa bola odoslaná na číslo <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"Vlastník zariadenia povolil iba tiesňové volania."</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"Telefónne účty"</string>
+ <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Povolené sú len tiesňové volania."</string>
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"V tejto aplikácii nie je možné uskutočňovať odchádzajúce hovory bez povolenia Telefón"</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"Ak chcete volať, zadajte platné číslo"</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"Hovor momentálne nie je možné pridať."</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"Ak chcete uskutočňovať videohovory, deaktivujte režim TTY."</string>
<string name="no_vm_number" msgid="4164780423805688336">"Chýba číslo hlasovej schránky"</string>
- <string name="no_vm_number_msg" msgid="1300729501030053828">"Na karte SIM nie je uložené žiadne číslo hlasovej schránky."</string>
+ <string name="no_vm_number_msg" msgid="1300729501030053828">"Na SIM karte nie je uložené žiadne číslo hlasovej schránky."</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"Pridať číslo"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"Chcete zmeniť predvolenú aplikáciu vytáčania?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"Chcete použiť aplikáciu <xliff:g id="NEW_APP">%1$s</xliff:g> namiesto aplikácie <xliff:g id="CURRENT_APP">%2$s</xliff:g> ako predvolenú aplikáciu vytáčania?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"Chcete použiť aplikáciu <xliff:g id="NEW_APP">%s</xliff:g> ako predvolenú aplikáciu vytáčania?"</string>
</resources>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index 51f9de5..c8036ee 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -16,7 +16,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"Telefon"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"Upravljanje telefonskih klicev"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"Telefon"</string>
<string name="unknown" msgid="6878797917991465859">"Neznano"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Neodgovorjeni klic"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Neodgovorjeni klici"</string>
@@ -35,11 +36,17 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Hiter odgovor"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"SMS poslan na številko <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"Lastnik naprave dovoljuje samo opravljanje klicev v sili"</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"Računi za klicanje"</string>
+ <!-- no translation found for outgoing_call_not_allowed_user_restriction (6872406278300131364) -->
+ <skip />
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Ta aplikacija lahko opravlja odhodne klice brez dovoljenja za telefon."</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"Če želite opraviti klic, vnesite veljavno številko."</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"Klica trenutno ni mogoče dodati."</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"Če želite opravljati videoklice, onemogočite način TTY."</string>
<string name="no_vm_number" msgid="4164780423805688336">"Manjkajoča številka glasovne pošte"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"Na kartici SIM ni shranjena številka glasovne pošte."</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"Dodaj številko"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"Želite spremeniti privzeto aplikacijo za klicanje?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"Želite <xliff:g id="NEW_APP">%1$s</xliff:g> uporabljati kot privzeto aplikacijo za klicanje namesto <xliff:g id="CURRENT_APP">%2$s</xliff:g>?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"Želite <xliff:g id="NEW_APP">%s</xliff:g> uporabljati kot privzeto aplikacijo za klicanje?"</string>
</resources>
diff --git a/res/values-sq-rAL/strings.xml b/res/values-sq-rAL/strings.xml
index 75361a8..aa6b3c4 100644
--- a/res/values-sq-rAL/strings.xml
+++ b/res/values-sq-rAL/strings.xml
@@ -16,13 +16,14 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"Telefoni"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"Menaxhimi i telefonatave"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"Telefoni"</string>
<string name="unknown" msgid="6878797917991465859">"I panjohur"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Telefonatë e humbur"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Telefonata të humbura"</string>
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> telefonata të humbura"</string>
<string name="notification_missedCallTicker" msgid="504686252427747209">"Telefonatë e humbur nga <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
- <string name="notification_missedCall_call_back" msgid="2684890353590890187">"Ri-telefono"</string>
+ <string name="notification_missedCall_call_back" msgid="2684890353590890187">"Telefono"</string>
<string name="notification_missedCall_message" msgid="3049928912736917988">"Mesazh"</string>
<string name="accessibility_call_muted" msgid="2776111226185342220">"Telefonata kaloi në heshtje."</string>
<string name="accessibility_speakerphone_enabled" msgid="1988512040421036359">"Altoparlanti u aktivizua."</string>
@@ -35,11 +36,17 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Përgjigje e shpejtë"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Mesazhi u dërgua te <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"Vetëm telefonatat e urgjencës lejohen nga zotëruesi i pajisjes"</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"Llogaritë e telefonatave"</string>
+ <!-- no translation found for outgoing_call_not_allowed_user_restriction (6872406278300131364) -->
+ <skip />
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Ky aplikacion nuk mund të kryejë telefonata dalëse pa lejen e Telefonit."</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"Për të kryer një telefonatë, fut një numër të vlefshëm."</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"Telefonata nuk mund të shtohet këtë herë."</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"Çaktivizo modalitetin TTY për të kryer telefonata me video."</string>
<string name="no_vm_number" msgid="4164780423805688336">"Mungon numri i postës zanore"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"Nuk ka numër të ruajtur në kartën SIM."</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"Shto numër"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"Të ndryshohet apl. parazgjedhur i formuesit të numrave?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"Të përdoret <xliff:g id="NEW_APP">%1$s</xliff:g> në vend të <xliff:g id="CURRENT_APP">%2$s</xliff:g> si aplikacioni i parazgjedhur i formuesit të numrave?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"Të përdoret <xliff:g id="NEW_APP">%s</xliff:g> si aplikacioni i parazgjedhur i formuesit të numrave?"</string>
</resources>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index 36d967c..ad9e9e3 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -16,7 +16,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"Телефон"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"Управљање телефонским позивима"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"Телефон"</string>
<string name="unknown" msgid="6878797917991465859">"Непознато"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Пропуштен позив"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Пропуштени позиви"</string>
@@ -26,7 +27,7 @@
<string name="notification_missedCall_message" msgid="3049928912736917988">"Порука"</string>
<string name="accessibility_call_muted" msgid="2776111226185342220">"Звук позива је искључен."</string>
<string name="accessibility_speakerphone_enabled" msgid="1988512040421036359">"Спикерфон је омогућен."</string>
- <string name="respond_via_sms_canned_response_1" msgid="2461606462788380215">"У гужви сам. Шта је у питању?"</string>
+ <string name="respond_via_sms_canned_response_1" msgid="2461606462788380215">"У гужви сам. О чему се ради?"</string>
<string name="respond_via_sms_canned_response_2" msgid="4074450431532859214">"Позваћу те ускоро."</string>
<string name="respond_via_sms_canned_response_3" msgid="3496079065723960450">"Позваћу те касније."</string>
<string name="respond_via_sms_canned_response_4" msgid="1698989243040062190">"У гужви сам. Да се чујемо касније?"</string>
@@ -35,11 +36,17 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Брзи одговор"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Порука је послата на број <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"Власник уређаја је дозволио само хитне позиве"</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"Налози за позивање"</string>
+ <!-- no translation found for outgoing_call_not_allowed_user_restriction (6872406278300131364) -->
+ <skip />
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Ова апликација не може да позива без дозволе за телефонирање."</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"Да бисте упутили позив, унесите важећи број."</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"Тренутно није могуће додати позив."</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"Онемогућите TTY режим да бисте упућивали видео позиве."</string>
<string name="no_vm_number" msgid="4164780423805688336">"Недостаје број за говорну пошту"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"Није ускладиштен ниједан број говорне поште на SIM картици."</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"Додај број"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"Променити подразумевану апликацију Телефон?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"Желите ли да користите апликацију <xliff:g id="NEW_APP">%1$s</xliff:g> уместо апликације <xliff:g id="CURRENT_APP">%2$s</xliff:g> као подразумевану апликацију за позивање телефонских бројева?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"Желите ли да користите апликацију <xliff:g id="NEW_APP">%s</xliff:g> као подразумевану апликацију за позивање телефонских бројева?"</string>
</resources>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index a2fa42d..6bbdeff 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -16,14 +16,15 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"Telefon"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"Samtalshantering"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"Telefon"</string>
<string name="unknown" msgid="6878797917991465859">"Okänd"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Missat samtal"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Missade samtal"</string>
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> missade samtal"</string>
<string name="notification_missedCallTicker" msgid="504686252427747209">"Missat samtal från <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
<string name="notification_missedCall_call_back" msgid="2684890353590890187">"Ring upp"</string>
- <string name="notification_missedCall_message" msgid="3049928912736917988">"Meddelande"</string>
+ <string name="notification_missedCall_message" msgid="3049928912736917988">"Sms:a"</string>
<string name="accessibility_call_muted" msgid="2776111226185342220">"Samtalets ljud avstängt."</string>
<string name="accessibility_speakerphone_enabled" msgid="1988512040421036359">"Högtalartelefon aktiverad."</string>
<string name="respond_via_sms_canned_response_1" msgid="2461606462788380215">"Kan inte prata nu. Läget?"</string>
@@ -35,11 +36,16 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Snabbsvar"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Meddelandet har skickats till <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"Endast nödsamtal är tillåtna av enhetens ägare"</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"Konton för samtal"</string>
+ <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Det går bara att ringa nödsamtal."</string>
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Den här appen kan inte göra utgående samtal utan behörigheten Telefon."</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"Ange ett giltigt nummer om du vill ringa ett samtal."</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"Det går inte att lägga till samtalet just nu."</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"Inaktivera texttelefonläget om du vill ringa videosamtal."</string>
<string name="no_vm_number" msgid="4164780423805688336">"Nummer till röstbrevlåda saknas"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"Det finns inget nummer till röstbrevlådan sparat på SIM-kortet."</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"Lägg till nummer"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"Vill du byta standardapp för uppringning?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"Vill du använda <xliff:g id="NEW_APP">%1$s</xliff:g> i stället för <xliff:g id="CURRENT_APP">%2$s</xliff:g> som standardapp för uppringning?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"Vill du använda <xliff:g id="NEW_APP">%s</xliff:g> som standardapp för uppringning?"</string>
</resources>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index 9e71c93..6298b4f 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -16,7 +16,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"Nambari ya simu"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"Usimamizi wa Mazunguzo ya Simu"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"Simu"</string>
<string name="unknown" msgid="6878797917991465859">"Haijulikani"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Simu isiyojibiwa"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Simu zisizojibiwa"</string>
@@ -35,11 +36,17 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Majibu ya haraka"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Ujumbe uliotumwa kwa <xliff:g id="PHONE_NUMBER">%s</xliff:g> ."</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"Ni simu za dharura pekee zinazoruhusiwa na mmiliki wa kifaa"</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"Akaunti za simu"</string>
+ <!-- no translation found for outgoing_call_not_allowed_user_restriction (6872406278300131364) -->
+ <skip />
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Programu hii haiwezi kupiga simu bila ruhusa ya Simu."</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"Ili upige simu, weka nambari sahihi."</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"Hangout ya video haiwezi kuongezwa kwa wakati huu."</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"Tafadhali zima Hali ya TTY ili uanzishe Hangout ya video."</string>
<string name="no_vm_number" msgid="4164780423805688336">"Nambari ya sauti inayokosekana"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"Hakuna nambari ya ujumbe wa sauti iliyohifadhiwa katika SIM kadi."</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"Ongeza nambari"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"Ungependa kubadilisha programu chaguo-msingi ya simu?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"Ungependa kutumia <xliff:g id="NEW_APP">%1$s</xliff:g> badala ya <xliff:g id="CURRENT_APP">%2$s</xliff:g> kama programu ya chaguo-msingi ya kupigia simu?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"Ungependa kutumia <xliff:g id="NEW_APP">%s</xliff:g> kama programu ya chaguo-msingi ya kupigia simu?"</string>
</resources>
diff --git a/res/values-ta-rIN/strings.xml b/res/values-ta-rIN/strings.xml
index 190cd0c..28f0b25 100644
--- a/res/values-ta-rIN/strings.xml
+++ b/res/values-ta-rIN/strings.xml
@@ -16,13 +16,14 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"ஃபோன்"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"ஃபோன் அழைப்பு நிர்வாகம்"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"ஃபோன்"</string>
<string name="unknown" msgid="6878797917991465859">"தெரியாதவர்"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"தவறிய அழைப்பு"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"தவறிய அழைப்புகள்"</string>
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> தவறிய அழைப்புகள்"</string>
<string name="notification_missedCallTicker" msgid="504686252427747209">"<xliff:g id="MISSED_CALL_FROM">%s</xliff:g> இடமிருந்து தவறிய அழைப்பு"</string>
- <string name="notification_missedCall_call_back" msgid="2684890353590890187">"திருப்பி அழை"</string>
+ <string name="notification_missedCall_call_back" msgid="2684890353590890187">"அழை"</string>
<string name="notification_missedCall_message" msgid="3049928912736917988">"செய்தி"</string>
<string name="accessibility_call_muted" msgid="2776111226185342220">"அழைப்பு முடக்கப்பட்டது."</string>
<string name="accessibility_speakerphone_enabled" msgid="1988512040421036359">"ஸ்பீக்கர்ஃபோன் இயக்கப்பட்டது."</string>
@@ -35,11 +36,16 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"விரைவு பதில்"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"<xliff:g id="PHONE_NUMBER">%s</xliff:g> க்குச் செய்தி அனுப்பப்பட்டது."</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"சாதன உரிமையாளர் அவசர அழைப்புகளை மட்டுமே அனுமதித்துள்ளார்"</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"அழைப்புக் கணக்குகள்"</string>
+ <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"அவசர அழைப்புகள் மட்டுமே அனுமதிக்கப்படும்."</string>
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"ஃபோன் அனுமதியில்லாமல், பயன்பாட்டினால் வெளிச்செல்லும் அழைப்புகளைச் செய்ய முடியாது."</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"அழைக்க, சரியான எண்ணை உள்ளிடவும்."</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"இப்போது அழைக்க முடியாது."</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"வீடியோ அழைப்புகளை மேற்கொள்ள, TTY பயன்முறையை முடக்கவும்."</string>
<string name="no_vm_number" msgid="4164780423805688336">"குரலஞ்சல் எண் இல்லை"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"சிம் கார்டில் குரலஞ்சலுக்கான எண் எதுவும் சேமிக்கப்படவில்லை."</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"எண்ணைச் சேர்"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"இயல்புநிலை டயலர் பயன்பாட்டை மாற்றவா?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"<xliff:g id="NEW_APP">%1$s</xliff:g>ஐ <xliff:g id="CURRENT_APP">%2$s</xliff:g>க்குப் பதிலாக, இயல்புநிலை டயலர் பயன்பாடாகப் பயன்படுத்தவா?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"<xliff:g id="NEW_APP">%s</xliff:g>ஐ இயல்புநிலை டயலர் பயன்பாடாகப் பயன்படுத்தவா?"</string>
</resources>
diff --git a/res/values-te-rIN/strings.xml b/res/values-te-rIN/strings.xml
index ee0c5b5..32d88d7 100644
--- a/res/values-te-rIN/strings.xml
+++ b/res/values-te-rIN/strings.xml
@@ -16,13 +16,14 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"ఫోన్"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"ఫోన్ కాల్ నిర్వహణ"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"ఫోన్"</string>
<string name="unknown" msgid="6878797917991465859">"తెలియదు"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"సమాధానం ఇవ్వని కాల్"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"సమాధానం ఇవ్వని కాల్లు"</string>
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> సమాధానం ఇవ్వని కాల్లు"</string>
<string name="notification_missedCallTicker" msgid="504686252427747209">"<xliff:g id="MISSED_CALL_FROM">%s</xliff:g> నుండి సమాధానం ఇవ్వని కాల్"</string>
- <string name="notification_missedCall_call_back" msgid="2684890353590890187">"తిరిగి కాల్ చేయి"</string>
+ <string name="notification_missedCall_call_back" msgid="2684890353590890187">"కాల్ చేయి"</string>
<string name="notification_missedCall_message" msgid="3049928912736917988">"సందేశం"</string>
<string name="accessibility_call_muted" msgid="2776111226185342220">"కాల్ మ్యూట్ చేయబడింది."</string>
<string name="accessibility_speakerphone_enabled" msgid="1988512040421036359">"స్పీకర్ ఫోన్ ప్రారంభించబడింది."</string>
@@ -31,15 +32,20 @@
<string name="respond_via_sms_canned_response_3" msgid="3496079065723960450">"నేను మీకు తర్వాత కాల్ చేస్తాను."</string>
<string name="respond_via_sms_canned_response_4" msgid="1698989243040062190">"ఇప్పుడు మాట్లాడలేను. నాకు తర్వాత కాల్ చేస్తారా?"</string>
<string name="respond_via_sms_setting_title" msgid="3754000371039709383">"శీఘ్ర ప్రతిస్పందనలు"</string>
- <string name="respond_via_sms_setting_title_2" msgid="6104662227299493906">"శీఘ్ర ప్రతిస్పందనలు సవరించండి"</string>
+ <string name="respond_via_sms_setting_title_2" msgid="6104662227299493906">"శీఘ్ర ప్రతిస్పందనల సవరణ"</string>
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"శీఘ్ర ప్రతిస్పందన"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"<xliff:g id="PHONE_NUMBER">%s</xliff:g>కు సందేశం పంపబడింది."</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"పరికరం యజమాని అత్యవసర కాల్లను మాత్రమే అనుమతించారు"</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"కాలింగ్ ఖాతాలు"</string>
+ <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"కేవలం అత్యవసర కాల్లు మాత్రమే అనుమతించబడతాయి."</string>
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"ఈ అనువర్తనం ఫోన్ అనుమతి లేకుండా అవుట్గోయింగ్ కాల్లను చేయలేదు."</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"కాల్ చేయడానికి, చెల్లుబాటు అయ్యే నంబర్ను నమోదు చేయండి."</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"ఈ సమయంలో కాల్ను జోడించడం సాధ్యపడదు."</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"దయచేసి వీడియో కాల్లు చేయడానికి TTY మోడ్ నిలిపివేయండి."</string>
<string name="no_vm_number" msgid="4164780423805688336">"వాయిస్ మెయిల్ నంబర్ లేదు"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"సిమ్ కార్డులో వాయిస్ మెయిల్ నంబర్ ఏదీ నిల్వ చేయబడలేదు."</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"నంబర్ను జోడించు"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"డిఫాల్ట్ డయలర్ అనువర్తనాన్ని మార్చాలా?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"మీ డిఫాల్ట్ డయలర్ అనువర్తనంగా <xliff:g id="CURRENT_APP">%2$s</xliff:g> బదులు <xliff:g id="NEW_APP">%1$s</xliff:g>ని ఉపయోగించాలా?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"మీ డిఫాల్ట్ డయలర్ అనువర్తనంగా <xliff:g id="NEW_APP">%s</xliff:g>ని ఉపయోగించాలా?"</string>
</resources>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index 2babae6..106a1b5 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -16,7 +16,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"โทรศัพท์"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"การจัดการการโทรศัพท์"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"โทรศัพท์"</string>
<string name="unknown" msgid="6878797917991465859">"ไม่ทราบ"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"สายที่ไม่ได้รับ"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"สายที่ไม่ได้รับ"</string>
@@ -35,11 +36,17 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"คำตอบด่วน"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"ส่งข้อความไปยัง <xliff:g id="PHONE_NUMBER">%s</xliff:g> แล้ว"</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"เจ้าของอุปกรณ์อนุญาตเฉพาะหมายเลขฉุกเฉิน"</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"บัญชีการโทร"</string>
+ <!-- no translation found for outgoing_call_not_allowed_user_restriction (6872406278300131364) -->
+ <skip />
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"แอปพลิเคชันนี้ไม่สามารถโทรออกโดยไม่มีสิทธิ์ใช้โทรศัพท์"</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"หากต้องการโทรออก โปรดป้อนหมายเลขที่ถูกต้อง"</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"ไม่สามารถเพิ่มสายได้ในขณะนี้"</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"โปรดปิดใช้โหมด TTY เพื่อแฮงเอาท์วิดีโอ"</string>
<string name="no_vm_number" msgid="4164780423805688336">"ไม่มีหมายเลขข้อความเสียง"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"ไม่มีหมายเลขข้อความเสียงจัดเก็บอยู่ในซิมการ์ด"</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"เพิ่มหมายเลข"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"เปลี่ยนแอปแป้นโทรศัพท์เริ่มต้นไหม"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"ใช้ <xliff:g id="NEW_APP">%1$s</xliff:g> เป็นแอปแป้นโทรศัพท์เริ่มต้นแทน <xliff:g id="CURRENT_APP">%2$s</xliff:g> ไหม"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"ใช้ <xliff:g id="NEW_APP">%s</xliff:g> เป็นแอปแป้นโทรศัพท์เริ่มต้นไหม"</string>
</resources>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index ca749b4..ea350ab 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -16,7 +16,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"Telepono"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"Pamamahala sa Tawag sa Telepono"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"Telepono"</string>
<string name="unknown" msgid="6878797917991465859">"Di-kilala"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Hindi nasagot na tawag"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Mga hindi nasagot na tawag"</string>
@@ -26,20 +27,26 @@
<string name="notification_missedCall_message" msgid="3049928912736917988">"Padalhan ng mensahe"</string>
<string name="accessibility_call_muted" msgid="2776111226185342220">"Naka-mute ang tawag."</string>
<string name="accessibility_speakerphone_enabled" msgid="1988512040421036359">"Pinapagana ang speakerphone."</string>
- <string name="respond_via_sms_canned_response_1" msgid="2461606462788380215">"Di masagot ngayon. Ano meron?"</string>
+ <string name="respond_via_sms_canned_response_1" msgid="2461606462788380215">"Di masagot ngayon. Ano\'ng meron?"</string>
<string name="respond_via_sms_canned_response_2" msgid="4074450431532859214">"Tawagan kita ulit."</string>
<string name="respond_via_sms_canned_response_3" msgid="3496079065723960450">"Tawagan kita mamaya."</string>
<string name="respond_via_sms_canned_response_4" msgid="1698989243040062190">"Di masagot ngayon. Tawag ka mamaya?"</string>
<string name="respond_via_sms_setting_title" msgid="3754000371039709383">"Mga mabilisang tugon"</string>
- <string name="respond_via_sms_setting_title_2" msgid="6104662227299493906">"I-edit ang mga mabilisang tugon"</string>
+ <string name="respond_via_sms_setting_title_2" msgid="6104662227299493906">"I-edit ang mga quick response"</string>
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Mabilisang tugon"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Naipadala ang mensahe sa <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"Ang mga emergency na tawag lang ang pinapayagan ng may-ari ng device"</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"Account sa pagtawag"</string>
+ <!-- no translation found for outgoing_call_not_allowed_user_restriction (6872406278300131364) -->
+ <skip />
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Hindi makakapagsagawa ng mga papalabas na tawag ang application na ito nang wala ang pahintulot ng Telepono."</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"Upang tumawag, maglagay ng wastong numero."</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"Hindi maidadagdag ang tawag sa oras na ito."</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"Paki-disable ang TTY Mode upang makapag-video call."</string>
<string name="no_vm_number" msgid="4164780423805688336">"Nawawala ang numero ng voicemail"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"Walang nakaimbak na numero ng voicemail sa SIM card."</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"Magdagdag ng numero"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"Baguhin ang iyong default na Dialer app?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"Gamitin ang <xliff:g id="NEW_APP">%1$s</xliff:g> sa halip na <xliff:g id="CURRENT_APP">%2$s</xliff:g> bilang default na dialer app mo?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"Gamitin ang <xliff:g id="NEW_APP">%s</xliff:g> bilang default na dialer app mo?"</string>
</resources>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index 662c3fd..0ef8a03 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -16,7 +16,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"Telefon"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"Telefon Çağrısı Yönetimi"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"Telefon"</string>
<string name="unknown" msgid="6878797917991465859">"Bilinmiyor"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Cevapsız çağrı"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Cevapsız çağrılar"</string>
@@ -35,11 +36,17 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Hızlı yanıt"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Mesaj, <xliff:g id="PHONE_NUMBER">%s</xliff:g> numaralı telefona gönderildi."</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"Cihaz sahibi sadece acil durum çağrılarına izin veriyor"</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"Çağrı hesapları"</string>
+ <!-- no translation found for outgoing_call_not_allowed_user_restriction (6872406278300131364) -->
+ <skip />
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Bu uygulama, Telefonun izni olmadan giden çağrılar yapamaz."</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"Telefon etmek için geçerli bir numara girin."</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"Çağrı şu anda eklenemiyor."</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"Video görüşmesi yapmak için lütfen TTY Modu\'nu devre dışı bırakın."</string>
<string name="no_vm_number" msgid="4164780423805688336">"Eksik sesli mesaj numarası"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"SIM kartta depolanan sesli mesaj numarası yok."</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"Numara ekle"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"Varsayılan Numara Çeviri uygulaması değiştirilsin mi?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"Varsayılan numara çevirici uygulamanız olarak <xliff:g id="CURRENT_APP">%2$s</xliff:g> yerine <xliff:g id="NEW_APP">%1$s</xliff:g> kullanılsın mı?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"Varsayılan numara çevirici uygulamanız olarak <xliff:g id="NEW_APP">%s</xliff:g> kullanılsın mı?"</string>
</resources>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index 16214b4..6182002 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -16,9 +16,10 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"Номер телефону"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"Керування дзвінками"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"Телефон"</string>
<string name="unknown" msgid="6878797917991465859">"Невідомий"</string>
- <string name="notification_missedCallTitle" msgid="7554385905572364535">"Пропущ. виклик"</string>
+ <string name="notification_missedCallTitle" msgid="7554385905572364535">"Пропущений виклик"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Пропущ. дзвінки"</string>
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"Пропущ. дзвінк: <xliff:g id="NUM_MISSED_CALLS">%s</xliff:g>"</string>
<string name="notification_missedCallTicker" msgid="504686252427747209">"Пропущ. виклик від <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
@@ -35,11 +36,16 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Швидка відповідь"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Повідомлення надіслано на номер <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"Власник пристрою дозволив лише екстрені виклики"</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"Облікові записи для дзвінків"</string>
+ <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Дозволено лише екстрені виклики."</string>
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Цей додаток не може телефонувати без відповідного дозволу."</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"Щоб зателефонувати, введіть дійсний номер."</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"Зараз не можна почати дзвінок."</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"Щоб здійснювати відеодзвінки, вимкніть режим TTY."</string>
- <string name="no_vm_number" msgid="4164780423805688336">"Відстун. номер голос. пошти"</string>
+ <string name="no_vm_number" msgid="4164780423805688336">"Відстуній номер голосової пошти"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"На SIM-карті немає збереж. номерів голос. пошти."</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"Додати номер"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"Змінити додаток для дзвінків за умовчанням?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"Зробити <xliff:g id="NEW_APP">%1$s</xliff:g> додатком для дзвінків за умовчанням замість додатка <xliff:g id="CURRENT_APP">%2$s</xliff:g>?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"Зробити <xliff:g id="NEW_APP">%s</xliff:g> додатком для дзвінків за умовчанням?"</string>
</resources>
diff --git a/res/values-ur-rPK/strings.xml b/res/values-ur-rPK/strings.xml
index ef9b16a..6144a70 100644
--- a/res/values-ur-rPK/strings.xml
+++ b/res/values-ur-rPK/strings.xml
@@ -16,7 +16,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"فون"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"فون کال کا نظم"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"فون"</string>
<string name="unknown" msgid="6878797917991465859">"نامعلوم"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"چھوٹی ہوئی کال"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"چھوٹی ہوئی کالیں"</string>
@@ -35,11 +36,17 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"فوری جواب"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"پیغام <xliff:g id="PHONE_NUMBER">%s</xliff:g> کو بھیج دیا گیا۔"</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"آلہ کے مالک نے صرف ہنگامی کالز کی اجازت دی ہے۔"</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"کالنگ اکاؤنٹس"</string>
+ <!-- no translation found for outgoing_call_not_allowed_user_restriction (6872406278300131364) -->
+ <skip />
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"یہ ایپلی کیشن فون کی اجازت کے بغیر باہر جانے والی کالیں نہیں کر سکتی۔"</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"کال کرنے کیلئے، ایک درست نمبر درج کریں۔"</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"اس وقت کال شامل نہیں کی جا سکتی ہے۔"</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"براہ کرم ویڈیو کالز کرنے کیلئے TTY وضع غیر فعال کریں۔"</string>
<string name="no_vm_number" msgid="4164780423805688336">"صوتی میل نمبر درج نہیں ہے"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"SIM کارڈ پر کوئی بھی صوتی میل نمبر اسٹور نہیں ہے۔"</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"نمبر شامل کریں"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"ڈیفالٹ ڈائلر ایپ تبدیل کریں"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"<xliff:g id="CURRENT_APP">%2$s</xliff:g> کی بجائے <xliff:g id="NEW_APP">%1$s</xliff:g> کو بطور اپنی ڈیفالٹ ڈائلر ایپ استعمال کریں؟"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"<xliff:g id="NEW_APP">%s</xliff:g> کو بطور اپنی ڈیفالٹ ڈائلر ایپ استعمال کریں؟"</string>
</resources>
diff --git a/res/values-uz-rUZ/strings.xml b/res/values-uz-rUZ/strings.xml
index 1cd5da6..3ad9cf5 100644
--- a/res/values-uz-rUZ/strings.xml
+++ b/res/values-uz-rUZ/strings.xml
@@ -16,30 +16,37 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"Telefon"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"Telefon – qo‘ng‘iroqlarni boshqarish"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"Telefon"</string>
<string name="unknown" msgid="6878797917991465859">"Noma’lum"</string>
- <string name="notification_missedCallTitle" msgid="7554385905572364535">"Javob berilmagan qo‘ng‘iroq"</string>
+ <string name="notification_missedCallTitle" msgid="7554385905572364535">"Javobsiz qo‘ng‘iroq"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Javobsiz qo‘ng‘iroqlar"</string>
<string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> ta javobsiz qo‘ng‘iroq"</string>
- <string name="notification_missedCallTicker" msgid="504686252427747209">"<xliff:g id="MISSED_CALL_FROM">%s</xliff:g>dan javobsiz qo‘ng‘iroq"</string>
- <string name="notification_missedCall_call_back" msgid="2684890353590890187">"Teskari qo‘ng‘iroq"</string>
- <string name="notification_missedCall_message" msgid="3049928912736917988">"Xabar"</string>
+ <string name="notification_missedCallTicker" msgid="504686252427747209">"<xliff:g id="MISSED_CALL_FROM">%s</xliff:g> qo‘ng‘irog‘i javobsiz qoldirildi"</string>
+ <string name="notification_missedCall_call_back" msgid="2684890353590890187">"Telefon"</string>
+ <string name="notification_missedCall_message" msgid="3049928912736917988">"SMS"</string>
<string name="accessibility_call_muted" msgid="2776111226185342220">"Qo‘ng‘iroq ovozi o‘chirildi."</string>
<string name="accessibility_speakerphone_enabled" msgid="1988512040421036359">"Karnaychalar yoqildi."</string>
<string name="respond_via_sms_canned_response_1" msgid="2461606462788380215">"Hozir gaplasholmayman. Tinchlikmi?"</string>
- <string name="respond_via_sms_canned_response_2" msgid="4074450431532859214">"Hozir qaytarib qo‘ng‘iroq qilaman."</string>
- <string name="respond_via_sms_canned_response_3" msgid="3496079065723960450">"Keyinroq qo‘ng‘iroq qilaman."</string>
- <string name="respond_via_sms_canned_response_4" msgid="1698989243040062190">"Bandman. Keyin qo‘n-q qilasizmi?"</string>
+ <string name="respond_via_sms_canned_response_2" msgid="4074450431532859214">"Hozir telefon qilaman."</string>
+ <string name="respond_via_sms_canned_response_3" msgid="3496079065723960450">"Keyinroq telefon qilaman."</string>
+ <string name="respond_via_sms_canned_response_4" msgid="1698989243040062190">"Bandman, keyinroq telefon qiling."</string>
<string name="respond_via_sms_setting_title" msgid="3754000371039709383">"Tezkor javoblar"</string>
- <string name="respond_via_sms_setting_title_2" msgid="6104662227299493906">"Tezkor javoblarni tahrirlash"</string>
+ <string name="respond_via_sms_setting_title_2" msgid="6104662227299493906">"Tezkor javoblar"</string>
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Tezkor javob"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Xabar <xliff:g id="PHONE_NUMBER">%s</xliff:g>ga jo‘natildi."</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"Qurilma egasi faqat favqulodda qo‘ng‘iroqlarga ruxsat bergan"</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"Qo‘ng‘iroq hisoblari"</string>
+ <!-- no translation found for outgoing_call_not_allowed_user_restriction (6872406278300131364) -->
+ <skip />
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Bu ilova Telefon ruxsatnomasisiz chiquvchi qo‘ng‘iroqlarni amalga oshira olmaydi."</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"Qo‘ng‘iroq qilish uchun raqamni to‘g‘ri kiriting."</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"Hozirgi vaqtda qo‘ng‘iroq qo‘shib bo‘lmaydi."</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"Video qo\'ng\'iroqlarni amalga oshirish uchun TTY rejimini o‘chirib qo‘yish kerak"</string>
- <string name="no_vm_number" msgid="4164780423805688336">"Javobsiz ovozli xabar raqami"</string>
- <string name="no_vm_number_msg" msgid="1300729501030053828">"SIM kartada birorta ham ovozli xabar saqlanmagan."</string>
+ <string name="no_vm_number" msgid="4164780423805688336">"Ovozli pochta raqami ko‘rsatilmagan"</string>
+ <string name="no_vm_number_msg" msgid="1300729501030053828">"SIM kartada birorta ham ovozli pochta raqami yo‘q."</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"Raqam qo‘shish"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"Birlamchi raqam terish ilovasi o‘zgartirilsinmi?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"Raqam terish uchun birlamchi ilova sifatida <xliff:g id="CURRENT_APP">%2$s</xliff:g> o‘rniga <xliff:g id="NEW_APP">%1$s</xliff:g> ilovasi tanlansinmi?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"<xliff:g id="NEW_APP">%s</xliff:g> raqam terish uchun birlamchi ilova sifatida tanlansinmi?"</string>
</resources>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index 3b7e1b4..6f5ccc5 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -16,7 +16,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"Điện thoại"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"Quản lý cuộc gọi điện thoại"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"Điện thoại"</string>
<string name="unknown" msgid="6878797917991465859">"Không xác định"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Cuộc gọi nhỡ"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Cuộc gọi nhỡ"</string>
@@ -35,11 +36,16 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Trả lời nhanh"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Đã gửi tin nhắn tới <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"Chỉ chủ sở hữu thiết bị mới được phép thực hiện cuộc gọi khẩn cấp"</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"Tài khoản gọi"</string>
+ <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Chỉ được phép thực hiện cuộc gọi khẩn cấp."</string>
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Ứng dụng này không thể thực hiện cuộc gọi đi mà không có quyền của Điện thoại."</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"Để thực hiện cuộc gọi, hãy nhập một số hợp lệ."</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"Không thể thêm cuộc gọi tại thời điểm này."</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"Vui lòng tắt Chế độ TTY để thực hiện cuộc gọi điện video."</string>
<string name="no_vm_number" msgid="4164780423805688336">"Thiếu số thư thoại"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"Không có số thư thoại nào được lưu trữ trên thẻ SIM."</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"Thêm số điện thoại"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"Thay đổi ứng dụng Trình quay số mặc định?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"Sử dụng <xliff:g id="NEW_APP">%1$s</xliff:g> làm ứng dụng trình quay số mặc định của bạn thay vì <xliff:g id="CURRENT_APP">%2$s</xliff:g>?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"Sử dụng <xliff:g id="NEW_APP">%s</xliff:g> làm ứng dụng trình quay số mặc định của bạn?"</string>
</resources>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index 2d06096..c6752ad 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -16,7 +16,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"电话"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"通话管理"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"电话"</string>
<string name="unknown" msgid="6878797917991465859">"未知"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"未接电话"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"未接电话"</string>
@@ -35,11 +36,17 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"快速回复"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"讯息已发送至 <xliff:g id="PHONE_NUMBER">%s</xliff:g>。"</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"设备机主仅允许拨打紧急呼救电话"</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"通话帐号"</string>
+ <!-- no translation found for outgoing_call_not_allowed_user_restriction (6872406278300131364) -->
+ <skip />
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"此应用没有电话权限,无法拨出电话。"</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"要拨打电话,请输入有效的电话号码。"</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"目前无法添加通话。"</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"要进行视频通话,请停用 TTY 模式。"</string>
<string name="no_vm_number" msgid="4164780423805688336">"缺少语音信箱号码"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"SIM卡上未存储语音信箱号码。"</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"添加号码"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"要更改默认拨号器应用吗?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"要使用<xliff:g id="NEW_APP">%1$s</xliff:g>(而非<xliff:g id="CURRENT_APP">%2$s</xliff:g>)作为您的默认拨号器应用吗?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"要使用<xliff:g id="NEW_APP">%s</xliff:g>作为您的默认拨号器应用吗?"</string>
</resources>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index c00d0b4..d00073d 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -16,7 +16,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"電話"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"手機通話管理"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"電話"</string>
<string name="unknown" msgid="6878797917991465859">"未知"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"未接來電"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"未接來電"</string>
@@ -35,11 +36,16 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"快速回應"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"訊息已傳送至 <xliff:g id="PHONE_NUMBER">%s</xliff:g>。"</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"只有裝置擁有者才可撥打緊急電話"</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"通話帳戶"</string>
+ <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"只限緊急電話。"</string>
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"此應用程式沒有電話權限,無法撥出電話。"</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"要撥打電話,請輸入有效的號碼。"</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"目前無法新增視像通話。"</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"請停用 TTY 模式以進行視像通話。"</string>
<string name="no_vm_number" msgid="4164780423805688336">"未填留言信箱號碼"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"SIM 卡中沒有儲存任何留言信箱號碼。"</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"新增電話號碼"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"變更預設撥號器應用程式?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"使用 <xliff:g id="NEW_APP">%1$s</xliff:g> 取代 <xliff:g id="CURRENT_APP">%2$s</xliff:g> 作為預設撥號器應用程式?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"使用 <xliff:g id="NEW_APP">%s</xliff:g> 為預設撥號器應用程式?"</string>
</resources>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index 231d938..a06b76f 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -16,7 +16,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"電話"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"通話管理"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"電話"</string>
<string name="unknown" msgid="6878797917991465859">"不明"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"未接來電"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"未接來電"</string>
@@ -35,11 +36,17 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"快速回應"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"訊息已傳送至 <xliff:g id="PHONE_NUMBER">%s</xliff:g>。"</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"裝置擁有者限定只能撥打緊急電話"</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"通話帳戶"</string>
+ <!-- no translation found for outgoing_call_not_allowed_user_restriction (6872406278300131364) -->
+ <skip />
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"這個應用程式不具備電話應用程式存取權限,無法撥出電話。"</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"如要撥打電話,請輸入有效的號碼。"</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"目前無法新增通話。"</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"如要進行視訊通話,請停用 TTY 模式。"</string>
<string name="no_vm_number" msgid="4164780423805688336">"遺失語音信箱號碼"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"SIM 卡中未儲存語音信箱號碼。"</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"新增號碼"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"要變更預設撥號應用程式嗎?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"要將 <xliff:g id="NEW_APP">%1$s</xliff:g> (而非 <xliff:g id="CURRENT_APP">%2$s</xliff:g>) 設為預設撥號應用程式嗎?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"要將 <xliff:g id="NEW_APP">%s</xliff:g> 設為預設撥號應用程式嗎?"</string>
</resources>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index acb00f3..70188be 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -16,7 +16,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="telecommAppLabel" product="default" msgid="3477737022166975496">"Ifoni"</string>
+ <string name="telecommAppLabel" product="default" msgid="9166784827254469057">"Ukuphathwa kwekholi yefoni"</string>
+ <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"Ifoni"</string>
<string name="unknown" msgid="6878797917991465859">"Akwaziwa"</string>
<string name="notification_missedCallTitle" msgid="7554385905572364535">"Ikholi ekulahlekele"</string>
<string name="notification_missedCallsTitle" msgid="1361677948941502522">"Amakholi akuphuthele"</string>
@@ -35,11 +36,17 @@
<string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
<string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Izimpendulo ezisheshayo"</string>
<string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Umlayezo othunyelwe ku <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
- <string name="outgoing_call_not_allowed" msgid="1435394568102165287">"Amakholi wesimo esiphuthumayo kuphela avunyelwe ngumnikazi wedivayisi"</string>
+ <string name="enable_account_preference_title" msgid="2021848090086481720">"Ama-akhawunti wokushaya"</string>
+ <!-- no translation found for outgoing_call_not_allowed_user_restriction (6872406278300131364) -->
+ <skip />
+ <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Lolu hlelo lokusebenza alikwazi ukwenza amakhli aphumayo ngaphandle kwemvume yefoni."</string>
<string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"Ukuze wenze ikholi, faka inombolo evumelekile."</string>
<string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"Ikholi ayikwazi ukungezwa ngalesi sikhathi."</string>
<string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"Sicela ukhubaze imodi ye-TTY ukuze wenze amakholi wevidiyo."</string>
<string name="no_vm_number" msgid="4164780423805688336">"Inombolo engekho yomyalezo wezwi"</string>
<string name="no_vm_number_msg" msgid="1300729501030053828">"Ayikho inombolo yomlayezo wezwi egcinwe ekhadini le-SIM."</string>
<string name="add_vm_number_str" msgid="4676479471644687453">"Engeza inombolo"</string>
+ <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"Guqula uhlelo lwakho lokusebenza oluzenzakalelayo lokudayela?"</string>
+ <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"Sebenzisa i-<xliff:g id="NEW_APP">%1$s</xliff:g> esikhundleni se-<xliff:g id="CURRENT_APP">%2$s</xliff:g> njengohlelo lwakho lokusebenza oluzenzakalelayo lokokudayela?"</string>
+ <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"Sebenzisa i-<xliff:g id="NEW_APP">%s</xliff:g> njengohlelo lwakho lokusebenza oluzenzakalelayo lokokudayela?"</string>
</resources>
diff --git a/src/com/android/server/telecom/Analytics.java b/src/com/android/server/telecom/Analytics.java
new file mode 100644
index 0000000..c86d6fc
--- /dev/null
+++ b/src/com/android/server/telecom/Analytics.java
@@ -0,0 +1,266 @@
+/*
+ * 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.server.telecom;
+
+import android.telecom.DisconnectCause;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A class that collects and stores data on how calls are being made, in order to
+ * aggregate these into useful statistics.
+ */
+public class Analytics {
+ public static class CallInfo {
+ void setCallStartTime(long startTime) {
+ }
+
+ void setCallEndTime(long endTime) {
+ }
+
+ void setCallIsAdditional(boolean isAdditional) {
+ }
+
+ void setCallIsInterrupted(boolean isInterrupted) {
+ }
+
+ void setCallDisconnectCause(DisconnectCause disconnectCause) {
+ }
+
+ void addCallTechnology(int callTechnology) {
+ }
+
+ void setCreatedFromExistingConnection(boolean createdFromExistingConnection) {
+ }
+
+ void setCallConnectionService(String connectionServiceName) {
+ }
+ }
+
+ /**
+ * A class that holds data associated with a call.
+ */
+ @VisibleForTesting
+ public static class CallInfoImpl extends CallInfo {
+ public String callId;
+ public long startTime; // start time in milliseconds since the epoch. 0 if not yet set.
+ public long endTime; // end time in milliseconds since the epoch. 0 if not yet set.
+ public int callDirection; // one of UNKNOWN_DIRECTION, INCOMING_DIRECTION,
+ // or OUTGOING_DIRECTION.
+ public boolean isAdditionalCall = false; // true if the call came in while another call was
+ // in progress or if the user dialed this call
+ // while in the middle of another call.
+ public boolean isInterrupted = false; // true if the call was interrupted by an incoming
+ // or outgoing call.
+ public int callTechnologies; // bitmask denoting which technologies a call used.
+
+ // true if the Telecom Call object was created from an existing connection via
+ // CallsManager#createCallForExistingConnection, for example, by ImsConference.
+ public boolean createdFromExistingConnection = false;
+
+ public DisconnectCause callTerminationReason;
+ public String connectionService;
+ public boolean isEmergency = false;
+
+ CallInfoImpl(String callId, int callDirection) {
+ this.callId = callId;
+ startTime = 0;
+ endTime = 0;
+ this.callDirection = callDirection;
+ callTechnologies = 0;
+ connectionService = "";
+ }
+
+ CallInfoImpl(CallInfoImpl other) {
+ this.callId = other.callId;
+ this.startTime = other.startTime;
+ this.endTime = other.endTime;
+ this.callDirection = other.callDirection;
+ this.isAdditionalCall = other.isAdditionalCall;
+ this.isInterrupted = other.isInterrupted;
+ this.callTechnologies = other.callTechnologies;
+ this.createdFromExistingConnection = other.createdFromExistingConnection;
+ this.connectionService = other.connectionService;
+ this.isEmergency = other.isEmergency;
+
+ if (other.callTerminationReason != null) {
+ this.callTerminationReason = new DisconnectCause(
+ other.callTerminationReason.getCode(),
+ other.callTerminationReason.getLabel(),
+ other.callTerminationReason.getDescription(),
+ other.callTerminationReason.getReason(),
+ other.callTerminationReason.getTone());
+ } else {
+ this.callTerminationReason = null;
+ }
+ }
+
+ @Override
+ public void setCallStartTime(long startTime) {
+ Log.d(TAG, "setting startTime for call " + callId + " to " + startTime);
+ this.startTime = startTime;
+ }
+
+ @Override
+ public void setCallEndTime(long endTime) {
+ Log.d(TAG, "setting endTime for call " + callId + " to " + endTime);
+ this.endTime = endTime;
+ }
+
+ @Override
+ public void setCallIsAdditional(boolean isAdditional) {
+ Log.d(TAG, "setting isAdditional for call " + callId + " to " + isAdditional);
+ this.isAdditionalCall = isAdditional;
+ }
+
+ @Override
+ public void setCallIsInterrupted(boolean isInterrupted) {
+ Log.d(TAG, "setting isInterrupted for call " + callId + " to " + isInterrupted);
+ this.isInterrupted = isInterrupted;
+ }
+
+ @Override
+ public void addCallTechnology(int callTechnology) {
+ Log.d(TAG, "adding callTechnology for call " + callId + ": " + callTechnology);
+ this.callTechnologies |= callTechnology;
+ }
+
+ @Override
+ public void setCallDisconnectCause(DisconnectCause disconnectCause) {
+ Log.d(TAG, "setting disconnectCause for call " + callId + " to " + disconnectCause);
+ this.callTerminationReason = disconnectCause;
+ }
+
+ @Override
+ public void setCreatedFromExistingConnection(boolean createdFromExistingConnection) {
+ Log.d(TAG, "setting createdFromExistingConnection for call " + callId + " to "
+ + createdFromExistingConnection);
+ this.createdFromExistingConnection = createdFromExistingConnection;
+ }
+
+ @Override
+ public void setCallConnectionService(String connectionServiceName) {
+ Log.d(TAG, "setting connection service for call " + callId + ": "
+ + connectionServiceName);
+ this.connectionService = connectionServiceName;
+ }
+
+ @Override
+ public String toString() {
+ return "{\n"
+ + " startTime: " + startTime + '\n'
+ + " endTime: " + endTime + '\n'
+ + " direction: " + getCallDirectionString() + '\n'
+ + " isAdditionalCall: " + isAdditionalCall + '\n'
+ + " isInterrupted: " + isInterrupted + '\n'
+ + " callTechnologies: " + getCallTechnologiesAsString() + '\n'
+ + " callTerminationReason: " + getCallDisconnectReasonString() + '\n'
+ + " connectionServices: " + connectionService + '\n'
+ + "}\n";
+ }
+
+ private String getCallDirectionString() {
+ switch (callDirection) {
+ case UNKNOWN_DIRECTION:
+ return "UNKNOWN";
+ case INCOMING_DIRECTION:
+ return "INCOMING";
+ case OUTGOING_DIRECTION:
+ return "OUTGOING";
+ default:
+ return "UNKNOWN";
+ }
+ }
+
+ private String getCallTechnologiesAsString() {
+ StringBuilder s = new StringBuilder();
+ s.append('[');
+ if ((callTechnologies & CDMA_PHONE) != 0) s.append("CDMA ");
+ if ((callTechnologies & GSM_PHONE) != 0) s.append("GSM ");
+ if ((callTechnologies & SIP_PHONE) != 0) s.append("SIP ");
+ if ((callTechnologies & IMS_PHONE) != 0) s.append("IMS ");
+ if ((callTechnologies & THIRD_PARTY_PHONE) != 0) s.append("THIRD_PARTY ");
+ s.append(']');
+ return s.toString();
+ }
+
+ private String getCallDisconnectReasonString() {
+ if (callTerminationReason != null) {
+ return callTerminationReason.toString();
+ } else {
+ return "NOT SET";
+ }
+ }
+ }
+ public static final String TAG = "TelecomAnalytics";
+
+ // Constants for call direction
+ public static final int UNKNOWN_DIRECTION = 0;
+ public static final int INCOMING_DIRECTION = 1;
+ public static final int OUTGOING_DIRECTION = 2;
+
+ // Constants for call technology
+ public static final int CDMA_PHONE = 0x1;
+ public static final int GSM_PHONE = 0x2;
+ public static final int IMS_PHONE = 0x4;
+ public static final int SIP_PHONE = 0x8;
+ public static final int THIRD_PARTY_PHONE = 0x10;
+
+ private static final Object sLock = new Object(); // Coarse lock for all of analytics
+ private static final Map<String, CallInfoImpl> sCallIdToInfo = new HashMap<>();
+
+ public static CallInfo initiateCallAnalytics(String callId, int direction) {
+ Log.d(TAG, "Starting analytics for call " + callId);
+ CallInfoImpl callInfo = new CallInfoImpl(callId, direction);
+ synchronized (sLock) {
+ sCallIdToInfo.put(callId, callInfo);
+ }
+ return callInfo;
+ }
+
+ public static void dump(IndentingPrintWriter writer) {
+ synchronized (sLock) {
+ for (Map.Entry<String, CallInfoImpl> entry : sCallIdToInfo.entrySet()) {
+ writer.printf("Call %s: ", entry.getKey());
+ writer.println(entry.getValue().toString());
+ }
+ }
+ }
+
+ public static void reset() {
+ synchronized (sLock) {
+ sCallIdToInfo.clear();
+ }
+ }
+
+ /**
+ * Returns a deep copy of callIdToInfo that's safe to read/write without synchronization
+ */
+ public static Map<String, CallInfoImpl> cloneData() {
+ synchronized (sLock) {
+ Map<String, CallInfoImpl> result = new HashMap<>(sCallIdToInfo.size());
+ for (Map.Entry<String, CallInfoImpl> entry : sCallIdToInfo.entrySet()) {
+ result.put(entry.getKey(), new CallInfoImpl(entry.getValue()));
+ }
+ return result;
+ }
+ }
+}
diff --git a/src/com/android/server/telecom/AsyncRingtonePlayer.java b/src/com/android/server/telecom/AsyncRingtonePlayer.java
index 3030fea..f597cf1 100644
--- a/src/com/android/server/telecom/AsyncRingtonePlayer.java
+++ b/src/com/android/server/telecom/AsyncRingtonePlayer.java
@@ -16,23 +16,20 @@
package com.android.server.telecom;
-import android.content.Context;
-import android.media.AudioManager;
import android.media.Ringtone;
-import android.media.RingtoneManager;
-import android.net.Uri;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
-import android.provider.Settings;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
/**
* Plays the default ringtone. Uses {@link Ringtone} in a separate thread so that this class can be
* used from the main thread.
*/
-class AsyncRingtonePlayer {
+@VisibleForTesting
+public class AsyncRingtonePlayer {
// Message codes used with the ringtone thread.
private static final int EVENT_PLAY = 1;
private static final int EVENT_STOP = 2;
@@ -47,23 +44,18 @@
/** The current ringtone. Only used by the ringtone thread. */
private Ringtone mRingtone;
- /**
- * The context.
- */
- private final Context mContext;
-
- AsyncRingtonePlayer(Context context) {
- mContext = context;
- }
-
/** Plays the ringtone. */
- void play(Uri ringtone) {
+ public void play(Ringtone ringtone) {
Log.d(this, "Posting play.");
+ if (ringtone == null) {
+ Log.i(this, "null (silence -- not playing anything)");
+ return;
+ }
postMessage(EVENT_PLAY, true /* shouldCreateHandler */, ringtone);
}
/** Stops playing the ringtone. */
- void stop() {
+ public void stop() {
Log.d(this, "Posting stop.");
postMessage(EVENT_STOP, false /* shouldCreateHandler */, null);
}
@@ -75,7 +67,7 @@
* @param messageCode The message to post.
* @param shouldCreateHandler True when a handler should be created to handle this message.
*/
- private void postMessage(int messageCode, boolean shouldCreateHandler, Uri ringtone) {
+ private void postMessage(int messageCode, boolean shouldCreateHandler, Ringtone ringtone) {
synchronized(this) {
if (mHandler == null && shouldCreateHandler) {
mHandler = getNewHandler();
@@ -103,7 +95,7 @@
public void handleMessage(Message msg) {
switch(msg.what) {
case EVENT_PLAY:
- handlePlay((Uri) msg.obj);
+ handlePlay((Ringtone) msg.obj);
break;
case EVENT_REPEAT:
handleRepeat();
@@ -119,7 +111,7 @@
/**
* Starts the actual playback of the ringtone. Executes on ringtone-thread.
*/
- private void handlePlay(Uri ringtoneUri) {
+ private void handlePlay(Ringtone ringtone) {
// don't bother with any of this if there is an EVENT_STOP waiting.
if (mHandler.hasMessages(EVENT_STOP)) {
return;
@@ -129,13 +121,7 @@
Log.i(this, "Play ringtone.");
if (mRingtone == null) {
- mRingtone = getRingtone(ringtoneUri);
-
- // Cancel everything if there is no ringtone.
- if (mRingtone == null) {
- handleStop();
- return;
- }
+ mRingtone = ringtone;
}
handleRepeat();
@@ -189,16 +175,4 @@
}
}
}
-
- private Ringtone getRingtone(Uri ringtoneUri) {
- if (ringtoneUri == null) {
- ringtoneUri = Settings.System.DEFAULT_RINGTONE_URI;
- }
-
- Ringtone ringtone = RingtoneManager.getRingtone(mContext, ringtoneUri);
- if (ringtone != null) {
- ringtone.setStreamType(AudioManager.STREAM_RING);
- }
- return ringtone;
- }
}
diff --git a/src/com/android/server/telecom/BluetoothHeadsetProxy.java b/src/com/android/server/telecom/BluetoothHeadsetProxy.java
new file mode 100644
index 0000000..48c60a1
--- /dev/null
+++ b/src/com/android/server/telecom/BluetoothHeadsetProxy.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.server.telecom;
+
+import android.bluetooth.BluetoothHeadset;
+
+/**
+ * A proxy class that facilitates testing of the BluetoothPhoneServiceImpl class.
+ *
+ * This is necessary due to the "final" attribute of the BluetoothHeadset class. In order to
+ * test the correct functioning of the BluetoothPhoneServiceImpl class, the final class must be put
+ * into a container that can be mocked correctly.
+ */
+public class BluetoothHeadsetProxy {
+
+ private BluetoothHeadset mBluetoothHeadset;
+
+ public BluetoothHeadsetProxy(BluetoothHeadset headset) {
+ mBluetoothHeadset = headset;
+ }
+
+ public void clccResponse(int index, int direction, int status, int mode, boolean mpty,
+ String number, int type) {
+
+ mBluetoothHeadset.clccResponse(index, direction, status, mode, mpty, number, type);
+ }
+
+ public void phoneStateChanged(int numActive, int numHeld, int callState, String number,
+ int type) {
+
+ mBluetoothHeadset.phoneStateChanged(numActive, numHeld, callState, number, type);
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/server/telecom/BluetoothManager.java b/src/com/android/server/telecom/BluetoothManager.java
index aec0d3e..389d0f6 100644
--- a/src/com/android/server/telecom/BluetoothManager.java
+++ b/src/com/android/server/telecom/BluetoothManager.java
@@ -24,8 +24,11 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.Looper;
import android.os.SystemClock;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
import java.util.List;
@@ -35,6 +38,9 @@
* overall audio state. Also provides method for connecting the bluetooth headset to the phone call.
*/
public class BluetoothManager {
+ public interface BluetoothStateListener {
+ void onBluetoothStateChange(BluetoothManager bluetoothManager);
+ }
private final BluetoothProfile.ServiceListener mBluetoothProfileServiceListener =
new BluetoothProfile.ServiceListener() {
@@ -78,17 +84,31 @@
}
};
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
+
private final BluetoothAdapter mBluetoothAdapter;
- private final CallAudioManager mCallAudioManager;
+ private BluetoothStateListener mBluetoothStateListener;
private BluetoothHeadset mBluetoothHeadset;
private boolean mBluetoothConnectionPending = false;
private long mBluetoothConnectionRequestTime;
+ private final Runnable mBluetoothConnectionTimeout = new Runnable() {
+ @Override
+ public void run() {
+ if (!isBluetoothAudioConnected()) {
+ Log.v(this, "Bluetooth audio inexplicably disconnected within 5 seconds of " +
+ "connection. Updating UI.");
+ }
+ mBluetoothConnectionPending = false;
+ updateBluetoothState();
+ }
+ };
+ private final Context mContext;
- public BluetoothManager(Context context, CallAudioManager callAudioManager) {
+ public BluetoothManager(Context context) {
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
- mCallAudioManager = callAudioManager;
+ mContext = context;
if (mBluetoothAdapter != null) {
mBluetoothAdapter.getProfileProxy(context, mBluetoothProfileServiceListener,
@@ -102,6 +122,10 @@
context.registerReceiver(mReceiver, intentFilter);
}
+ public void setBluetoothStateListener(BluetoothStateListener bluetoothStateListener) {
+ mBluetoothStateListener = bluetoothStateListener;
+ }
+
//
// Bluetooth helper methods.
//
@@ -121,7 +145,8 @@
* available to the user (i.e. if the device is BT-capable
* and a headset is connected.)
*/
- boolean isBluetoothAvailable() {
+ @VisibleForTesting
+ public boolean isBluetoothAvailable() {
Log.v(this, "isBluetoothAvailable()...");
// There's no need to ask the Bluetooth system service if BT is enabled:
@@ -191,7 +216,8 @@
* that the BT audio connection is currently being set
* up, and will be connected soon.)
*/
- /* package */ boolean isBluetoothAudioConnectedOrPending() {
+ @VisibleForTesting
+ public boolean isBluetoothAudioConnectedOrPending() {
if (isBluetoothAudioConnected()) {
Log.v(this, "isBluetoothAudioConnectedOrPending: ==> TRUE (really connected)");
return true;
@@ -203,16 +229,9 @@
if (mBluetoothConnectionPending) {
long timeSinceRequest =
SystemClock.elapsedRealtime() - mBluetoothConnectionRequestTime;
- if (timeSinceRequest < 5000 /* 5 seconds */) {
- Log.v(this, "isBluetoothAudioConnectedOrPending: ==> TRUE (requested "
- + timeSinceRequest + " msec ago)");
- return true;
- } else {
- Log.v(this, "isBluetoothAudioConnectedOrPending: ==> FALSE (request too old: "
- + timeSinceRequest + " msec ago)");
- mBluetoothConnectionPending = false;
- return false;
- }
+ Log.v(this, "isBluetoothAudioConnectedOrPending: ==> TRUE (requested "
+ + timeSinceRequest + " msec ago)");
+ return true;
}
Log.v(this, "isBluetoothAudioConnectedOrPending: ==> FALSE");
@@ -223,10 +242,11 @@
* Notified audio manager of a change to the bluetooth state.
*/
void updateBluetoothState() {
- mCallAudioManager.onBluetoothStateChange(this);
+ mBluetoothStateListener.onBluetoothStateChange(this);
}
- void connectBluetoothAudio() {
+ @VisibleForTesting
+ public void connectBluetoothAudio() {
Log.v(this, "connectBluetoothAudio()...");
if (mBluetoothHeadset != null) {
mBluetoothHeadset.connectAudio();
@@ -239,13 +259,18 @@
// instantly. (See isBluetoothAudioConnectedOrPending() above.)
mBluetoothConnectionPending = true;
mBluetoothConnectionRequestTime = SystemClock.elapsedRealtime();
+ mHandler.removeCallbacks(mBluetoothConnectionTimeout);
+ mHandler.postDelayed(mBluetoothConnectionTimeout,
+ Timeouts.getBluetoothPendingTimeoutMillis(mContext.getContentResolver()));
}
- void disconnectBluetoothAudio() {
+ @VisibleForTesting
+ public void disconnectBluetoothAudio() {
Log.v(this, "disconnectBluetoothAudio()...");
if (mBluetoothHeadset != null) {
mBluetoothHeadset.disconnectAudio();
}
+ mHandler.removeCallbacks(mBluetoothConnectionTimeout);
mBluetoothConnectionPending = false;
}
diff --git a/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java b/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java
index dab4545..5c7cae4 100644
--- a/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java
+++ b/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java
@@ -34,6 +34,7 @@
import android.telephony.TelephonyManager;
import android.text.TextUtils;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.telecom.CallsManager.CallsManagerListener;
import java.util.Collection;
@@ -45,7 +46,13 @@
* Bluetooth headset manager for Telecom. This class shares the call state with the bluetooth device
* and accepts call-related commands to perform on behalf of the BT device.
*/
-public final class BluetoothPhoneServiceImpl {
+public class BluetoothPhoneServiceImpl {
+
+ public interface BluetoothPhoneServiceImplFactory {
+ BluetoothPhoneServiceImpl makeBluetoothPhoneServiceImpl(Context context,
+ TelecomSystem.SyncRoot lock, CallsManager callsManager,
+ PhoneAccountRegistrar phoneAccountRegistrar);
+ }
private static final String TAG = "BluetoothPhoneService";
@@ -79,7 +86,8 @@
* Binder implementation of IBluetoothHeadsetPhone. Implements the command interface that the
* bluetooth headset code uses to control call.
*/
- private final IBluetoothHeadsetPhone.Stub mBinder = new IBluetoothHeadsetPhone.Stub() {
+ @VisibleForTesting
+ public final IBluetoothHeadsetPhone.Stub mBinder = new IBluetoothHeadsetPhone.Stub() {
@Override
public boolean answerCall() throws RemoteException {
synchronized (mLock) {
@@ -271,7 +279,8 @@
* Listens to call changes from the CallsManager and calls into methods to update the bluetooth
* headset with the new states.
*/
- private CallsManagerListener mCallsManagerListener = new CallsManagerListenerBase() {
+ @VisibleForTesting
+ public CallsManagerListener mCallsManagerListener = new CallsManagerListenerBase() {
@Override
public void onCallAdded(Call call) {
updateHeadsetWithCallState(false /* force */);
@@ -350,27 +359,29 @@
* Listens to connections and disconnections of bluetooth headsets. We need to save the current
* bluetooth headset so that we know where to send call updates.
*/
- private BluetoothProfile.ServiceListener mProfileListener =
+ @VisibleForTesting
+ public BluetoothProfile.ServiceListener mProfileListener =
new BluetoothProfile.ServiceListener() {
- @Override
- public void onServiceConnected(int profile, BluetoothProfile proxy) {
- synchronized (mLock) {
- mBluetoothHeadset = (BluetoothHeadset) proxy;
- }
- }
+ @Override
+ public void onServiceConnected(int profile, BluetoothProfile proxy) {
+ synchronized (mLock) {
+ setBluetoothHeadset(new BluetoothHeadsetProxy((BluetoothHeadset) proxy));
+ }
+ }
- @Override
- public void onServiceDisconnected(int profile) {
- synchronized (mLock) {
- mBluetoothHeadset = null;
- }
- }
- };
+ @Override
+ public void onServiceDisconnected(int profile) {
+ synchronized (mLock) {
+ mBluetoothHeadset = null;
+ }
+ }
+ };
/**
* Receives events for global state changes of the bluetooth adapter.
*/
- private final BroadcastReceiver mBluetoothAdapterReceiver = new BroadcastReceiver() {
+ @VisibleForTesting
+ public final BroadcastReceiver mBluetoothAdapterReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
synchronized (mLock) {
@@ -389,7 +400,7 @@
};
private BluetoothAdapter mBluetoothAdapter;
- private BluetoothHeadset mBluetoothHeadset;
+ private BluetoothHeadsetProxy mBluetoothHeadset;
// A map from Calls to indexes used to identify calls for CLCC (C* List Current Calls).
private Map<Call, Integer> mClccIndexMap = new HashMap<>();
@@ -431,6 +442,11 @@
updateHeadsetWithCallState(false /* force */);
}
+ @VisibleForTesting
+ public void setBluetoothHeadset(BluetoothHeadsetProxy bluetoothHeadset) {
+ mBluetoothHeadset = bluetoothHeadset;
+ }
+
private boolean processChld(int chld) {
Call activeCall = mCallsManager.getActiveCall();
Call ringingCall = mCallsManager.getRingingCall();
@@ -485,7 +501,7 @@
if (!conferenceable.isEmpty()) {
mCallsManager.conference(activeCall, conferenceable.get(0));
return true;
- }
+ }
}
}
}
@@ -544,7 +560,7 @@
boolean shouldReevaluateState =
conferenceCall.can(Connection.CAPABILITY_MERGE_CONFERENCE) ||
(conferenceCall.can(Connection.CAPABILITY_SWAP_CONFERENCE) &&
- !conferenceCall.wasConferencePreviouslyMerged());
+ !conferenceCall.wasConferencePreviouslyMerged());
if (shouldReevaluateState) {
isPartOfConference = false;
@@ -621,7 +637,6 @@
* changed.
*/
private void updateHeadsetWithCallState(boolean force) {
- CallsManager callsManager = mCallsManager;
Call activeCall = mCallsManager.getActiveCall();
Call ringingCall = mCallsManager.getRingingCall();
Call heldCall = mCallsManager.getHeldCall();
@@ -676,11 +691,11 @@
(force ||
(!callsPendingSwitch &&
(numActiveCalls != mNumActiveCalls ||
- numHeldCalls != mNumHeldCalls ||
- bluetoothCallState != mBluetoothCallState ||
- !TextUtils.equals(ringingAddress, mRingingAddress) ||
- ringingAddressType != mRingingAddressType ||
- (heldCall != mOldHeldCall && !ignoreHeldCallChange))))) {
+ numHeldCalls != mNumHeldCalls ||
+ bluetoothCallState != mBluetoothCallState ||
+ !TextUtils.equals(ringingAddress, mRingingAddress) ||
+ ringingAddressType != mRingingAddressType ||
+ (heldCall != mOldHeldCall && !ignoreHeldCallChange))))) {
// If the call is transitioning into the alerting state, send DIALING first.
// Some devices expect to see a DIALING state prior to seeing an ALERTING state
@@ -812,15 +827,15 @@
PhoneAccount account = null;
if (call != null) {
// First try to get the network name of the foreground call.
- account = mPhoneAccountRegistrar.getPhoneAccountCheckCallingUser(
+ account = mPhoneAccountRegistrar.getPhoneAccountOfCurrentUser(
call.getTargetPhoneAccount());
}
if (account == null) {
// Second, Try to get the label for the default Phone Account.
- account = mPhoneAccountRegistrar.getPhoneAccountCheckCallingUser(
- mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(
- PhoneAccount.SCHEME_TEL));
+ account = mPhoneAccountRegistrar.getPhoneAccountUnchecked(
+ mPhoneAccountRegistrar.getOutgoingPhoneAccountForSchemeOfCurrentUser(
+ PhoneAccount.SCHEME_TEL));
}
return account;
}
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index cee5332..8555c20 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -29,7 +29,6 @@
import android.telecom.DisconnectCause;
import android.telecom.Connection;
import android.telecom.GatewayInfo;
-import android.telecom.InCallService.VideoCall;
import android.telecom.ParcelableConnection;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
@@ -39,10 +38,12 @@
import android.telecom.VideoProfile;
import android.telephony.PhoneNumberUtils;
import android.text.TextUtils;
+import android.os.UserHandle;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telecom.IVideoProvider;
import com.android.internal.telephony.CallerInfo;
+import com.android.internal.telephony.CallerInfoAsyncQuery;
import com.android.internal.telephony.CallerInfoAsyncQuery.OnQueryCompleteListener;
import com.android.internal.telephony.SmsApplication;
import com.android.server.telecom.ContactsAsyncHelper.OnImageLoadCompleteListener;
@@ -65,13 +66,21 @@
*/
@VisibleForTesting
public class Call implements CreateConnectionResponse {
+ public final static String CALL_ID_UNKNOWN = "-1";
+ public final static long DATA_USAGE_NOT_SET = -1;
+
+ public static final int CALL_DIRECTION_UNDEFINED = 0;
+ public static final int CALL_DIRECTION_OUTGOING = 1;
+ public static final int CALL_DIRECTION_INCOMING = 2;
+ public static final int CALL_DIRECTION_UNKNOWN = 3;
+
/**
* Listener for events on the call.
*/
interface Listener {
void onSuccessfulOutgoingCall(Call call, int callState);
void onFailedOutgoingCall(Call call, DisconnectCause disconnectCause);
- void onSuccessfulIncomingCall(Call call);
+ void onSuccessfulIncomingCall(Call call, boolean shouldSendToVoicemail);
void onFailedIncomingCall(Call call);
void onSuccessfulUnknownCall(Call call, int callState);
void onFailedUnknownCall(Call call);
@@ -103,7 +112,7 @@
@Override
public void onFailedOutgoingCall(Call call, DisconnectCause disconnectCause) {}
@Override
- public void onSuccessfulIncomingCall(Call call) {}
+ public void onSuccessfulIncomingCall(Call call, boolean shouldSendToVoicemail) {}
@Override
public void onFailedIncomingCall(Call call) {}
@Override
@@ -161,7 +170,10 @@
public void onQueryComplete(int token, Object cookie, CallerInfo callerInfo) {
synchronized (mLock) {
if (cookie != null) {
- ((Call) cookie).setCallerInfo(callerInfo, token);
+ CallSessionCookie callSession = (CallSessionCookie) cookie;
+ Log.continueSession(callSession.mSession, "OQCL.oQC");
+ callSession.mSessionCall.setCallerInfo(callerInfo, token);
+ Log.endSession();
}
}
}
@@ -175,28 +187,56 @@
int token, Drawable photo, Bitmap photoIcon, Object cookie) {
synchronized (mLock) {
if (cookie != null) {
- ((Call) cookie).setPhoto(photo, photoIcon, token);
+ CallSessionCookie callSession = (CallSessionCookie) cookie;
+ Log.continueSession(callSession.mSession, "OCLCL.oILC");
+ callSession.mSessionCall.setPhoto(photo, photoIcon, token);
+ Log.endSession();
}
}
}
};
- private final Runnable mDirectToVoicemailRunnable = new Runnable() {
+ private class DirectToVoicemailRunnable implements Runnable {
+
+ Session mSession;
+
+ public DirectToVoicemailRunnable(Session session) {
+ mSession = session;
+ }
+
@Override
public void run() {
- synchronized (mLock) {
- processDirectToVoicemail();
+ try {
+ Log.continueSession(mSession, "DTVR.r");
+ synchronized (mLock) {
+ processDirectToVoicemail();
+ }
+ } finally {
+ Log.endSession();
+ mSession = null;
}
}
- };
+ }
- /** True if this is an incoming call. */
- private final boolean mIsIncoming;
+ private class CallSessionCookie {
+ Call mSessionCall;
+ Session mSession;
- /** True if this is a currently unknown call that was not previously tracked by CallsManager,
- * and did not originate via the regular incoming/outgoing call code paths.
+ public CallSessionCookie(Call call, Session session) {
+ mSessionCall = call;
+ mSession = session;
+ }
+ }
+
+ /**
+ * One of CALL_DIRECTION_INCOMING, CALL_DIRECTION_OUTGOING, or CALL_DIRECTION_UNKNOWN
*/
- private boolean mIsUnknown;
+ private final int mCallDirection;
+
+ /**
+ * The post-dial digits that were dialed after the network portion of the number
+ */
+ private final String mPostDialDigits;
/**
* The time this call was created. Beyond logging and such, may also be used for bookkeeping
@@ -220,6 +260,8 @@
private PhoneAccountHandle mTargetPhoneAccountHandle;
+ private UserHandle mInitiatingUser;
+
private final Handler mHandler = new Handler(Looper.getMainLooper());
private final List<Call> mConferenceableCalls = new ArrayList<>();
@@ -298,6 +340,8 @@
private boolean mIsConference = false;
+ private final boolean mShouldAttachToExistingConnection;
+
private Call mParentCall = null;
private List<Call> mChildCalls = new LinkedList<>();
@@ -320,6 +364,8 @@
private final CallsManager mCallsManager;
private final TelecomSystem.SyncRoot mLock;
private final CallerInfoAsyncQueryFactory mCallerInfoAsyncQueryFactory;
+ private final String mId;
+ private Analytics.CallInfo mAnalytics;
private boolean mWasConferencePreviouslyMerged = false;
@@ -332,6 +378,11 @@
private boolean mIsLocallyDisconnecting = false;
/**
+ * Tracks the current call data usage as reported by the video provider.
+ */
+ private long mCallDataUsage = DATA_USAGE_NOT_SET;
+
+ /**
* Persists the specified parameters and initializes the new instance.
*
* @param context The context.
@@ -343,9 +394,13 @@
* {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER} flag.
* @param targetPhoneAccountHandle Account information to use for the call. This account must be
* one that was registered with the {@link PhoneAccount#CAPABILITY_CALL_PROVIDER} flag.
- * @param isIncoming True if this is an incoming call.
+ * @param callDirection one of CALL_DIRECTION_INCOMING, CALL_DIRECTION_OUTGOING,
+ * or CALL_DIRECTION_UNKNOWN.
+ * @param shouldAttachToExistingConnection Set to true to attach the call to an existing
+ * connection, regardless of whether it's incoming or outgoing.
*/
public Call(
+ String callId,
Context context,
CallsManager callsManager,
TelecomSystem.SyncRoot lock,
@@ -356,8 +411,10 @@
GatewayInfo gatewayInfo,
PhoneAccountHandle connectionManagerPhoneAccountHandle,
PhoneAccountHandle targetPhoneAccountHandle,
- boolean isIncoming,
+ int callDirection,
+ boolean shouldAttachToExistingConnection,
boolean isConference) {
+ mId = callId;
mState = isConference ? CallState.ACTIVE : CallState.NEW;
mContext = context;
mCallsManager = callsManager;
@@ -366,12 +423,18 @@
mContactsAsyncHelper = contactsAsyncHelper;
mCallerInfoAsyncQueryFactory = callerInfoAsyncQueryFactory;
setHandle(handle);
+ mPostDialDigits = handle != null
+ ? PhoneNumberUtils.extractPostDialPortion(handle.getSchemeSpecificPart()) : "";
mGatewayInfo = gatewayInfo;
setConnectionManagerPhoneAccount(connectionManagerPhoneAccountHandle);
setTargetPhoneAccount(targetPhoneAccountHandle);
- mIsIncoming = isIncoming;
+ mCallDirection = callDirection;
mIsConference = isConference;
+ mShouldAttachToExistingConnection = shouldAttachToExistingConnection
+ || callDirection == CALL_DIRECTION_INCOMING;
+
maybeLoadCannedSmsResponses();
+ mAnalytics = new Analytics.CallInfo();
Log.event(this, Log.Events.CREATED);
}
@@ -388,10 +451,14 @@
* {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER} flag.
* @param targetPhoneAccountHandle Account information to use for the call. This account must be
* one that was registered with the {@link PhoneAccount#CAPABILITY_CALL_PROVIDER} flag.
- * @param isIncoming True if this is an incoming call.
+ * @param callDirection one of CALL_DIRECTION_INCOMING, CALL_DIRECTION_OUTGOING,
+ * or CALL_DIRECTION_UNKNOWN
+ * @param shouldAttachToExistingConnection Set to true to attach the call to an existing
+ * connection, regardless of whether it's incoming or outgoing.
* @param connectTimeMillis The connection time of the call.
*/
Call(
+ String callId,
Context context,
CallsManager callsManager,
TelecomSystem.SyncRoot lock,
@@ -402,15 +469,17 @@
GatewayInfo gatewayInfo,
PhoneAccountHandle connectionManagerPhoneAccountHandle,
PhoneAccountHandle targetPhoneAccountHandle,
- boolean isIncoming,
+ int callDirection,
+ boolean shouldAttachToExistingConnection,
boolean isConference,
long connectTimeMillis) {
- this(context, callsManager, lock, repository, contactsAsyncHelper,
+ this(callId, context, callsManager, lock, repository, contactsAsyncHelper,
callerInfoAsyncQueryFactory, handle, gatewayInfo,
- connectionManagerPhoneAccountHandle, targetPhoneAccountHandle, isIncoming,
- isConference);
+ connectionManagerPhoneAccountHandle, targetPhoneAccountHandle, callDirection,
+ shouldAttachToExistingConnection, isConference);
mConnectTimeMillis = connectTimeMillis;
+ mAnalytics.setCallStartTime(connectTimeMillis);
}
public void addListener(Listener listener) {
@@ -423,6 +492,27 @@
}
}
+ public void initAnalytics() {
+ int analyticsDirection;
+ switch (mCallDirection) {
+ case CALL_DIRECTION_OUTGOING:
+ analyticsDirection = Analytics.OUTGOING_DIRECTION;
+ break;
+ case CALL_DIRECTION_INCOMING:
+ analyticsDirection = Analytics.INCOMING_DIRECTION;
+ break;
+ case CALL_DIRECTION_UNKNOWN:
+ case CALL_DIRECTION_UNDEFINED:
+ default:
+ analyticsDirection = Analytics.UNKNOWN_DIRECTION;
+ }
+ mAnalytics = Analytics.initiateCallAnalytics(mId, analyticsDirection);
+ }
+
+ public Analytics.CallInfo getAnalytics() {
+ return mAnalytics;
+ }
+
public void destroy() {
Log.event(this, Log.Events.DESTROYED);
}
@@ -438,7 +528,7 @@
return String.format(Locale.US, "[%s, %s, %s, %s, %s, childs(%d), has_parent(%b), [%s]]",
- System.identityHashCode(this),
+ mId,
CallState.toString(mState),
component,
Log.piiHandle(mHandle),
@@ -476,7 +566,8 @@
return sb.toString();
}
- int getState() {
+ @VisibleForTesting
+ public int getState() {
return mState;
}
@@ -503,6 +594,14 @@
}
/**
+ * Returns the unique ID for this call as it exists in Telecom.
+ * @return The call ID.
+ */
+ public String getId() {
+ return mId;
+ }
+
+ /**
* Sets the call state. Although there exists the notion of appropriate state transitions
* (see {@link CallState}), in practice those expectations break down when cellular systems
* misbehave and they do this very often. The result is that we do not enforce state transitions
@@ -527,6 +626,7 @@
// call from resetting active time when it goes in and out of
// ACTIVE/ON_HOLD
mConnectTimeMillis = System.currentTimeMillis();
+ mAnalytics.setCallStartTime(mConnectTimeMillis);
}
// Video state changes are normally tracked against history when a call is active.
@@ -539,6 +639,7 @@
mDisconnectTimeMillis = 0;
} else if (mState == CallState.DISCONNECTED) {
mDisconnectTimeMillis = System.currentTimeMillis();
+ mAnalytics.setCallEndTime(mDisconnectTimeMillis);
setLocallyDisconnecting(false);
fixParentAfterDisconnect();
}
@@ -601,7 +702,8 @@
return mRingbackRequested;
}
- boolean isConference() {
+ @VisibleForTesting
+ public boolean isConference() {
return mIsConference;
}
@@ -609,6 +711,10 @@
return mHandle;
}
+ public String getPostDialDigits() {
+ return mPostDialDigits;
+ }
+
int getHandlePresentation() {
return mHandlePresentation;
}
@@ -689,6 +795,7 @@
public void setDisconnectCause(DisconnectCause disconnectCause) {
// TODO: Consider combining this method with a setDisconnected() method that is totally
// separate from setState.
+ mAnalytics.setCallDisconnectCause(disconnectCause);
mDisconnectCause = disconnectCause;
}
@@ -696,7 +803,8 @@
return mDisconnectCause;
}
- boolean isEmergencyCall() {
+ @VisibleForTesting
+ public boolean isEmergencyCall() {
return mIsEmergencyCall;
}
@@ -711,7 +819,8 @@
return getHandle();
}
- GatewayInfo getGatewayInfo() {
+ @VisibleForTesting
+ public GatewayInfo getGatewayInfo() {
return mGatewayInfo;
}
@@ -719,11 +828,13 @@
mGatewayInfo = gatewayInfo;
}
- PhoneAccountHandle getConnectionManagerPhoneAccount() {
+ @VisibleForTesting
+ public PhoneAccountHandle getConnectionManagerPhoneAccount() {
return mConnectionManagerPhoneAccountHandle;
}
- void setConnectionManagerPhoneAccount(PhoneAccountHandle accountHandle) {
+ @VisibleForTesting
+ public void setConnectionManagerPhoneAccount(PhoneAccountHandle accountHandle) {
if (!Objects.equals(mConnectionManagerPhoneAccountHandle, accountHandle)) {
mConnectionManagerPhoneAccountHandle = accountHandle;
for (Listener l : mListeners) {
@@ -733,11 +844,13 @@
}
- PhoneAccountHandle getTargetPhoneAccount() {
+ @VisibleForTesting
+ public PhoneAccountHandle getTargetPhoneAccount() {
return mTargetPhoneAccountHandle;
}
- void setTargetPhoneAccount(PhoneAccountHandle accountHandle) {
+ @VisibleForTesting
+ public void setTargetPhoneAccount(PhoneAccountHandle accountHandle) {
if (!Objects.equals(mTargetPhoneAccountHandle, accountHandle)) {
mTargetPhoneAccountHandle = accountHandle;
for (Listener l : mListeners) {
@@ -746,8 +859,13 @@
}
}
- boolean isIncoming() {
- return mIsIncoming;
+ @VisibleForTesting
+ public boolean isIncoming() {
+ return mCallDirection == CALL_DIRECTION_INCOMING;
+ }
+
+ boolean shouldAttachToExistingConnection() {
+ return mShouldAttachToExistingConnection;
}
/**
@@ -755,7 +873,8 @@
* period since this call was added to the set pending outgoing calls, see
* mCreationTimeMillis.
*/
- long getAgeMillis() {
+ @VisibleForTesting
+ public long getAgeMillis() {
if (mState == CallState.DISCONNECTED &&
(mDisconnectCause.getCode() == DisconnectCause.REJECTED ||
mDisconnectCause.getCode() == DisconnectCause.MISSED)) {
@@ -808,23 +927,28 @@
}
}
- Call getParentCall() {
+ @VisibleForTesting
+ public Call getParentCall() {
return mParentCall;
}
- List<Call> getChildCalls() {
+ @VisibleForTesting
+ public List<Call> getChildCalls() {
return mChildCalls;
}
- boolean wasConferencePreviouslyMerged() {
+ @VisibleForTesting
+ public boolean wasConferencePreviouslyMerged() {
return mWasConferencePreviouslyMerged;
}
- Call getConferenceLevelActiveCall() {
+ @VisibleForTesting
+ public Call getConferenceLevelActiveCall() {
return mConferenceLevelActiveCall;
}
- ConnectionServiceWrapper getConnectionService() {
+ @VisibleForTesting
+ public ConnectionServiceWrapper getConnectionService() {
return mConnectionService;
}
@@ -837,13 +961,15 @@
return mContext;
}
- void setConnectionService(ConnectionServiceWrapper service) {
+ @VisibleForTesting
+ public void setConnectionService(ConnectionServiceWrapper service) {
Preconditions.checkNotNull(service);
clearConnectionService();
service.incrementAssociatedCallCount();
mConnectionService = service;
+ mAnalytics.setCallConnectionService(service.getComponentName().flattenToShortString());
mConnectionService.addCall(this);
}
@@ -868,20 +994,20 @@
private void processDirectToVoicemail() {
if (mDirectToVoicemailQueryPending) {
+ boolean shouldSendToVoicemail;
if (mCallerInfo != null && mCallerInfo.shouldSendToVoicemail) {
Log.i(this, "Directing call to voicemail: %s.", this);
// TODO: Once we move State handling from CallsManager to Call, we
// will not need to set STATE_RINGING state prior to calling reject.
- setState(CallState.RINGING, "directing to voicemail");
- reject(false, null);
+ shouldSendToVoicemail = true;
} else {
- // TODO: Make this class (not CallsManager) responsible for changing
- // the call state to STATE_RINGING.
-
- // TODO: Replace this with state transition to STATE_RINGING.
- for (Listener l : mListeners) {
- l.onSuccessfulIncomingCall(this);
- }
+ shouldSendToVoicemail = false;
+ }
+ // TODO: Make this class (not CallsManager) responsible for changing
+ // the call state to STATE_RINGING.
+ // TODO: Replace this with state transition to STATE_RINGING.
+ for (Listener l : mListeners) {
+ l.onSuccessfulIncomingCall(this, shouldSendToVoicemail);
}
mDirectToVoicemailQueryPending = false;
@@ -923,26 +1049,31 @@
mConferenceableCalls.add(idMapper.getCall(id));
}
- if (mIsUnknown) {
- for (Listener l : mListeners) {
- l.onSuccessfulUnknownCall(this, getStateFromConnectionState(connection.getState()));
- }
- } else if (mIsIncoming) {
- // We do not handle incoming calls immediately when they are verified by the connection
- // service. We allow the caller-info-query code to execute first so that we can read the
- // direct-to-voicemail property before deciding if we want to show the incoming call to
- // the user or if we want to reject the call.
- mDirectToVoicemailQueryPending = true;
+ switch (mCallDirection) {
+ case CALL_DIRECTION_INCOMING:
+ // We do not handle incoming calls immediately when they are verified by the
+ // connection service. We allow the caller-info-query code to execute first so
+ // that we can read the direct-to-voicemail property before deciding if we want
+ // to show the incoming call to the user or if we want to reject the call.
+ mDirectToVoicemailQueryPending = true;
- // Timeout the direct-to-voicemail lookup execution so that we dont wait too long before
- // showing the user the incoming call screen.
- mHandler.postDelayed(mDirectToVoicemailRunnable, Timeouts.getDirectToVoicemailMillis(
- mContext.getContentResolver()));
- } else {
- for (Listener l : mListeners) {
- l.onSuccessfulOutgoingCall(this,
- getStateFromConnectionState(connection.getState()));
- }
+ // Timeout the direct-to-voicemail lookup execution so that we dont wait too long
+ // before showing the user the incoming call screen.
+ mHandler.postDelayed(new DirectToVoicemailRunnable(Log.createSubsession()),
+ Timeouts.getDirectToVoicemailMillis(mContext.getContentResolver()));
+ break;
+ case CALL_DIRECTION_OUTGOING:
+ for (Listener l : mListeners) {
+ l.onSuccessfulOutgoingCall(this,
+ getStateFromConnectionState(connection.getState()));
+ }
+ break;
+ case CALL_DIRECTION_UNKNOWN:
+ for (Listener l : mListeners) {
+ l.onSuccessfulUnknownCall(this, getStateFromConnectionState(connection
+ .getState()));
+ }
+ break;
}
}
@@ -952,18 +1083,22 @@
setDisconnectCause(disconnectCause);
mCallsManager.markCallAsDisconnected(this, disconnectCause);
- if (mIsUnknown) {
- for (Listener listener : mListeners) {
- listener.onFailedUnknownCall(this);
- }
- } else if (mIsIncoming) {
- for (Listener listener : mListeners) {
- listener.onFailedIncomingCall(this);
- }
- } else {
- for (Listener listener : mListeners) {
- listener.onFailedOutgoingCall(this, disconnectCause);
- }
+ switch (mCallDirection) {
+ case CALL_DIRECTION_INCOMING:
+ for (Listener listener : mListeners) {
+ listener.onFailedIncomingCall(this);
+ }
+ break;
+ case CALL_DIRECTION_OUTGOING:
+ for (Listener listener : mListeners) {
+ listener.onFailedOutgoingCall(this, disconnectCause);
+ }
+ break;
+ case CALL_DIRECTION_UNKNOWN:
+ for (Listener listener : mListeners) {
+ listener.onFailedUnknownCall(this);
+ }
+ break;
}
}
@@ -1134,7 +1269,8 @@
}
/** Checks if this is a live call or not. */
- boolean isAlive() {
+ @VisibleForTesting
+ public boolean isAlive() {
switch (mState) {
case CallState.NEW:
case CallState.RINGING:
@@ -1172,7 +1308,8 @@
/**
* @return the uri of the contact associated with this call.
*/
- Uri getContactUri() {
+ @VisibleForTesting
+ public Uri getContactUri() {
if (mCallerInfo == null || !mCallerInfo.contactExists) {
return getHandle();
}
@@ -1217,7 +1354,8 @@
}
}
- void mergeConference() {
+ @VisibleForTesting
+ public void mergeConference() {
if (mConnectionService == null) {
Log.w(this, "merging conference calls without a connection service.");
} else if (can(Connection.CAPABILITY_MERGE_CONFERENCE)) {
@@ -1227,7 +1365,8 @@
}
}
- void swapConference() {
+ @VisibleForTesting
+ public void swapConference() {
if (mConnectionService == null) {
Log.w(this, "swapping conference calls without a connection service.");
} else if (can(Connection.CAPABILITY_SWAP_CONFERENCE)) {
@@ -1285,11 +1424,13 @@
}
}
- List<Call> getConferenceableCalls() {
+ @VisibleForTesting
+ public List<Call> getConferenceableCalls() {
return mConferenceableCalls;
}
- boolean can(int capability) {
+ @VisibleForTesting
+ public boolean can(int capability) {
return (mConnectionCapabilities & capability) == capability;
}
@@ -1416,15 +1557,28 @@
mCallerInfo = null;
if (!TextUtils.isEmpty(number)) {
Log.v(this, "Looking up information for: %s.", Log.piiHandle(number));
+ final Session subsession = Log.createSubsession();
mHandler.post(new Runnable() {
@Override
public void run() {
- mCallerInfoAsyncQueryFactory.startQuery(
- mQueryToken,
- mContext,
- number,
- mCallerInfoQueryListener,
- Call.this);
+ Session subsubsession = null;
+ try {
+ Log.continueSession(subsession, "CIAQF.sQ");
+ subsubsession = Log.createSubsession();
+ CallerInfoAsyncQuery value = mCallerInfoAsyncQueryFactory.startQuery(
+ mQueryToken, mContext, number, mCallerInfoQueryListener,
+ new CallSessionCookie(Call.this, subsubsession));
+ // If there is an exception in startQuery, then this assignment will never
+ // occur.
+ if(value != null) {
+ subsubsession = null;
+ }
+ } finally {
+ if (subsubsession != null) {
+ Log.cancelSubsession(subsubsession);
+ }
+ Log.endSession();
+ }
}
});
}
@@ -1446,15 +1600,25 @@
Log.i(this, "CallerInfo received for %s: %s", Log.piiHandle(mHandle), callerInfo);
if (mCallerInfo.contactDisplayPhotoUri != null) {
- Log.d(this, "Searching person uri %s for call %s",
- mCallerInfo.contactDisplayPhotoUri, this);
- mContactsAsyncHelper.startObtainPhotoAsync(
- token,
- mContext,
- mCallerInfo.contactDisplayPhotoUri,
- mPhotoLoadListener,
- this);
- // Do not call onCallerInfoChanged yet in this case. We call it in setPhoto().
+ Session subsession = null;
+ try {
+ subsession = Log.createSubsession();
+ Log.d(this, "Searching person uri %s for call %s",
+ mCallerInfo.contactDisplayPhotoUri, this);
+ mContactsAsyncHelper.startObtainPhotoAsync(
+ token,
+ mContext,
+ mCallerInfo.contactDisplayPhotoUri,
+ mPhotoLoadListener,
+ new CallSessionCookie(this, subsession));
+ // If there is an exception, then this assignment will never occur.
+ subsession = null;
+ // Do not call onCallerInfoChanged yet in this case. We call it in setPhoto().
+ } finally {
+ if(subsession != null) {
+ Log.cancelSubsession(subsession);
+ }
+ }
} else {
for (Listener l : mListeners) {
l.onCallerInfoChanged(this);
@@ -1489,7 +1653,9 @@
}
private void maybeLoadCannedSmsResponses() {
- if (mIsIncoming && isRespondViaSmsCapable() && !mCannedSmsResponsesLoadingStarted) {
+ if (mCallDirection == CALL_DIRECTION_INCOMING
+ && isRespondViaSmsCapable()
+ && !mCannedSmsResponsesLoadingStarted) {
Log.d(this, "maybeLoadCannedSmsResponses: starting task to load messages");
mCannedSmsResponsesLoadingStarted = true;
mCallsManager.getRespondViaSmsManager().loadCannedTextMessages(
@@ -1641,11 +1807,7 @@
}
public boolean isUnknown() {
- return mIsUnknown;
- }
-
- public void setIsUnknown(boolean isUnknown) {
- mIsUnknown = isUnknown;
+ return mCallDirection == CALL_DIRECTION_UNKNOWN;
}
/**
@@ -1666,6 +1828,22 @@
mIsLocallyDisconnecting = isLocallyDisconnecting;
}
+ /**
+ * @return user handle of user initiating the outgoing call.
+ */
+ public UserHandle getInitiatingUser() {
+ return mInitiatingUser;
+ }
+
+ /**
+ * Set the user handle of user initiating the outgoing call.
+ * @param initiatingUser
+ */
+ public void setInitiatingUser(UserHandle initiatingUser) {
+ Preconditions.checkNotNull(initiatingUser);
+ mInitiatingUser = initiatingUser;
+ }
+
static int getStateFromConnectionState(int state) {
switch (state) {
case Connection.STATE_INITIALIZING:
@@ -1694,4 +1872,22 @@
public boolean isDisconnected() {
return (getState() == CallState.DISCONNECTED || getState() == CallState.ABORTED);
}
+
+ /**
+ * Sets the call data usage for the call.
+ *
+ * @param callDataUsage The new call data usage (in bytes).
+ */
+ public void setCallDataUsage(long callDataUsage) {
+ mCallDataUsage = callDataUsage;
+ }
+
+ /**
+ * Returns the call data usage for the call.
+ *
+ * @return The call data usage (in bytes).
+ */
+ public long getCallDataUsage() {
+ return mCallDataUsage;
+ }
}
diff --git a/src/com/android/server/telecom/CallAudioManager.java b/src/com/android/server/telecom/CallAudioManager.java
index 23284e3..4fa08fa 100644
--- a/src/com/android/server/telecom/CallAudioManager.java
+++ b/src/com/android/server/telecom/CallAudioManager.java
@@ -16,31 +16,24 @@
package com.android.server.telecom;
-import android.app.ActivityManagerNative;
import android.content.Context;
-import android.content.pm.UserInfo;
import android.media.AudioManager;
import android.media.IAudioService;
-import android.os.Binder;
import android.os.Handler;
-import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.UserHandle;
import android.telecom.CallAudioState;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.SomeArgs;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
-import java.util.Objects;
-
/**
* This class manages audio modes, streams and other properties.
*/
-final class CallAudioManager extends CallsManagerListenerBase
- implements WiredHeadsetManager.Listener, DockManager.Listener {
+@VisibleForTesting
+public class CallAudioManager extends CallsManagerListenerBase {
private static final int STREAM_NONE = -1;
private static final String STREAM_DESCRIPTION_NONE = "STEAM_NONE";
@@ -61,9 +54,7 @@
private static final String MODE_DESCRIPTION_IN_COMMUNICATION = "MODE_IN_COMMUNICATION";
private static final int MSG_AUDIO_MANAGER_INITIALIZE = 0;
- private static final int MSG_AUDIO_MANAGER_TURN_ON_SPEAKER = 1;
private static final int MSG_AUDIO_MANAGER_ABANDON_AUDIO_FOCUS_FOR_CALL = 2;
- private static final int MSG_AUDIO_MANAGER_SET_MICROPHONE_MUTE = 3;
private static final int MSG_AUDIO_MANAGER_REQUEST_AUDIO_FOCUS_FOR_CALL = 4;
private static final int MSG_AUDIO_MANAGER_SET_MODE = 5;
@@ -73,124 +64,100 @@
@Override
public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_AUDIO_MANAGER_INITIALIZE: {
- mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
- break;
+ SomeArgs args = (SomeArgs) msg.obj;
+ int arg2 = 0;
+ try {
+ if (args != null) {
+ Session subsession = (Session) args.arg1;
+ Log.continueSession(subsession, "CAM.hM_" + msg.what);
+ arg2 = (int) args.arg2;
}
- case MSG_AUDIO_MANAGER_TURN_ON_SPEAKER: {
- boolean on = (msg.arg1 != 0);
- // Wired headset and earpiece work the same way
- if (mAudioManager.isSpeakerphoneOn() != on) {
- Log.i(this, "turning speaker phone %s", on);
- mAudioManager.setSpeakerphoneOn(on);
+ switch (msg.what) {
+ case MSG_AUDIO_MANAGER_INITIALIZE: {
+ mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+ break;
}
- break;
- }
- case MSG_AUDIO_MANAGER_ABANDON_AUDIO_FOCUS_FOR_CALL: {
- mAudioManager.abandonAudioFocusForCall();
- break;
- }
- case MSG_AUDIO_MANAGER_SET_MICROPHONE_MUTE: {
- boolean mute = (msg.arg1 != 0);
- if (mute != mAudioManager.isMicrophoneMute()) {
- IAudioService audio = getAudioService();
- Log.i(this, "changing microphone mute state to: %b [serviceIsNull=%b]",
- mute, audio == null);
- if (audio != null) {
- try {
- // We use the audio service directly here so that we can specify
- // the current user. Telecom runs in the system_server process which
- // may run as a separate user from the foreground user. If we
- // used AudioManager directly, we would change mute for the system's
- // user and not the current foreground, which we want to avoid.
- audio.setMicrophoneMute(
- mute, mContext.getOpPackageName(), getCurrentUserId());
+ case MSG_AUDIO_MANAGER_ABANDON_AUDIO_FOCUS_FOR_CALL: {
+ mAudioManager.abandonAudioFocusForCall();
+ break;
+ }
+ case MSG_AUDIO_MANAGER_REQUEST_AUDIO_FOCUS_FOR_CALL: {
+ int stream = arg2;
+ mAudioManager.requestAudioFocusForCall(
+ stream,
+ AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
+ break;
+ }
+ case MSG_AUDIO_MANAGER_SET_MODE: {
+ int newMode = arg2;
+ int oldMode = mAudioManager.getMode();
+ Log.v(this, "Request to change audio mode from %s to %s", modeToString(oldMode),
+ modeToString(newMode));
- } catch (RemoteException e) {
- Log.e(this, e, "Remote exception while toggling mute.");
+ if (oldMode != newMode) {
+ if (oldMode == AudioManager.MODE_IN_CALL &&
+ newMode == AudioManager.MODE_RINGTONE) {
+ Log.i(this, "Transition from IN_CALL -> RINGTONE."
+ + " Resetting to NORMAL first.");
+ mAudioManager.setMode(AudioManager.MODE_NORMAL);
}
- // TODO: Check microphone state after attempting to set to ensure that
- // our state corroborates AudioManager's state.
+ mAudioManager.setMode(newMode);
+ synchronized (mLock) {
+ mMostRecentlyUsedMode = newMode;
+ }
}
+ break;
}
-
- break;
+ default:
+ break;
}
- case MSG_AUDIO_MANAGER_REQUEST_AUDIO_FOCUS_FOR_CALL: {
- int stream = msg.arg1;
- mAudioManager.requestAudioFocusForCall(
- stream,
- AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
- break;
- }
- case MSG_AUDIO_MANAGER_SET_MODE: {
- int newMode = msg.arg1;
- int oldMode = mAudioManager.getMode();
- Log.v(this, "Request to change audio mode from %s to %s", modeToString(oldMode),
- modeToString(newMode));
-
- if (oldMode != newMode) {
- if (oldMode == AudioManager.MODE_IN_CALL &&
- newMode == AudioManager.MODE_RINGTONE) {
- Log.i(this, "Transition from IN_CALL -> RINGTONE."
- + " Resetting to NORMAL first.");
- mAudioManager.setMode(AudioManager.MODE_NORMAL);
- }
- mAudioManager.setMode(newMode);
- synchronized (mLock) {
- mMostRecentlyUsedMode = newMode;
- }
- }
- break;
- }
- default:
- break;
+ } finally {
+ Log.endSession();
+ args.recycle();
}
}
};
+ public interface AudioServiceFactory {
+ IAudioService getAudioService();
+ }
+
private final Context mContext;
private final TelecomSystem.SyncRoot mLock;
- private final StatusBarNotifier mStatusBarNotifier;
- private final BluetoothManager mBluetoothManager;
- private final WiredHeadsetManager mWiredHeadsetManager;
- private final DockManager mDockManager;
private final CallsManager mCallsManager;
+ private final CallAudioRouteStateMachine mCallAudioRouteStateMachine;
- private CallAudioState mCallAudioState;
private int mAudioFocusStreamType;
private boolean mIsRinging;
private boolean mIsTonePlaying;
- private boolean mWasSpeakerOn;
private int mMostRecentlyUsedMode = AudioManager.MODE_IN_CALL;
private Call mCallToSpeedUpMTAudio = null;
- CallAudioManager(
+ public CallAudioManager(
Context context,
TelecomSystem.SyncRoot lock,
- StatusBarNotifier statusBarNotifier,
- WiredHeadsetManager wiredHeadsetManager,
- DockManager dockManager,
- CallsManager callsManager) {
+ CallsManager callsManager,
+ CallAudioRouteStateMachine callAudioRouteStateMachine) {
mContext = context;
mLock = lock;
- mAudioManagerHandler.obtainMessage(MSG_AUDIO_MANAGER_INITIALIZE, 0, 0).sendToTarget();
- mStatusBarNotifier = statusBarNotifier;
- mBluetoothManager = new BluetoothManager(context, this);
- mWiredHeadsetManager = wiredHeadsetManager;
+ mAudioManagerHandler.obtainMessage(MSG_AUDIO_MANAGER_INITIALIZE,
+ setArgs(0)).sendToTarget();
mCallsManager = callsManager;
-
- mWiredHeadsetManager.addListener(this);
- mDockManager = dockManager;
- mDockManager.addListener(this);
-
- saveAudioState(getInitialAudioState(null));
mAudioFocusStreamType = STREAM_NONE;
+
+ mCallAudioRouteStateMachine = callAudioRouteStateMachine;
}
- CallAudioState getCallAudioState() {
- return mCallAudioState;
+ private SomeArgs setArgs(int arg) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = Log.createSubsession();
+ args.arg2 = arg;
+ return args;
+ }
+
+ @VisibleForTesting
+ public CallAudioState getCallAudioState() {
+ return mCallAudioRouteStateMachine.getCurrentCallAudioState();
}
@Override
@@ -201,8 +168,8 @@
if (hasFocus() && getForegroundCall() == call) {
if (!call.isIncoming()) {
// Unmute new outgoing call.
- setSystemAudioState(false, mCallAudioState.getRoute(),
- mCallAudioState.getSupportedRouteMask());
+ mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+ CallAudioRouteStateMachine.MUTE_OFF);
}
}
}
@@ -210,13 +177,14 @@
@Override
public void onCallRemoved(Call call) {
Log.v(this, "onCallRemoved");
+ if (mCallsManager.getCalls().isEmpty()) {
+ Log.v(this, "all calls removed, resetting system audio to default state");
+ mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+ CallAudioRouteStateMachine.REINITIALIZE);
+ }
+
// If we didn't already have focus, there's nothing to do.
if (hasFocus()) {
- if (mCallsManager.getCalls().isEmpty()) {
- Log.v(this, "all calls removed, resetting system audio to default state");
- setInitialAudioState(null, false /* force */);
- mWasSpeakerOn = false;
- }
updateAudioStreamAndMode(call);
}
}
@@ -230,19 +198,12 @@
@Override
public void onIncomingCallAnswered(Call call) {
Log.v(this, "onIncomingCallAnswered");
- int route = mCallAudioState.getRoute();
- // We do two things:
- // (1) If this is the first call, then we can to turn on bluetooth if available.
- // (2) Unmute the audio for the new incoming call.
- boolean isOnlyCall = mCallsManager.getCalls().size() == 1;
- if (isOnlyCall && mBluetoothManager.isBluetoothAvailable()) {
- mBluetoothManager.connectBluetoothAudio();
- route = CallAudioState.ROUTE_BLUETOOTH;
+ if (mCallsManager.getCalls().size() == 1) {
+ mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+ CallAudioRouteStateMachine.SWITCH_FOCUS, CallAudioRouteStateMachine.HAS_FOCUS);
}
- setSystemAudioState(false /* isMute */, route, mCallAudioState.getSupportedRouteMask());
-
if (call.can(android.telecom.Call.Details.CAPABILITY_SPEED_UP_MT_AUDIO)) {
Log.v(this, "Speed up audio setup for IMS MT call.");
mCallToSpeedUpMTAudio = call;
@@ -262,73 +223,9 @@
updateAudioStreamAndMode(call);
}
- /**
- * Updates the audio route when the headset plugged in state changes. For example, if audio is
- * being routed over speakerphone and a headset is plugged in then switch to wired headset.
- */
- @Override
- public void onWiredHeadsetPluggedInChanged(boolean oldIsPluggedIn, boolean newIsPluggedIn) {
- // This can happen even when there are no calls and we don't have focus.
- if (!hasFocus()) {
- return;
- }
-
- boolean isCurrentlyWiredHeadset = mCallAudioState.getRoute()
- == CallAudioState.ROUTE_WIRED_HEADSET;
-
- int newRoute = mCallAudioState.getRoute(); // start out with existing route
- if (newIsPluggedIn) {
- newRoute = CallAudioState.ROUTE_WIRED_HEADSET;
- } else if (isCurrentlyWiredHeadset) {
- Call call = getForegroundCall();
- boolean hasLiveCall = call != null && call.isAlive();
-
- if (hasLiveCall) {
- // In order of preference when a wireless headset is unplugged.
- if (mWasSpeakerOn) {
- newRoute = CallAudioState.ROUTE_SPEAKER;
- } else {
- newRoute = CallAudioState.ROUTE_EARPIECE;
- }
-
- // We don't automatically connect to bluetooth when user unplugs their wired headset
- // and they were previously using the wired. Wired and earpiece are effectively the
- // same choice in that they replace each other as an option when wired headsets
- // are plugged in and out. This means that keeping it earpiece is a bit more
- // consistent with the status quo. Bluetooth also has more danger associated with
- // choosing it in the wrong curcumstance because bluetooth devices can be
- // semi-public (like in a very-occupied car) where earpiece doesn't carry that risk.
- }
- }
-
- // We need to call this every time even if we do not change the route because the supported
- // routes changed either to include or not include WIRED_HEADSET.
- setSystemAudioState(mCallAudioState.isMuted(), newRoute, calculateSupportedRoutes());
- }
-
- @Override
- public void onDockChanged(boolean isDocked) {
- // This can happen even when there are no calls and we don't have focus.
- if (!hasFocus()) {
- return;
- }
-
- if (isDocked) {
- // Device just docked, turn to speakerphone. Only do so if the route is currently
- // earpiece so that we dont switch out of a BT headset or a wired headset.
- if (mCallAudioState.getRoute() == CallAudioState.ROUTE_EARPIECE) {
- setAudioRoute(CallAudioState.ROUTE_SPEAKER);
- }
- } else {
- // Device just undocked, remove from speakerphone if possible.
- if (mCallAudioState.getRoute() == CallAudioState.ROUTE_SPEAKER) {
- setAudioRoute(CallAudioState.ROUTE_WIRED_OR_EARPIECE);
- }
- }
- }
-
void toggleMute() {
- mute(!mCallAudioState.isMuted());
+ mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+ CallAudioRouteStateMachine.TOGGLE_MUTE);
}
void mute(boolean shouldMute) {
@@ -344,15 +241,8 @@
Log.v(this, "ignoring mute for emergency call");
}
- if (mCallAudioState.isMuted() != shouldMute) {
- // We user CallsManager's foreground call so that we dont ignore ringing calls
- // for logging purposes
- Log.event(mCallsManager.getForegroundCall(), Log.Events.MUTE,
- shouldMute ? "on" : "off");
-
- setSystemAudioState(shouldMute, mCallAudioState.getRoute(),
- mCallAudioState.getSupportedRouteMask());
- }
+ mCallAudioRouteStateMachine.sendMessageWithSessionInfo(shouldMute
+ ? CallAudioRouteStateMachine.MUTE_ON : CallAudioRouteStateMachine.MUTE_OFF);
}
/**
@@ -361,28 +251,30 @@
* @param route The new audio route to use. See {@link CallAudioState}.
*/
void setAudioRoute(int route) {
- // This can happen even when there are no calls and we don't have focus.
- if (!hasFocus()) {
- return;
- }
-
Log.v(this, "setAudioRoute, route: %s", CallAudioState.audioRouteToString(route));
-
- // Change ROUTE_WIRED_OR_EARPIECE to a single entry.
- int newRoute = selectWiredOrEarpiece(route, mCallAudioState.getSupportedRouteMask());
-
- // If route is unsupported, do nothing.
- if ((mCallAudioState.getSupportedRouteMask() | newRoute) == 0) {
- Log.wtf(this, "Asking to set to a route that is unsupported: %d", newRoute);
- return;
- }
-
- if (mCallAudioState.getRoute() != newRoute) {
- // Remember the new speaker state so it can be restored when the user plugs and unplugs
- // a headset.
- mWasSpeakerOn = newRoute == CallAudioState.ROUTE_SPEAKER;
- setSystemAudioState(mCallAudioState.isMuted(), newRoute,
- mCallAudioState.getSupportedRouteMask());
+ switch (route) {
+ case CallAudioState.ROUTE_BLUETOOTH:
+ mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+ CallAudioRouteStateMachine.SWITCH_BLUETOOTH);
+ return;
+ case CallAudioState.ROUTE_SPEAKER:
+ mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+ CallAudioRouteStateMachine.SWITCH_SPEAKER);
+ return;
+ case CallAudioState.ROUTE_WIRED_HEADSET:
+ mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+ CallAudioRouteStateMachine.SWITCH_HEADSET);
+ return;
+ case CallAudioState.ROUTE_EARPIECE:
+ mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+ CallAudioRouteStateMachine.SWITCH_EARPIECE);
+ return;
+ case CallAudioState.ROUTE_WIRED_OR_EARPIECE:
+ mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+ CallAudioRouteStateMachine.SWITCH_WIRED_OR_EARPIECE);
+ return;
+ default:
+ Log.wtf(this, "Invalid route specified: %d", route);
}
}
@@ -392,7 +284,8 @@
* @param call The call which changed ringing state.
* @param isRinging {@code true} if the call is ringing, {@code false} otherwise.
*/
- void setIsRinging(Call call, boolean isRinging) {
+ @VisibleForTesting
+ public void setIsRinging(Call call, boolean isRinging) {
if (mIsRinging != isRinging) {
Log.i(this, "setIsRinging %b -> %b (call = %s)", mIsRinging, isRinging, call);
mIsRinging = isRinging;
@@ -415,44 +308,6 @@
}
}
- /**
- * Updates the audio routing according to the bluetooth state.
- */
- void onBluetoothStateChange(BluetoothManager bluetoothManager) {
- // This can happen even when there are no calls and we don't have focus.
- if (!hasFocus()) {
- return;
- }
-
- int supportedRoutes = calculateSupportedRoutes();
- int newRoute = mCallAudioState.getRoute();
- if (bluetoothManager.isBluetoothAudioConnectedOrPending()) {
- newRoute = CallAudioState.ROUTE_BLUETOOTH;
- } else if (mCallAudioState.getRoute() == CallAudioState.ROUTE_BLUETOOTH) {
- newRoute = selectWiredOrEarpiece(CallAudioState.ROUTE_WIRED_OR_EARPIECE,
- supportedRoutes);
- // Do not switch to speaker when bluetooth disconnects.
- mWasSpeakerOn = false;
- }
-
- setSystemAudioState(mCallAudioState.isMuted(), newRoute, supportedRoutes);
- }
-
- boolean isBluetoothAudioOn() {
- return mBluetoothManager.isBluetoothAudioConnected();
- }
-
- boolean isBluetoothDeviceAvailable() {
- return mBluetoothManager.isBluetoothAvailable();
- }
-
- private void saveAudioState(CallAudioState callAudioState) {
- mCallAudioState = callAudioState;
- mStatusBarNotifier.notifyMute(mCallAudioState.isMuted());
- mStatusBarNotifier.notifySpeakerphone(mCallAudioState.getRoute()
- == CallAudioState.ROUTE_SPEAKER);
- }
-
private void onCallUpdated(Call call) {
updateAudioStreamAndMode(call);
if (call != null && call.getState() == CallState.ACTIVE &&
@@ -461,70 +316,6 @@
}
}
- private void setSystemAudioState(boolean isMuted, int route, int supportedRouteMask) {
- setSystemAudioState(false /* force */, isMuted, route, supportedRouteMask);
- }
-
- private void setSystemAudioState(
- boolean force, boolean isMuted, int route, int supportedRouteMask) {
- if (!hasFocus()) {
- return;
- }
-
- CallAudioState oldAudioState = mCallAudioState;
- saveAudioState(new CallAudioState(isMuted, route, supportedRouteMask));
- if (!force && Objects.equals(oldAudioState, mCallAudioState)) {
- return;
- }
-
- Log.i(this, "setSystemAudioState: changing from %s to %s", oldAudioState, mCallAudioState);
- Log.event(mCallsManager.getForegroundCall(), Log.Events.AUDIO_ROUTE,
- CallAudioState.audioRouteToString(mCallAudioState.getRoute()));
-
- mAudioManagerHandler.obtainMessage(
- MSG_AUDIO_MANAGER_SET_MICROPHONE_MUTE,
- mCallAudioState.isMuted() ? 1 : 0,
- 0)
- .sendToTarget();
-
- // Audio route.
- if (mCallAudioState.getRoute() == CallAudioState.ROUTE_BLUETOOTH) {
- turnOnSpeaker(false);
- turnOnBluetooth(true);
- } else if (mCallAudioState.getRoute() == CallAudioState.ROUTE_SPEAKER) {
- turnOnBluetooth(false);
- turnOnSpeaker(true);
- } else if (mCallAudioState.getRoute() == CallAudioState.ROUTE_EARPIECE ||
- mCallAudioState.getRoute() == CallAudioState.ROUTE_WIRED_HEADSET) {
- turnOnBluetooth(false);
- turnOnSpeaker(false);
- }
-
- if (!oldAudioState.equals(mCallAudioState)) {
- mCallsManager.onCallAudioStateChanged(oldAudioState, mCallAudioState);
- updateAudioForForegroundCall();
- }
- }
-
- private void turnOnSpeaker(boolean on) {
- mAudioManagerHandler.obtainMessage(MSG_AUDIO_MANAGER_TURN_ON_SPEAKER, on ? 1 : 0, 0)
- .sendToTarget();
- }
-
- private void turnOnBluetooth(boolean on) {
- if (mBluetoothManager.isBluetoothAvailable()) {
- boolean isAlreadyOn = mBluetoothManager.isBluetoothAudioConnectedOrPending();
- if (on != isAlreadyOn) {
- Log.i(this, "connecting bluetooth %s", on);
- if (on) {
- mBluetoothManager.connectBluetoothAudio();
- } else {
- mBluetoothManager.disconnectBluetoothAudio();
- }
- }
- }
- }
-
private void updateAudioStreamAndMode() {
updateAudioStreamAndMode(null /* call */);
}
@@ -533,7 +324,6 @@
Log.i(this, "updateAudioStreamAndMode : mIsRinging: %b, mIsTonePlaying: %b, call: %s",
mIsRinging, mIsTonePlaying, callToUpdate);
- boolean wasVoiceCall = mAudioFocusStreamType == AudioManager.STREAM_VOICE_CALL;
if (mIsRinging) {
Log.i(this, "updateAudioStreamAndMode : ringing");
requestAudioFocusAndSetMode(AudioManager.STREAM_RING, AudioManager.MODE_RINGTONE);
@@ -576,14 +366,6 @@
// focus will be correctly abandoned by the if clause above.
}
}
-
- boolean isVoiceCall = mAudioFocusStreamType == AudioManager.STREAM_VOICE_CALL;
-
- // If we transition from not a voice call to a voice call, we need to set an initial audio
- // state for the call.
- if (!wasVoiceCall && isVoiceCall) {
- setInitialAudioState(callToUpdate, true /* force */);
- }
}
private void requestAudioFocusAndSetMode(int stream, int mode) {
@@ -597,13 +379,12 @@
if (mAudioFocusStreamType != stream) {
Log.i(this, "requestAudioFocusAndSetMode : requesting stream: %s -> %s",
streamTypeToString(mAudioFocusStreamType), streamTypeToString(stream));
- mAudioManagerHandler.obtainMessage(
- MSG_AUDIO_MANAGER_REQUEST_AUDIO_FOCUS_FOR_CALL,
- stream,
- 0)
- .sendToTarget();
+ mAudioManagerHandler.obtainMessage(MSG_AUDIO_MANAGER_REQUEST_AUDIO_FOCUS_FOR_CALL,
+ setArgs(stream)).sendToTarget();
}
mAudioFocusStreamType = stream;
+ mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+ CallAudioRouteStateMachine.SWITCH_FOCUS, CallAudioRouteStateMachine.HAS_FOCUS);
setMode(mode);
}
@@ -612,11 +393,13 @@
if (hasFocus()) {
setMode(AudioManager.MODE_NORMAL);
Log.v(this, "abandoning audio focus");
- mAudioManagerHandler.obtainMessage(MSG_AUDIO_MANAGER_ABANDON_AUDIO_FOCUS_FOR_CALL, 0, 0)
- .sendToTarget();
+ mAudioManagerHandler.obtainMessage(MSG_AUDIO_MANAGER_ABANDON_AUDIO_FOCUS_FOR_CALL,
+ setArgs(0)).sendToTarget();
mAudioFocusStreamType = STREAM_NONE;
mCallToSpeedUpMTAudio = null;
}
+ mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+ CallAudioRouteStateMachine.SWITCH_FOCUS, CallAudioRouteStateMachine.NO_FOCUS);
}
/**
@@ -626,80 +409,15 @@
*/
private void setMode(int newMode) {
Preconditions.checkState(hasFocus());
- mAudioManagerHandler.obtainMessage(MSG_AUDIO_MANAGER_SET_MODE, newMode, 0).sendToTarget();
- }
-
- private int selectWiredOrEarpiece(int route, int supportedRouteMask) {
- // Since they are mutually exclusive and one is ALWAYS valid, we allow a special input of
- // ROUTE_WIRED_OR_EARPIECE so that callers dont have to make a call to check which is
- // supported before calling setAudioRoute.
- if (route == CallAudioState.ROUTE_WIRED_OR_EARPIECE) {
- route = CallAudioState.ROUTE_WIRED_OR_EARPIECE & supportedRouteMask;
- if (route == 0) {
- Log.wtf(this, "One of wired headset or earpiece should always be valid.");
- // assume earpiece in this case.
- route = CallAudioState.ROUTE_EARPIECE;
- }
- }
- return route;
- }
-
- private int calculateSupportedRoutes() {
- int routeMask = CallAudioState.ROUTE_SPEAKER;
-
- if (mWiredHeadsetManager.isPluggedIn()) {
- routeMask |= CallAudioState.ROUTE_WIRED_HEADSET;
- } else {
- routeMask |= CallAudioState.ROUTE_EARPIECE;
- }
-
- if (mBluetoothManager.isBluetoothAvailable()) {
- routeMask |= CallAudioState.ROUTE_BLUETOOTH;
- }
-
- return routeMask;
- }
-
- private CallAudioState getInitialAudioState(Call call) {
- int supportedRouteMask = calculateSupportedRoutes();
- int route = selectWiredOrEarpiece(
- CallAudioState.ROUTE_WIRED_OR_EARPIECE, supportedRouteMask);
-
- // We want the UI to indicate that "bluetooth is in use" in two slightly different cases:
- // (a) The obvious case: if a bluetooth headset is currently in use for an ongoing call.
- // (b) The not-so-obvious case: if an incoming call is ringing, and we expect that audio
- // *will* be routed to a bluetooth headset once the call is answered. In this case, just
- // check if the headset is available. Note this only applies when we are dealing with
- // the first call.
- if (call != null && mBluetoothManager.isBluetoothAvailable()) {
- switch(call.getState()) {
- case CallState.ACTIVE:
- case CallState.ON_HOLD:
- case CallState.DIALING:
- case CallState.CONNECTING:
- case CallState.RINGING:
- route = CallAudioState.ROUTE_BLUETOOTH;
- break;
- default:
- break;
- }
- }
-
- return new CallAudioState(false, route, supportedRouteMask);
- }
-
- private void setInitialAudioState(Call call, boolean force) {
- CallAudioState audioState = getInitialAudioState(call);
- Log.i(this, "setInitialAudioState : audioState = %s, call = %s", audioState, call);
- setSystemAudioState(
- force, audioState.isMuted(), audioState.getRoute(),
- audioState.getSupportedRouteMask());
+ mAudioManagerHandler.obtainMessage(MSG_AUDIO_MANAGER_SET_MODE,
+ setArgs(newMode)).sendToTarget();
}
private void updateAudioForForegroundCall() {
Call call = mCallsManager.getForegroundCall();
if (call != null && call.getConnectionService() != null) {
- call.getConnectionService().onCallAudioStateChanged(call, mCallAudioState);
+ call.getConnectionService().onCallAudioStateChanged(call,
+ mCallAudioRouteStateMachine.getCurrentCallAudioState());
}
}
@@ -727,23 +445,6 @@
return mAudioFocusStreamType != STREAM_NONE;
}
- private IAudioService getAudioService() {
- return IAudioService.Stub.asInterface(ServiceManager.getService(Context.AUDIO_SERVICE));
- }
-
- private int getCurrentUserId() {
- final long ident = Binder.clearCallingIdentity();
- try {
- UserInfo currentUser = ActivityManagerNative.getDefault().getCurrentUser();
- return currentUser.id;
- } catch (RemoteException e) {
- // Activity manager not running, nothing we can do assume user 0.
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- return UserHandle.USER_OWNER;
- }
-
/**
* Translates an {@link AudioManager} stream type to a human-readable string description.
*
@@ -806,23 +507,10 @@
* @param pw The {@code IndentingPrintWriter} to write the state to.
*/
public void dump(IndentingPrintWriter pw) {
- pw.println("mAudioState: " + mCallAudioState);
- pw.println("mBluetoothManager:");
- pw.increaseIndent();
- mBluetoothManager.dump(pw);
- pw.decreaseIndent();
- if (mWiredHeadsetManager != null) {
- pw.println("mWiredHeadsetManager:");
- pw.increaseIndent();
- mWiredHeadsetManager.dump(pw);
- pw.decreaseIndent();
- } else {
- pw.println("mWiredHeadsetManager: null");
- }
+ pw.println("mAudioState: " + mCallAudioRouteStateMachine.getCurrentCallAudioState());
pw.println("mAudioFocusStreamType: " + streamTypeToString(mAudioFocusStreamType));
pw.println("mIsRinging: " + mIsRinging);
pw.println("mIsTonePlaying: " + mIsTonePlaying);
- pw.println("mWasSpeakerOn: " + mWasSpeakerOn);
pw.println("mMostRecentlyUsedMode: " + modeToString(mMostRecentlyUsedMode));
}
-}
+}
\ No newline at end of file
diff --git a/src/com/android/server/telecom/CallAudioRoutePeripheralAdapter.java b/src/com/android/server/telecom/CallAudioRoutePeripheralAdapter.java
new file mode 100644
index 0000000..45e5afa
--- /dev/null
+++ b/src/com/android/server/telecom/CallAudioRoutePeripheralAdapter.java
@@ -0,0 +1,78 @@
+/*
+ * 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.server.telecom;
+
+/**
+ * A class that acts as a listener to things that could change call audio routing, namely
+ * bluetooth status, wired headset status, and dock status.
+ */
+public class CallAudioRoutePeripheralAdapter implements BluetoothManager.BluetoothStateListener,
+ WiredHeadsetManager.Listener, DockManager.Listener {
+ private final CallAudioRouteStateMachine mCallAudioRouteStateMachine;
+ private final BluetoothManager mBluetoothManager;
+
+ public CallAudioRoutePeripheralAdapter(
+ CallAudioRouteStateMachine callAudioRouteStateMachine,
+ BluetoothManager bluetoothManager,
+ WiredHeadsetManager wiredHeadsetManager,
+ DockManager dockManager) {
+ mCallAudioRouteStateMachine = callAudioRouteStateMachine;
+ mBluetoothManager = bluetoothManager;
+
+ mBluetoothManager.setBluetoothStateListener(this);
+ wiredHeadsetManager.addListener(this);
+ dockManager.addListener(this);
+ }
+
+ @Override
+ public void onBluetoothStateChange(BluetoothManager bluetoothManager) {
+ if (bluetoothManager.isBluetoothAvailable()) {
+ mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+ CallAudioRouteStateMachine.CONNECT_BLUETOOTH);
+ } else {
+ mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+ CallAudioRouteStateMachine.DISCONNECT_BLUETOOTH);
+ }
+ }
+
+ public boolean isBluetoothAudioOn() {
+ return mBluetoothManager.isBluetoothAudioConnected();
+ }
+
+ /**
+ * Updates the audio route when the headset plugged in state changes. For example, if audio is
+ * being routed over speakerphone and a headset is plugged in then switch to wired headset.
+ */
+ @Override
+ public void onWiredHeadsetPluggedInChanged(boolean oldIsPluggedIn, boolean newIsPluggedIn) {
+ if (!oldIsPluggedIn && newIsPluggedIn) {
+ mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+ CallAudioRouteStateMachine.CONNECT_WIRED_HEADSET);
+ } else if (oldIsPluggedIn && !newIsPluggedIn){
+ mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+ CallAudioRouteStateMachine.DISCONNECT_WIRED_HEADSET);
+ }
+ }
+
+ @Override
+ public void onDockChanged(boolean isDocked) {
+ mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+ isDocked ? CallAudioRouteStateMachine.CONNECT_DOCK
+ : CallAudioRouteStateMachine.DISCONNECT_DOCK
+ );
+ }
+}
diff --git a/src/com/android/server/telecom/CallAudioRouteStateMachine.java b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
new file mode 100644
index 0000000..a774130
--- /dev/null
+++ b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
@@ -0,0 +1,1157 @@
+/*
+ * 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.server.telecom;
+
+
+import android.app.ActivityManagerNative;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.media.AudioManager;
+import android.media.IAudioService;
+import android.os.Binder;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.telecom.CallAudioState;
+
+import com.android.internal.util.IState;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+
+import java.util.HashMap;
+
+/**
+ * This class describes the available routes of a call as a state machine.
+ * Transitions are caused solely by the commands sent as messages. Possible values for msg.what
+ * are defined as event constants in this file.
+ *
+ * The eight states are all instances of the abstract base class, {@link AudioState}. Each state
+ * is a combination of one of the four audio routes (earpiece, wired headset, bluetooth, and
+ * speakerphone) and audio focus status (active or quiescent).
+ *
+ * Messages are processed first by the processMessage method in the base class, AudioState.
+ * Any messages not completely handled by AudioState are further processed by the same method in
+ * the route-specific abstract classes: {@link EarpieceRoute}, {@link HeadsetRoute},
+ * {@link BluetoothRoute}, and {@link SpeakerRoute}. Finally, messages that are not handled at
+ * this level are then processed by the classes corresponding to the state instances themselves.
+ *
+ * There are several variables carrying additional state. These include:
+ * mAvailableRoutes: A bitmask describing which audio routes are available
+ * mWasOnSpeaker: A boolean indicating whether we should switch to speakerphone after disconnecting
+ * from a wired headset
+ * mIsMuted: a boolean indicating whether the audio is muted
+ */
+public class CallAudioRouteStateMachine extends StateMachine {
+ /** Direct the audio stream through the device's earpiece. */
+ public static final int ROUTE_EARPIECE = CallAudioState.ROUTE_EARPIECE;
+
+ /** Direct the audio stream through Bluetooth. */
+ public static final int ROUTE_BLUETOOTH = CallAudioState.ROUTE_BLUETOOTH;
+
+ /** Direct the audio stream through a wired headset. */
+ public static final int ROUTE_WIRED_HEADSET = CallAudioState.ROUTE_WIRED_HEADSET;
+
+ /** Direct the audio stream through the device's speakerphone. */
+ public static final int ROUTE_SPEAKER = CallAudioState.ROUTE_SPEAKER;
+
+ /** Valid values for msg.what */
+ public static final int CONNECT_WIRED_HEADSET = 1;
+ public static final int DISCONNECT_WIRED_HEADSET = 2;
+ public static final int CONNECT_BLUETOOTH = 3;
+ public static final int DISCONNECT_BLUETOOTH = 4;
+ public static final int CONNECT_DOCK = 5;
+ public static final int DISCONNECT_DOCK = 6;
+
+ public static final int SWITCH_EARPIECE = 1001;
+ public static final int SWITCH_BLUETOOTH = 1002;
+ public static final int SWITCH_HEADSET = 1003;
+ public static final int SWITCH_SPEAKER = 1004;
+ public static final int SWITCH_WIRED_OR_EARPIECE = 1005;
+
+ public static final int REINITIALIZE = 2001;
+
+ public static final int MUTE_ON = 3001;
+ public static final int MUTE_OFF = 3002;
+ public static final int TOGGLE_MUTE = 3003;
+
+ public static final int SWITCH_FOCUS = 4001;
+
+ /** Valid values for mAudioFocusType */
+ public static final int NO_FOCUS = 1;
+ public static final int HAS_FOCUS = 2;
+
+ private static final String ACTIVE_EARPIECE_ROUTE_NAME = "ActiveEarpieceRoute";
+ private static final String ACTIVE_BLUETOOTH_ROUTE_NAME = "ActiveBluetoothRoute";
+ private static final String ACTIVE_SPEAKER_ROUTE_NAME = "ActiveSpeakerRoute";
+ private static final String ACTIVE_HEADSET_ROUTE_NAME = "ActiveHeadsetRoute";
+ private static final String QUIESCENT_EARPIECE_ROUTE_NAME = "QuiescentEarpieceRoute";
+ private static final String QUIESCENT_BLUETOOTH_ROUTE_NAME = "QuiescentBluetoothRoute";
+ private static final String QUIESCENT_SPEAKER_ROUTE_NAME = "QuiescentSpeakerRoute";
+ private static final String QUIESCENT_HEADSET_ROUTE_NAME = "QuiescentHeadsetRoute";
+
+ public static final String NAME = CallAudioRouteStateMachine.class.getName();
+
+ @Override
+ protected void onPreHandleMessage(Message msg) {
+ if (msg.obj != null && msg.obj instanceof Session) {
+ Log.continueSession((Session) msg.obj, "CARSM.pM_" + msg.what);
+ }
+ }
+
+ @Override
+ protected void onPostHandleMessage(Message msg) {
+ Log.endSession();
+ }
+
+ abstract class AudioState extends State {
+ @Override
+ public void enter() {
+ super.enter();
+ Log.event(mCallsManager.getForegroundCall(), Log.Events.AUDIO_ROUTE,
+ "Entering state " + getName());
+ }
+
+ @Override
+ public void exit() {
+ Log.event(mCallsManager.getForegroundCall(), Log.Events.AUDIO_ROUTE,
+ "Leaving state " + getName());
+ super.exit();
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ Log.d(this, "Message received: %s", msg);
+ switch (msg.what) {
+ case CONNECT_WIRED_HEADSET:
+ Log.event(mCallsManager.getForegroundCall(), Log.Events.AUDIO_ROUTE,
+ "Wired headset connected");
+ mAvailableRoutes &= ~ROUTE_EARPIECE;
+ mAvailableRoutes |= ROUTE_WIRED_HEADSET;
+ return NOT_HANDLED;
+ case CONNECT_BLUETOOTH:
+ // This case is here because the bluetooth manager sends out a lot of spurious
+ // state changes, and no layers above this one can tell which are actual changes
+ // in connection/disconnection status. This filters it out.
+ if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0) {
+ return HANDLED; // Do nothing if we already have bluetooth as enabled.
+ } else {
+ Log.event(mCallsManager.getForegroundCall(), Log.Events.AUDIO_ROUTE,
+ "Bluetooth connected");
+ mAvailableRoutes |= ROUTE_BLUETOOTH;
+ return NOT_HANDLED;
+ }
+ case DISCONNECT_WIRED_HEADSET:
+ Log.event(mCallsManager.getForegroundCall(), Log.Events.AUDIO_ROUTE,
+ "Wired headset disconnected");
+ mAvailableRoutes &= ~ROUTE_WIRED_HEADSET;
+ mAvailableRoutes |= ROUTE_EARPIECE;
+ return NOT_HANDLED;
+ case DISCONNECT_BLUETOOTH:
+ if ((mAvailableRoutes & ROUTE_BLUETOOTH) == 0) {
+ return HANDLED;
+ } else {
+ Log.event(mCallsManager.getForegroundCall(), Log.Events.AUDIO_ROUTE,
+ "Bluetooth disconnected");
+ mAvailableRoutes &= ~ROUTE_BLUETOOTH;
+ return NOT_HANDLED;
+ }
+ case SWITCH_WIRED_OR_EARPIECE:
+ if ((mAvailableRoutes & ROUTE_EARPIECE) != 0) {
+ sendInternalMessage(SWITCH_EARPIECE);
+ } else if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) {
+ sendInternalMessage(SWITCH_HEADSET);
+ } else {
+ Log.e(this, new IllegalStateException(),
+ "Neither headset nor earpiece are available. Defaulting to " +
+ "earpiece.");
+ sendInternalMessage(SWITCH_EARPIECE);
+ }
+ return HANDLED;
+ case REINITIALIZE:
+ CallAudioState initState = getInitialAudioState();
+ mAvailableRoutes = initState.getSupportedRouteMask();
+ mIsMuted = initState.isMuted();
+ mWasOnSpeaker = initState.getRoute() == ROUTE_SPEAKER;
+ transitionTo(mRouteCodeToQuiescentState.get(initState.getRoute()));
+ return HANDLED;
+ default:
+ return NOT_HANDLED;
+ }
+ }
+
+ // Behavior will depend on whether the state is an active one or a quiescent one.
+ abstract public void updateSystemAudioState();
+ abstract public boolean isActive();
+ }
+
+ class ActiveEarpieceRoute extends EarpieceRoute {
+ @Override
+ public String getName() {
+ return ACTIVE_EARPIECE_ROUTE_NAME;
+ }
+
+ @Override
+ public boolean isActive() {
+ return true;
+ }
+
+ @Override
+ public void enter() {
+ super.enter();
+ setSpeakerphoneOn(false);
+ setBluetoothOn(false);
+ CallAudioState newState = new CallAudioState(mIsMuted, ROUTE_EARPIECE,
+ mAvailableRoutes);
+ setSystemAudioState(mCurrentCallAudioState, newState);
+ updateInternalCallAudioState();
+ }
+
+ @Override
+ public void updateSystemAudioState() {
+ CallAudioState oldAudioState = mCurrentCallAudioState;
+ updateInternalCallAudioState();
+ setSystemAudioState(oldAudioState, mCurrentCallAudioState);
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ if (super.processMessage(msg) == HANDLED) {
+ return HANDLED;
+ }
+ switch (msg.what) {
+ case SWITCH_EARPIECE:
+ // Nothing to do here
+ return HANDLED;
+ case SWITCH_BLUETOOTH:
+ if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0) {
+ transitionTo(mActiveBluetoothRoute);
+ } else {
+ Log.w(this, "Ignoring switch to bluetooth command. Not available.");
+ }
+ return HANDLED;
+ case SWITCH_HEADSET:
+ if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) {
+ transitionTo(mActiveHeadsetRoute);
+ } else {
+ Log.w(this, "Ignoring switch to headset command. Not available.");
+ }
+ return HANDLED;
+ case SWITCH_SPEAKER:
+ transitionTo(mActiveSpeakerRoute);
+ return HANDLED;
+ case SWITCH_FOCUS:
+ if (msg.arg1 == NO_FOCUS) {
+ transitionTo(mQuiescentEarpieceRoute);
+ }
+ return HANDLED;
+ default:
+ return NOT_HANDLED;
+ }
+ }
+ }
+
+ class QuiescentEarpieceRoute extends EarpieceRoute {
+ @Override
+ public String getName() {
+ return QUIESCENT_EARPIECE_ROUTE_NAME;
+ }
+
+ @Override
+ public boolean isActive() {
+ return false;
+ }
+
+ @Override
+ public void enter() {
+ super.enter();
+ updateInternalCallAudioState();
+ }
+
+ @Override
+ public void updateSystemAudioState() {
+ updateInternalCallAudioState();
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ if (super.processMessage(msg) == HANDLED) {
+ return HANDLED;
+ }
+ switch (msg.what) {
+ case SWITCH_EARPIECE:
+ // Nothing to do here
+ return HANDLED;
+ case SWITCH_BLUETOOTH:
+ if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0) {
+ transitionTo(mQuiescentBluetoothRoute);
+ } else {
+ Log.w(this, "Ignoring switch to bluetooth command. Not available.");
+ }
+ return HANDLED;
+ case SWITCH_HEADSET:
+ if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) {
+ transitionTo(mQuiescentHeadsetRoute);
+ } else {
+ Log.w(this, "Ignoring switch to headset command. Not available.");
+ }
+ return HANDLED;
+ case SWITCH_SPEAKER:
+ transitionTo(mQuiescentSpeakerRoute);
+ return HANDLED;
+ case SWITCH_FOCUS:
+ if (msg.arg1 == HAS_FOCUS) {
+ transitionTo(mActiveEarpieceRoute);
+ }
+ return HANDLED;
+ default:
+ return NOT_HANDLED;
+ }
+ }
+ }
+
+ abstract class EarpieceRoute extends AudioState {
+ @Override
+ public boolean processMessage(Message msg) {
+ if (super.processMessage(msg) == HANDLED) {
+ return HANDLED;
+ }
+ switch (msg.what) {
+ case CONNECT_WIRED_HEADSET:
+ sendInternalMessage(SWITCH_HEADSET);
+ return HANDLED;
+ case CONNECT_BLUETOOTH:
+ sendInternalMessage(SWITCH_BLUETOOTH);
+ return HANDLED;
+ case DISCONNECT_BLUETOOTH:
+ updateSystemAudioState();
+ // No change in audio route required
+ return HANDLED;
+ case DISCONNECT_WIRED_HEADSET:
+ Log.e(this, new IllegalStateException(),
+ "Wired headset should not go from connected to not when on " +
+ "earpiece");
+ updateSystemAudioState();
+ return HANDLED;
+ case CONNECT_DOCK:
+ sendInternalMessage(SWITCH_SPEAKER);
+ return HANDLED;
+ case DISCONNECT_DOCK:
+ // Nothing to do here
+ return HANDLED;
+ default:
+ return NOT_HANDLED;
+ }
+ }
+ }
+
+ class ActiveHeadsetRoute extends HeadsetRoute {
+ @Override
+ public String getName() {
+ return ACTIVE_HEADSET_ROUTE_NAME;
+ }
+
+ @Override
+ public boolean isActive() {
+ return true;
+ }
+
+ @Override
+ public void enter() {
+ super.enter();
+ setSpeakerphoneOn(false);
+ setBluetoothOn(false);
+ CallAudioState newState = new CallAudioState(mIsMuted, ROUTE_WIRED_HEADSET,
+ mAvailableRoutes);
+ setSystemAudioState(mCurrentCallAudioState, newState);
+ updateInternalCallAudioState();
+ }
+
+ @Override
+ public void updateSystemAudioState() {
+ CallAudioState oldAudioState = mCurrentCallAudioState;
+ updateInternalCallAudioState();
+ setSystemAudioState(oldAudioState, mCurrentCallAudioState);
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ if (super.processMessage(msg) == HANDLED) {
+ return HANDLED;
+ }
+ switch (msg.what) {
+ case SWITCH_EARPIECE:
+ if ((mAvailableRoutes & ROUTE_EARPIECE) != 0) {
+ transitionTo(mActiveEarpieceRoute);
+ } else {
+ Log.w(this, "Ignoring switch to earpiece command. Not available.");
+ }
+ return HANDLED;
+ case SWITCH_BLUETOOTH:
+ if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0) {
+ transitionTo(mActiveBluetoothRoute);
+ } else {
+ Log.w(this, "Ignoring switch to bluetooth command. Not available.");
+ }
+ return HANDLED;
+ case SWITCH_HEADSET:
+ // Nothing to do
+ return HANDLED;
+ case SWITCH_SPEAKER:
+ transitionTo(mActiveSpeakerRoute);
+ return HANDLED;
+ case SWITCH_FOCUS:
+ if (msg.arg1 == NO_FOCUS) {
+ transitionTo(mQuiescentHeadsetRoute);
+ }
+ return HANDLED;
+ default:
+ return NOT_HANDLED;
+ }
+ }
+ }
+
+ class QuiescentHeadsetRoute extends HeadsetRoute {
+ @Override
+ public String getName() {
+ return QUIESCENT_HEADSET_ROUTE_NAME;
+ }
+
+ @Override
+ public boolean isActive() {
+ return false;
+ }
+
+ @Override
+ public void enter() {
+ super.enter();
+ updateInternalCallAudioState();
+ }
+
+ @Override
+ public void updateSystemAudioState() {
+ updateInternalCallAudioState();
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ if (super.processMessage(msg) == HANDLED) {
+ return HANDLED;
+ }
+ switch (msg.what) {
+ case SWITCH_EARPIECE:
+ if ((mAvailableRoutes & ROUTE_EARPIECE) != 0) {
+ transitionTo(mQuiescentEarpieceRoute);
+ } else {
+ Log.w(this, "Ignoring switch to earpiece command. Not available.");
+ }
+ return HANDLED;
+ case SWITCH_BLUETOOTH:
+ if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0) {
+ transitionTo(mQuiescentBluetoothRoute);
+ } else {
+ Log.w(this, "Ignoring switch to bluetooth command. Not available.");
+ }
+ return HANDLED;
+ case SWITCH_HEADSET:
+ // Nothing to do
+ return HANDLED;
+ case SWITCH_SPEAKER:
+ transitionTo(mQuiescentSpeakerRoute);
+ return HANDLED;
+ case SWITCH_FOCUS:
+ if (msg.arg1 == HAS_FOCUS) {
+ transitionTo(mActiveHeadsetRoute);
+ }
+ return HANDLED;
+ default:
+ return NOT_HANDLED;
+ }
+ }
+ }
+
+ abstract class HeadsetRoute extends AudioState {
+ @Override
+ public boolean processMessage(Message msg) {
+ if (super.processMessage(msg) == HANDLED) {
+ return HANDLED;
+ }
+ switch (msg.what) {
+ case CONNECT_WIRED_HEADSET:
+ Log.e(this, new IllegalStateException(),
+ "Wired headset should already be connected.");
+ mAvailableRoutes |= ROUTE_WIRED_HEADSET;
+ updateSystemAudioState();
+ return HANDLED;
+ case CONNECT_BLUETOOTH:
+ sendInternalMessage(SWITCH_BLUETOOTH);
+ return HANDLED;
+ case DISCONNECT_BLUETOOTH:
+ updateSystemAudioState();
+ // No change in audio route required
+ return HANDLED;
+ case DISCONNECT_WIRED_HEADSET:
+ if (mWasOnSpeaker) {
+ sendInternalMessage(SWITCH_SPEAKER);
+ } else {
+ sendInternalMessage(SWITCH_EARPIECE);
+ }
+ return HANDLED;
+ case CONNECT_DOCK:
+ // Nothing to do here
+ return HANDLED;
+ case DISCONNECT_DOCK:
+ // Nothing to do here
+ return HANDLED;
+ default:
+ return NOT_HANDLED;
+ }
+ }
+ }
+
+ class ActiveBluetoothRoute extends BluetoothRoute {
+ @Override
+ public String getName() {
+ return ACTIVE_BLUETOOTH_ROUTE_NAME;
+ }
+
+ @Override
+ public boolean isActive() {
+ return true;
+ }
+
+ @Override
+ public void enter() {
+ super.enter();
+ setSpeakerphoneOn(false);
+ setBluetoothOn(true);
+ CallAudioState newState = new CallAudioState(mIsMuted, ROUTE_BLUETOOTH,
+ mAvailableRoutes);
+ setSystemAudioState(mCurrentCallAudioState, newState);
+ updateInternalCallAudioState();
+ }
+
+ @Override
+ public void updateSystemAudioState() {
+ CallAudioState oldAudioState = mCurrentCallAudioState;
+ updateInternalCallAudioState();
+ setSystemAudioState(oldAudioState, mCurrentCallAudioState);
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ if (super.processMessage(msg) == HANDLED) {
+ return HANDLED;
+ }
+ switch (msg.what) {
+ case SWITCH_EARPIECE:
+ if ((mAvailableRoutes & ROUTE_EARPIECE) != 0) {
+ transitionTo(mActiveEarpieceRoute);
+ } else {
+ Log.w(this, "Ignoring switch to earpiece command. Not available.");
+ }
+ return HANDLED;
+ case SWITCH_BLUETOOTH:
+ // Nothing to do
+ return HANDLED;
+ case SWITCH_HEADSET:
+ if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) {
+ transitionTo(mActiveHeadsetRoute);
+ } else {
+ Log.w(this, "Ignoring switch to headset command. Not available.");
+ }
+ return HANDLED;
+ case SWITCH_SPEAKER:
+ transitionTo(mActiveSpeakerRoute);
+ return HANDLED;
+ case SWITCH_FOCUS:
+ if (msg.arg1 == NO_FOCUS) {
+ transitionTo(mQuiescentBluetoothRoute);
+ }
+ return HANDLED;
+ default:
+ return NOT_HANDLED;
+ }
+ }
+ }
+
+ class QuiescentBluetoothRoute extends BluetoothRoute {
+ @Override
+ public String getName() {
+ return QUIESCENT_BLUETOOTH_ROUTE_NAME;
+ }
+
+ @Override
+ public boolean isActive() {
+ return false;
+ }
+
+ @Override
+ public void enter() {
+ super.enter();
+ updateInternalCallAudioState();
+ }
+
+ @Override
+ public void updateSystemAudioState() {
+ updateInternalCallAudioState();
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ if (super.processMessage(msg) == HANDLED) {
+ return HANDLED;
+ }
+ switch (msg.what) {
+ case SWITCH_EARPIECE:
+ if ((mAvailableRoutes & ROUTE_EARPIECE) != 0) {
+ transitionTo(mQuiescentEarpieceRoute);
+ } else {
+ Log.w(this, "Ignoring switch to earpiece command. Not available.");
+ }
+ return HANDLED;
+ case SWITCH_BLUETOOTH:
+ // Nothing to do
+ return HANDLED;
+ case SWITCH_HEADSET:
+ if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) {
+ transitionTo(mQuiescentHeadsetRoute);
+ } else {
+ Log.w(this, "Ignoring switch to headset command. Not available.");
+ }
+ return HANDLED;
+ case SWITCH_SPEAKER:
+ transitionTo(mQuiescentSpeakerRoute);
+ return HANDLED;
+ case SWITCH_FOCUS:
+ if (msg.arg1 == HAS_FOCUS) {
+ transitionTo(mActiveBluetoothRoute);
+ }
+ return HANDLED;
+ default:
+ return NOT_HANDLED;
+ }
+ }
+ }
+
+ abstract class BluetoothRoute extends AudioState {
+ @Override
+ public boolean processMessage(Message msg) {
+ if (super.processMessage(msg) == HANDLED) {
+ return HANDLED;
+ }
+ switch (msg.what) {
+ case CONNECT_WIRED_HEADSET:
+ sendInternalMessage(SWITCH_HEADSET);
+ return HANDLED;
+ case CONNECT_BLUETOOTH:
+ // We can't tell when a change in bluetooth state corresponds to an
+ // actual connection or disconnection, so we'll just ignore it if we're already
+ // in the bluetooth route.
+ return HANDLED;
+ case DISCONNECT_BLUETOOTH:
+ sendInternalMessage(SWITCH_WIRED_OR_EARPIECE);
+ mWasOnSpeaker = false;
+ return HANDLED;
+ case DISCONNECT_WIRED_HEADSET:
+ updateSystemAudioState();
+ // No change in audio route required
+ return HANDLED;
+ case CONNECT_DOCK:
+ // Nothing to do here
+ return HANDLED;
+ case DISCONNECT_DOCK:
+ // Nothing to do here
+ return HANDLED;
+ default:
+ return NOT_HANDLED;
+ }
+ }
+ }
+
+ class ActiveSpeakerRoute extends SpeakerRoute {
+ @Override
+ public String getName() {
+ return ACTIVE_SPEAKER_ROUTE_NAME;
+ }
+
+ @Override
+ public boolean isActive() {
+ return true;
+ }
+
+ @Override
+ public void enter() {
+ super.enter();
+ mWasOnSpeaker = true;
+ setSpeakerphoneOn(true);
+ setBluetoothOn(false);
+ CallAudioState newState = new CallAudioState(mIsMuted, ROUTE_SPEAKER,
+ mAvailableRoutes);
+ setSystemAudioState(mCurrentCallAudioState, newState);
+ updateInternalCallAudioState();
+ }
+
+ @Override
+ public void updateSystemAudioState() {
+ CallAudioState oldAudioState = mCurrentCallAudioState;
+ updateInternalCallAudioState();
+ setSystemAudioState(oldAudioState, mCurrentCallAudioState);
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ if (super.processMessage(msg) == HANDLED) {
+ return HANDLED;
+ }
+ switch(msg.what) {
+ case SWITCH_EARPIECE:
+ if ((mAvailableRoutes & ROUTE_EARPIECE) != 0) {
+ transitionTo(mActiveEarpieceRoute);
+ } else {
+ Log.w(this, "Ignoring switch to earpiece command. Not available.");
+ }
+ return HANDLED;
+ case SWITCH_BLUETOOTH:
+ if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0) {
+ transitionTo(mActiveBluetoothRoute);
+ } else {
+ Log.w(this, "Ignoring switch to bluetooth command. Not available.");
+ }
+ return HANDLED;
+ case SWITCH_HEADSET:
+ if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) {
+ transitionTo(mActiveHeadsetRoute);
+ } else {
+ Log.w(this, "Ignoring switch to headset command. Not available.");
+ }
+ return HANDLED;
+ case SWITCH_SPEAKER:
+ // Nothing to do
+ return HANDLED;
+ case SWITCH_FOCUS:
+ if (msg.arg1 == NO_FOCUS) {
+ transitionTo(mQuiescentSpeakerRoute);
+ }
+ return HANDLED;
+ default:
+ return NOT_HANDLED;
+ }
+ }
+ }
+
+ class QuiescentSpeakerRoute extends SpeakerRoute {
+ @Override
+ public String getName() {
+ return QUIESCENT_SPEAKER_ROUTE_NAME;
+ }
+
+ @Override
+ public boolean isActive() {
+ return false;
+ }
+
+ @Override
+ public void enter() {
+ super.enter();
+ // Omit setting mWasOnSpeaker to true here, since this does not reflect a call
+ // actually being on speakerphone.
+ updateInternalCallAudioState();
+ }
+
+ @Override
+ public void updateSystemAudioState() {
+ updateInternalCallAudioState();
+ }
+
+ @Override
+ public boolean processMessage(Message msg) {
+ if (super.processMessage(msg) == HANDLED) {
+ return HANDLED;
+ }
+ switch(msg.what) {
+ case SWITCH_EARPIECE:
+ if ((mAvailableRoutes & ROUTE_EARPIECE) != 0) {
+ transitionTo(mQuiescentEarpieceRoute);
+ } else {
+ Log.w(this, "Ignoring switch to earpiece command. Not available.");
+ }
+ return HANDLED;
+ case SWITCH_BLUETOOTH:
+ if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0) {
+ transitionTo(mQuiescentBluetoothRoute);
+ } else {
+ Log.w(this, "Ignoring switch to bluetooth command. Not available.");
+ }
+ return HANDLED;
+ case SWITCH_HEADSET:
+ if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) {
+ transitionTo(mQuiescentHeadsetRoute);
+ } else {
+ Log.w(this, "Ignoring switch to headset command. Not available.");
+ }
+ return HANDLED;
+ case SWITCH_SPEAKER:
+ // Nothing to do
+ return HANDLED;
+ case SWITCH_FOCUS:
+ if (msg.arg1 == HAS_FOCUS) {
+ transitionTo(mActiveSpeakerRoute);
+ }
+ return HANDLED;
+ default:
+ return NOT_HANDLED;
+ }
+ }
+ }
+
+ abstract class SpeakerRoute extends AudioState {
+ @Override
+ public boolean processMessage(Message msg) {
+ if (super.processMessage(msg) == HANDLED) {
+ return HANDLED;
+ }
+ switch (msg.what) {
+ case CONNECT_WIRED_HEADSET:
+ sendInternalMessage(SWITCH_HEADSET);
+ return HANDLED;
+ case CONNECT_BLUETOOTH:
+ sendInternalMessage(SWITCH_BLUETOOTH);
+ return HANDLED;
+ case DISCONNECT_BLUETOOTH:
+ updateSystemAudioState();
+ // No change in audio route required
+ return HANDLED;
+ case DISCONNECT_WIRED_HEADSET:
+ updateSystemAudioState();
+ // No change in audio route required
+ return HANDLED;
+ case CONNECT_DOCK:
+ // Nothing to do here
+ return HANDLED;
+ case DISCONNECT_DOCK:
+ sendInternalMessage(SWITCH_WIRED_OR_EARPIECE);
+ return HANDLED;
+ default:
+ return NOT_HANDLED;
+ }
+ }
+ }
+
+ private final ActiveEarpieceRoute mActiveEarpieceRoute = new ActiveEarpieceRoute();
+ private final ActiveHeadsetRoute mActiveHeadsetRoute = new ActiveHeadsetRoute();
+ private final ActiveBluetoothRoute mActiveBluetoothRoute = new ActiveBluetoothRoute();
+ private final ActiveSpeakerRoute mActiveSpeakerRoute = new ActiveSpeakerRoute();
+ private final QuiescentEarpieceRoute mQuiescentEarpieceRoute = new QuiescentEarpieceRoute();
+ private final QuiescentHeadsetRoute mQuiescentHeadsetRoute = new QuiescentHeadsetRoute();
+ private final QuiescentBluetoothRoute mQuiescentBluetoothRoute = new QuiescentBluetoothRoute();
+ private final QuiescentSpeakerRoute mQuiescentSpeakerRoute = new QuiescentSpeakerRoute();
+
+ /**
+ * A few pieces of hidden state. Used to avoid exponential explosion of number of explicit
+ * states
+ */
+ private int mAvailableRoutes;
+ private boolean mWasOnSpeaker;
+ private boolean mIsMuted;
+
+ private final Context mContext;
+ private final CallsManager mCallsManager;
+ private final AudioManager mAudioManager;
+ private final BluetoothManager mBluetoothManager;
+ private final WiredHeadsetManager mWiredHeadsetManager;
+ private final StatusBarNotifier mStatusBarNotifier;
+ private final CallAudioManager.AudioServiceFactory mAudioServiceFactory;
+
+ private HashMap<String, Integer> mStateNameToRouteCode;
+ private HashMap<Integer, AudioState> mRouteCodeToQuiescentState;
+
+ // CallAudioState is used as an interface to communicate with many other system components.
+ // No internal state transitions should depend on this variable.
+ private CallAudioState mCurrentCallAudioState;
+
+ public CallAudioRouteStateMachine(
+ Context context,
+ CallsManager callsManager,
+ BluetoothManager bluetoothManager,
+ WiredHeadsetManager wiredHeadsetManager,
+ StatusBarNotifier statusBarNotifier,
+ CallAudioManager.AudioServiceFactory audioServiceFactory) {
+ super(NAME);
+ addState(mActiveEarpieceRoute);
+ addState(mActiveHeadsetRoute);
+ addState(mActiveBluetoothRoute);
+ addState(mActiveSpeakerRoute);
+ addState(mQuiescentEarpieceRoute);
+ addState(mQuiescentHeadsetRoute);
+ addState(mQuiescentBluetoothRoute);
+ addState(mQuiescentSpeakerRoute);
+
+ mContext = context;
+ mCallsManager = callsManager;
+ mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+ mBluetoothManager = bluetoothManager;
+ mWiredHeadsetManager = wiredHeadsetManager;
+ mStatusBarNotifier = statusBarNotifier;
+ mAudioServiceFactory = audioServiceFactory;
+
+ mStateNameToRouteCode = new HashMap<>(8);
+ mStateNameToRouteCode.put(mQuiescentEarpieceRoute.getName(), ROUTE_EARPIECE);
+ mStateNameToRouteCode.put(mQuiescentBluetoothRoute.getName(), ROUTE_BLUETOOTH);
+ mStateNameToRouteCode.put(mQuiescentHeadsetRoute.getName(), ROUTE_WIRED_HEADSET);
+ mStateNameToRouteCode.put(mQuiescentSpeakerRoute.getName(), ROUTE_SPEAKER);
+ mStateNameToRouteCode.put(mActiveEarpieceRoute.getName(), ROUTE_EARPIECE);
+ mStateNameToRouteCode.put(mActiveBluetoothRoute.getName(), ROUTE_BLUETOOTH);
+ mStateNameToRouteCode.put(mActiveHeadsetRoute.getName(), ROUTE_WIRED_HEADSET);
+ mStateNameToRouteCode.put(mActiveSpeakerRoute.getName(), ROUTE_SPEAKER);
+
+ mRouteCodeToQuiescentState = new HashMap<>(4);
+ mRouteCodeToQuiescentState.put(ROUTE_EARPIECE, mQuiescentEarpieceRoute);
+ mRouteCodeToQuiescentState.put(ROUTE_BLUETOOTH, mQuiescentBluetoothRoute);
+ mRouteCodeToQuiescentState.put(ROUTE_SPEAKER, mQuiescentSpeakerRoute);
+ mRouteCodeToQuiescentState.put(ROUTE_WIRED_HEADSET, mQuiescentHeadsetRoute);
+ initialize();
+ }
+
+ /**
+ * Initializes the state machine with info on initial audio route, supported audio routes,
+ * and mute status.
+ */
+ public void initialize() {
+ CallAudioState initState = getInitialAudioState();
+ initialize(initState);
+ }
+
+ public void initialize(CallAudioState initState) {
+ mCurrentCallAudioState = initState;
+ mAvailableRoutes = initState.getSupportedRouteMask();
+ mIsMuted = initState.isMuted();
+ mWasOnSpeaker = initState.getRoute() == ROUTE_SPEAKER;
+
+ mStatusBarNotifier.notifyMute(initState.isMuted());
+ mStatusBarNotifier.notifySpeakerphone(initState.getRoute() == CallAudioState.ROUTE_SPEAKER);
+ setInitialState(mRouteCodeToQuiescentState.get(initState.getRoute()));
+ start();
+ }
+
+ /**
+ * Getter for the current CallAudioState object that the state machine is keeping track of.
+ * Used for compatibility purposes.
+ */
+ public CallAudioState getCurrentCallAudioState() {
+ return mCurrentCallAudioState;
+ }
+
+ public void sendMessageWithSessionInfo(int message, int arg) {
+ sendMessage(message, arg, 0, Log.createSubsession());
+ }
+
+ public void sendMessageWithSessionInfo(int message) {
+ sendMessage(message, 0, 0, Log.createSubsession());
+ }
+
+ /**
+ * This is for state-independent changes in audio route (i.e. muting)
+ * @param msg that couldn't be handled.
+ */
+ @Override
+ protected void unhandledMessage(Message msg) {
+ CallAudioState newCallAudioState;
+ switch (msg.what) {
+ case MUTE_ON:
+ setMuteOn(true);
+ newCallAudioState = new CallAudioState(mIsMuted,
+ mCurrentCallAudioState.getRoute(),
+ mAvailableRoutes);
+ setSystemAudioState(mCurrentCallAudioState, newCallAudioState);
+ updateInternalCallAudioState();
+ return;
+ case MUTE_OFF:
+ setMuteOn(false);
+ newCallAudioState = new CallAudioState(mIsMuted,
+ mCurrentCallAudioState.getRoute(),
+ mAvailableRoutes);
+ setSystemAudioState(mCurrentCallAudioState, newCallAudioState);
+ updateInternalCallAudioState();
+ return;
+ case TOGGLE_MUTE:
+ if (mIsMuted) {
+ sendInternalMessage(MUTE_OFF);
+ } else {
+ sendInternalMessage(MUTE_ON);
+ }
+ return;
+ default:
+ Log.e(this, new IllegalStateException(),
+ "Unexpected message code");
+ }
+ }
+
+ private void setSpeakerphoneOn(boolean on) {
+ if (mAudioManager.isSpeakerphoneOn() != on) {
+ Log.i(this, "turning speaker phone %s", on);
+ mAudioManager.setSpeakerphoneOn(on);
+ }
+ }
+
+ private void setBluetoothOn(boolean on) {
+ if (mBluetoothManager.isBluetoothAvailable()) {
+ boolean isAlreadyOn = mBluetoothManager.isBluetoothAudioConnectedOrPending();
+ if (on != isAlreadyOn) {
+ Log.i(this, "connecting bluetooth %s", on);
+ if (on) {
+ mBluetoothManager.connectBluetoothAudio();
+ } else {
+ mBluetoothManager.disconnectBluetoothAudio();
+ }
+ }
+ }
+ }
+
+ private void setMuteOn(boolean mute) {
+ mIsMuted = mute;
+ Log.event(mCallsManager.getForegroundCall(), Log.Events.MUTE,
+ mute ? "on" : "off");
+ if (mute != mAudioManager.isMicrophoneMute() && isInActiveState()) {
+ IAudioService audio = mAudioServiceFactory.getAudioService();
+ Log.i(this, "changing microphone mute state to: %b [serviceIsNull=%b]",
+ mute, audio == null);
+ if (audio != null) {
+ try {
+ // We use the audio service directly here so that we can specify
+ // the current user. Telecom runs in the system_server process which
+ // may run as a separate user from the foreground user. If we
+ // used AudioManager directly, we would change mute for the system's
+ // user and not the current foreground, which we want to avoid.
+ audio.setMicrophoneMute(
+ mute, mContext.getOpPackageName(), getCurrentUserId());
+
+ } catch (RemoteException e) {
+ Log.e(this, e, "Remote exception while toggling mute.");
+ }
+ // TODO: Check microphone state after attempting to set to ensure that
+ // our state corroborates AudioManager's state.
+ }
+ }
+ }
+
+ /**
+ * Updates the CallAudioState object from current internal state. The result is used for
+ * external communication only.
+ */
+ private void updateInternalCallAudioState() {
+ IState currentState = getCurrentState();
+ if (currentState == null) {
+ Log.e(this, new IllegalStateException(), "Current state should never be null" +
+ " when updateInternalCallAudioState is called.");
+ mCurrentCallAudioState = new CallAudioState(
+ mIsMuted, mCurrentCallAudioState.getRoute(), mAvailableRoutes);
+ return;
+ }
+ int currentRoute = mStateNameToRouteCode.get(currentState.getName());
+ mCurrentCallAudioState = new CallAudioState(mIsMuted, currentRoute, mAvailableRoutes);
+ }
+
+ private void setSystemAudioState(CallAudioState oldCallAudioState,
+ CallAudioState newCallAudioState) {
+ Log.i(this, "setSystemAudioState: changing from %s to %s", oldCallAudioState,
+ newCallAudioState);
+ Log.event(mCallsManager.getForegroundCall(), Log.Events.AUDIO_ROUTE,
+ CallAudioState.audioRouteToString(newCallAudioState.getRoute()));
+
+ if (!oldCallAudioState.equals(newCallAudioState)) {
+ mCallsManager.onCallAudioStateChanged(oldCallAudioState, newCallAudioState);
+ updateAudioForForegroundCall(newCallAudioState);
+ }
+ }
+
+ private void updateAudioForForegroundCall(CallAudioState newCallAudioState) {
+ Call call = mCallsManager.getForegroundCall();
+ if (call != null && call.getConnectionService() != null) {
+ call.getConnectionService().onCallAudioStateChanged(call, newCallAudioState);
+ }
+ }
+
+ private int calculateSupportedRoutes() {
+ int routeMask = CallAudioState.ROUTE_SPEAKER;
+
+ if (mWiredHeadsetManager.isPluggedIn()) {
+ routeMask |= CallAudioState.ROUTE_WIRED_HEADSET;
+ } else {
+ routeMask |= CallAudioState.ROUTE_EARPIECE;
+ }
+
+ if (mBluetoothManager.isBluetoothAvailable()) {
+ routeMask |= CallAudioState.ROUTE_BLUETOOTH;
+ }
+
+ return routeMask;
+ }
+
+ private void sendInternalMessage(int messageCode) {
+ // Internal messages are messages which the state machine sends to itself in the
+ // course of processing externally-sourced messages. We want to send these messages at
+ // the front of the queue in order to make actions appear atomic to the user and to
+ // prevent scenarios such as these:
+ // 1. State machine handler thread is suspended for some reason.
+ // 2. Headset gets connected (sends CONNECT_HEADSET).
+ // 3. User switches to speakerphone in the UI (sends SWITCH_SPEAKER).
+ // 4. State machine handler is un-suspended.
+ // 5. State machine handler processes the CONNECT_HEADSET message and sends
+ // SWITCH_HEADSET at end of queue.
+ // 6. State machine handler processes SWITCH_SPEAKER.
+ // 7. State machine handler processes SWITCH_HEADSET.
+ Session subsession = Log.createSubsession();
+ if(subsession != null) {
+ sendMessageAtFrontOfQueue(messageCode, subsession);
+ } else {
+ sendMessageAtFrontOfQueue(messageCode);
+ }
+ }
+
+ private CallAudioState getInitialAudioState() {
+ int supportedRouteMask = calculateSupportedRoutes();
+ int route = (supportedRouteMask & ROUTE_WIRED_HEADSET) != 0
+ ? ROUTE_WIRED_HEADSET : ROUTE_EARPIECE;
+ if ((supportedRouteMask & ROUTE_BLUETOOTH) != 0) {
+ route = ROUTE_BLUETOOTH;
+ }
+
+ return new CallAudioState(false, route, supportedRouteMask);
+ }
+
+ private int getCurrentUserId() {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ UserInfo currentUser = ActivityManagerNative.getDefault().getCurrentUser();
+ return currentUser.id;
+ } catch (RemoteException e) {
+ // Activity manager not running, nothing we can do assume user 0.
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ return UserHandle.USER_OWNER;
+ }
+
+ private boolean isInActiveState() {
+ AudioState currentState = (AudioState) getCurrentState();
+ if (currentState == null) {
+ Log.w(this, "Current state is null, assuming inactive state");
+ return false;
+ }
+ return currentState.isActive();
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/server/telecom/CallIdMapper.java b/src/com/android/server/telecom/CallIdMapper.java
index 8199dfa..b097bea 100644
--- a/src/com/android/server/telecom/CallIdMapper.java
+++ b/src/com/android/server/telecom/CallIdMapper.java
@@ -18,10 +18,13 @@
import android.util.ArrayMap;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.util.Map;
/** Utility to map {@link Call} objects to unique IDs. IDs are generated when a call is added. */
-class CallIdMapper {
+@VisibleForTesting
+public class CallIdMapper {
/**
* A very basic bidirectional map.
*/
@@ -75,12 +78,6 @@
}
private final BiMap<String, Call> mCalls = new BiMap<>();
- private final String mCallIdPrefix;
- private static int sIdCount;
-
- CallIdMapper(String callIdPrefix) {
- mCallIdPrefix = callIdPrefix + "@";
- }
void replaceCall(Call newCall, Call callToReplace) {
// Use the old call's ID for the new call.
@@ -96,7 +93,7 @@
}
void addCall(Call call) {
- addCall(call, getNewId());
+ addCall(call, call.getId());
}
void removeCall(Call call) {
@@ -111,10 +108,10 @@
}
String getCallId(Call call) {
- if (call == null) {
+ if (call == null || mCalls.getKey(call) == null) {
return null;
}
- return mCalls.getKey(call);
+ return call.getId();
}
Call getCall(Object objId) {
@@ -122,9 +119,6 @@
if (objId instanceof String) {
callId = (String) objId;
}
- if (!isValidCallId(callId) && !isValidConferenceId(callId)) {
- return null;
- }
return mCalls.getValue(callId);
}
@@ -132,18 +126,4 @@
void clear() {
mCalls.clear();
}
-
- boolean isValidCallId(String callId) {
- // Note, no need for thread check, this method is thread safe.
- return callId != null && callId.startsWith(mCallIdPrefix);
- }
-
- boolean isValidConferenceId(String callId) {
- return callId != null;
- }
-
- String getNewId() {
- sIdCount++;
- return mCallIdPrefix + sIdCount;
- }
}
diff --git a/src/com/android/server/telecom/CallIntentProcessor.java b/src/com/android/server/telecom/CallIntentProcessor.java
index a6840b9..57db8a4 100644
--- a/src/com/android/server/telecom/CallIntentProcessor.java
+++ b/src/com/android/server/telecom/CallIntentProcessor.java
@@ -33,6 +33,12 @@
*/
public static final String KEY_IS_PRIVILEGED_DIALER = "is_privileged_dialer";
+ /**
+ * The user initiating the outgoing call.
+ */
+ public static final String KEY_INITIATING_USER = "initiating_user";
+
+
private final Context mContext;
private final CallsManager mCallsManager;
@@ -96,8 +102,11 @@
final boolean isPrivilegedDialer = intent.getBooleanExtra(KEY_IS_PRIVILEGED_DIALER, false);
+ UserHandle initiatingUser = intent.getParcelableExtra(KEY_INITIATING_USER);
+
// Send to CallsManager to ensure the InCallUI gets kicked off before the broadcast returns
- Call call = callsManager.startOutgoingCall(handle, phoneAccountHandle, clientExtras);
+ Call call = callsManager
+ .startOutgoingCall(handle, phoneAccountHandle, clientExtras, initiatingUser);
if (call != null) {
// Asynchronous calls should not usually be made inside a BroadcastReceiver because once
diff --git a/src/com/android/server/telecom/CallLogManager.java b/src/com/android/server/telecom/CallLogManager.java
index 1fe491e..bf9aa39 100755
--- a/src/com/android/server/telecom/CallLogManager.java
+++ b/src/com/android/server/telecom/CallLogManager.java
@@ -20,13 +20,16 @@
import android.content.Intent;
import android.net.Uri;
import android.os.AsyncTask;
+import android.os.UserHandle;
import android.provider.CallLog.Calls;
import android.telecom.DisconnectCause;
+import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telecom.VideoProfile;
import android.telephony.PhoneNumberUtils;
// TODO: Needed for move to system service: import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.CallerInfo;
/**
@@ -34,7 +37,8 @@
* caller details to the call log. All logging activity will be performed asynchronously in a
* background thread to avoid blocking on the main thread.
*/
-final class CallLogManager extends CallsManagerListenerBase {
+@VisibleForTesting
+public final class CallLogManager extends CallsManagerListenerBase {
/**
* Parameter object to hold the arguments to add a call in the call log DB.
*/
@@ -51,12 +55,14 @@
* @param durationInMillis Duration of the call (milliseconds).
* @param dataUsage Data usage in bytes, or null if not applicable.
*/
- public AddCallArgs(Context context, CallerInfo callerInfo, String number,
+ public AddCallArgs(Context context, CallerInfo callerInfo, String number, String postDialDigits,
int presentation, int callType, int features, PhoneAccountHandle accountHandle,
- long creationDate, long durationInMillis, Long dataUsage) {
+ long creationDate, long durationInMillis, Long dataUsage,
+ UserHandle initiatingUser) {
this.context = context;
this.callerInfo = callerInfo;
this.number = number;
+ this.postDialDigits = postDialDigits;
this.presentation = presentation;
this.callType = callType;
this.features = features;
@@ -64,12 +70,14 @@
this.timestamp = creationDate;
this.durationInSec = (int)(durationInMillis / 1000);
this.dataUsage = dataUsage;
+ this.initiatingUser = initiatingUser;
}
// Since the members are accessed directly, we don't use the
// mXxxx notation.
public final Context context;
public final CallerInfo callerInfo;
public final String number;
+ public final String postDialDigits;
public final int presentation;
public final int callType;
public final int features;
@@ -77,11 +85,13 @@
public final long timestamp;
public final int durationInSec;
public final Long dataUsage;
+ public final UserHandle initiatingUser;
}
private static final String TAG = CallLogManager.class.getSimpleName();
private final Context mContext;
+ private final PhoneAccountRegistrar mPhoneAccountRegistrar;
private static final String ACTION_CALLS_TABLE_ADD_ENTRY =
"com.android.server.telecom.intent.action.CALLS_ADD_ENTRY";
private static final String PERMISSION_PROCESS_CALLLOG_INFO =
@@ -89,8 +99,9 @@
private static final String CALL_TYPE = "callType";
private static final String CALL_DURATION = "duration";
- public CallLogManager(Context context) {
+ public CallLogManager(Context context, PhoneAccountRegistrar phoneAccountRegistrar) {
mContext = context;
+ mPhoneAccountRegistrar = phoneAccountRegistrar;
}
@Override
@@ -145,11 +156,13 @@
accountHandle = null;
}
- // TODO(vt): Once data usage is available, wire it up here.
+ Long callDataUsage = call.getCallDataUsage() == Call.DATA_USAGE_NOT_SET ? null :
+ call.getCallDataUsage();
+
int callFeatures = getCallFeatures(call.getVideoStateHistory());
- logCall(call.getCallerInfo(), logNumber, call.getHandlePresentation(),
- callLogType, callFeatures, accountHandle, creationTime, age, null,
- call.isEmergencyCall());
+ logCall(call.getCallerInfo(), logNumber, call.getPostDialDigits(),
+ call.getHandlePresentation(), callLogType, callFeatures, accountHandle,
+ creationTime, age, callDataUsage, call.isEmergencyCall(), call.getInitiatingUser());
}
/**
@@ -157,6 +170,8 @@
*
* @param callerInfo Caller details.
* @param number The number the call was made to or from.
+ * @param postDialDigits The post-dial digits that were dialed after the number,
+ * if it was an outgoing call. Otherwise ''.
* @param presentation
* @param callType The type of call.
* @param features The features of the call.
@@ -168,6 +183,7 @@
private void logCall(
CallerInfo callerInfo,
String number,
+ String postDialDigits,
int presentation,
int callType,
int features,
@@ -175,7 +191,8 @@
long start,
long duration,
Long dataUsage,
- boolean isEmergency) {
+ boolean isEmergency,
+ UserHandle initiatingUser) {
// On some devices, to avoid accidental redialing of emergency numbers, we *never* log
// emergency calls to the Call Log. (This behavior is set on a per-product basis, based
@@ -192,8 +209,9 @@
Log.d(TAG, "Logging Calllog entry: " + callerInfo + ", "
+ Log.pii(number) + "," + presentation + ", " + callType
+ ", " + start + ", " + duration);
- AddCallArgs args = new AddCallArgs(mContext, callerInfo, number, presentation,
- callType, features, accountHandle, start, duration, dataUsage);
+ AddCallArgs args = new AddCallArgs(mContext, callerInfo, number, postDialDigits,
+ presentation, callType, features, accountHandle, start, duration, dataUsage,
+ initiatingUser);
logCallAsync(args);
} else {
Log.d(TAG, "Not adding emergency call to call log.");
@@ -257,12 +275,9 @@
Uri[] result = new Uri[count];
for (int i = 0; i < count; i++) {
AddCallArgs c = callList[i];
-
try {
// May block.
- result[i] = Calls.addCall(c.callerInfo, c.context, c.number, c.presentation,
- c.callType, c.features, c.accountHandle, c.timestamp, c.durationInSec,
- c.dataUsage, true /* addForAllUsers */);
+ result[i] = addCall(c);
} catch (Exception e) {
// This is very rare but may happen in legitimate cases.
// E.g. If the phone is encrypted and thus write request fails, it may cause
@@ -278,6 +293,34 @@
return result;
}
+ private Uri addCall(AddCallArgs c) {
+ PhoneAccount phoneAccount = mPhoneAccountRegistrar
+ .getPhoneAccountUnchecked(c.accountHandle);
+ if (phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_MULTI_USER)) {
+ if (c.initiatingUser != null &&
+ UserUtil.isManagedProfile(mContext, c.initiatingUser)) {
+ return addCall(c, c.initiatingUser);
+ } else {
+ return addCall(c, null);
+ }
+ } else {
+ return addCall(c, c.accountHandle.getUserHandle());
+ }
+ }
+
+ /**
+ * Insert the call to a specific user or all users except managed profile.
+ * @param c context
+ * @param userToBeInserted user handle of user that the call going be inserted to. null
+ * if insert to all users except managed profile.
+ */
+ private Uri addCall(AddCallArgs c, UserHandle userToBeInserted) {
+ return Calls.addCall(c.callerInfo, c.context, c.number, c.postDialDigits,
+ c.presentation, c.callType, c.features, c.accountHandle, c.timestamp,
+ c.durationInSec, c.dataUsage, userToBeInserted == null,
+ userToBeInserted);
+ }
+
/**
* Performs a simple sanity check to make sure the call was written in the database.
* Typically there is only one result per call so it is easy to identify which one failed.
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 7204469..f388977 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -22,7 +22,9 @@
import android.os.Handler;
import android.os.Looper;
import android.os.SystemProperties;
+import android.os.SystemVibrator;
import android.os.Trace;
+import android.os.UserHandle;
import android.provider.CallLog.Calls;
import android.telecom.CallAudioState;
import android.telecom.Conference;
@@ -46,8 +48,10 @@
import java.util.Collection;
import java.util.Collections;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@@ -63,7 +67,8 @@
public class CallsManager extends Call.ListenerBase implements VideoProviderProxy.Listener {
// TODO: Consider renaming this CallsManagerPlugin.
- interface CallsManagerListener {
+ @VisibleForTesting
+ public interface CallsManagerListener {
void onCallAdded(Call call);
void onCallRemoved(Call call);
void onCallStateChanged(Call call, int oldState, int newState);
@@ -97,6 +102,19 @@
private static final int[] LIVE_CALL_STATES =
{CallState.CONNECTING, CallState.SELECT_PHONE_ACCOUNT, CallState.DIALING, CallState.ACTIVE};
+ public static final String TELECOM_CALL_ID_PREFIX = "TC@";
+
+ // Maps call technologies in PhoneConstants to those in Analytics.
+ private static final Map<Integer, Integer> sAnalyticsTechnologyMap;
+ static {
+ sAnalyticsTechnologyMap = new HashMap<>(5);
+ sAnalyticsTechnologyMap.put(PhoneConstants.PHONE_TYPE_CDMA, Analytics.CDMA_PHONE);
+ sAnalyticsTechnologyMap.put(PhoneConstants.PHONE_TYPE_GSM, Analytics.GSM_PHONE);
+ sAnalyticsTechnologyMap.put(PhoneConstants.PHONE_TYPE_IMS, Analytics.IMS_PHONE);
+ sAnalyticsTechnologyMap.put(PhoneConstants.PHONE_TYPE_SIP, Analytics.SIP_PHONE);
+ sAnalyticsTechnologyMap.put(PhoneConstants.PHONE_TYPE_THIRD_PARTY,
+ Analytics.THIRD_PARTY_PHONE);
+ }
/**
* The main call repository. Keeps an instance of all live calls. New incoming and outgoing
@@ -109,6 +127,13 @@
private final Set<Call> mCalls = Collections.newSetFromMap(
new ConcurrentHashMap<Call, Boolean>(8, 0.9f, 1));
+ /**
+ * The current telecom call ID. Used when creating new instances of {@link Call}. Should
+ * only be accessed using the {@link #getNextCallId()} method which synchronizes on the
+ * {@link #mLock} sync root.
+ */
+ private int mCallId = 0;
+
private final ConnectionServiceRepository mConnectionServiceRepository;
private final DtmfLocalTonePlayer mDtmfLocalTonePlayer;
private final InCallController mInCallController;
@@ -122,6 +147,7 @@
new ConcurrentHashMap<CallsManagerListener, Boolean>(16, 0.9f, 1));
private final HeadsetMediaButton mHeadsetMediaButton;
private final WiredHeadsetManager mWiredHeadsetManager;
+ private final BluetoothManager mBluetoothManager;
private final DockManager mDockManager;
private final TtyManager mTtyManager;
private final ProximitySensorManager mProximitySensorManager;
@@ -160,7 +186,10 @@
PhoneAccountRegistrar phoneAccountRegistrar,
HeadsetMediaButtonFactory headsetMediaButtonFactory,
ProximitySensorManagerFactory proximitySensorManagerFactory,
- InCallWakeLockControllerFactory inCallWakeLockControllerFactory) {
+ InCallWakeLockControllerFactory inCallWakeLockControllerFactory,
+ CallAudioManager.AudioServiceFactory audioServiceFactory,
+ BluetoothManager bluetoothManager,
+ WiredHeadsetManager wiredHeadsetManager) {
mContext = context;
mLock = lock;
mContactsAsyncHelper = contactsAsyncHelper;
@@ -168,23 +197,48 @@
mPhoneAccountRegistrar = phoneAccountRegistrar;
mMissedCallNotifier = missedCallNotifier;
StatusBarNotifier statusBarNotifier = new StatusBarNotifier(context, this);
- mWiredHeadsetManager = new WiredHeadsetManager(context);
+ mWiredHeadsetManager = wiredHeadsetManager;
+ mBluetoothManager = bluetoothManager;
mDockManager = new DockManager(context);
- mCallAudioManager = new CallAudioManager(
- context, mLock, statusBarNotifier, mWiredHeadsetManager, mDockManager, this);
- InCallTonePlayer.Factory playerFactory = new InCallTonePlayer.Factory(mCallAudioManager, lock);
- mRinger = new Ringer(mCallAudioManager, this, playerFactory, context);
+ CallAudioRouteStateMachine callAudioRouteStateMachine = new CallAudioRouteStateMachine(
+ context,
+ this,
+ bluetoothManager,
+ wiredHeadsetManager,
+ statusBarNotifier,
+ audioServiceFactory
+ );
+ CallAudioRoutePeripheralAdapter callAudioRoutePeripheralAdapter =
+ new CallAudioRoutePeripheralAdapter(
+ callAudioRouteStateMachine,
+ bluetoothManager,
+ wiredHeadsetManager,
+ mDockManager);
+
+ mCallAudioManager = new CallAudioManager(context, mLock, this, callAudioRouteStateMachine);
+
+ InCallTonePlayer.Factory playerFactory = new InCallTonePlayer.Factory(mCallAudioManager,
+ callAudioRoutePeripheralAdapter, lock);
+
+ RingtoneFactory ringtoneFactory = new RingtoneFactory(context);
+ SystemVibrator systemVibrator = new SystemVibrator(context);
+ AsyncRingtonePlayer asyncRingtonePlayer = new AsyncRingtonePlayer();
+ SystemSettingsUtil systemSettingsUtil = new SystemSettingsUtil();
+ mRinger = new Ringer(
+ mCallAudioManager, this, playerFactory, context, systemSettingsUtil,
+ asyncRingtonePlayer, ringtoneFactory, systemVibrator);
mHeadsetMediaButton = headsetMediaButtonFactory.create(context, this, mLock);
mTtyManager = new TtyManager(context, mWiredHeadsetManager);
mProximitySensorManager = proximitySensorManagerFactory.create(context, this);
mPhoneStateBroadcaster = new PhoneStateBroadcaster(this);
- mCallLogManager = new CallLogManager(context);
+ mCallLogManager = new CallLogManager(context, phoneAccountRegistrar);
mInCallController = new InCallController(context, mLock, this);
mDtmfLocalTonePlayer = new DtmfLocalTonePlayer(context);
mConnectionServiceRepository =
new ConnectionServiceRepository(mPhoneAccountRegistrar, mContext, mLock, this);
mInCallWakeLockController = inCallWakeLockControllerFactory.create(context, this);
+ mListeners.add(mInCallWakeLockController);
mListeners.add(statusBarNotifier);
mListeners.add(mCallLogManager);
mListeners.add(mPhoneStateBroadcaster);
@@ -241,14 +295,25 @@
}
@Override
- public void onSuccessfulIncomingCall(Call incomingCall) {
+ public void onSuccessfulIncomingCall(Call incomingCall, boolean shouldSendToVoicemail) {
Log.d(this, "onSuccessfulIncomingCall");
- setCallState(incomingCall, CallState.RINGING, "successful incoming call");
- if (hasMaximumRingingCalls() || hasMaximumDialingCalls()) {
+ // Only set the incoming call as ringing if it isn't already disconnected. It is possible
+ // that the connection service disconnected the call before it was even added to Telecom, in
+ // which case it makes no sense to set it back to a ringing state.
+ if (incomingCall.getState() != CallState.DISCONNECTED &&
+ incomingCall.getState() != CallState.DISCONNECTING) {
+ setCallState(incomingCall, CallState.RINGING,
+ shouldSendToVoicemail ? "directing to voicemail" : "successful incoming call");
+ } else {
+ Log.i(this, "onSuccessfulIncomingCall: call already disconnected.");
+ }
+
+ if (hasMaximumRingingCalls() || hasMaximumDialingCalls() || shouldSendToVoicemail) {
incomingCall.reject(false, null);
- // since the call was not added to the list of calls, we have to call the missed
+ // Since the call was not added to the list of calls, we have to call the missed
// call notifier and the call logger manually.
+ // Do we need missed call notification for direct to Voicemail calls?
mMissedCallNotifier.showMissedCallNotification(incomingCall);
mCallLogManager.logCall(incomingCall, Calls.MISSED_TYPE);
} else {
@@ -304,7 +369,8 @@
@Override
public void run() {
synchronized (mLock) {
- // Set a timeout to stop the tone in case there isn't another tone to follow.
+ // Set a timeout to stop the tone in case there isn't another tone to
+ // follow.
mDtmfLocalTonePlayer.stopTone(call);
}
}
@@ -414,11 +480,13 @@
}
}
- Collection<Call> getCalls() {
+ @VisibleForTesting
+ public Collection<Call> getCalls() {
return Collections.unmodifiableCollection(mCalls);
}
- Call getForegroundCall() {
+ @VisibleForTesting
+ public Call getForegroundCall() {
return mForegroundCall;
}
@@ -469,7 +537,8 @@
return mTtyManager.getCurrentTtyMode();
}
- void addListener(CallsManagerListener listener) {
+ @VisibleForTesting
+ public void addListener(CallsManagerListener listener) {
mListeners.add(listener);
}
@@ -492,6 +561,7 @@
handle = extras.getParcelable(TelephonyManager.EXTRA_INCOMING_NUMBER);
}
Call call = new Call(
+ getNextCallId(),
mContext,
this,
mLock,
@@ -502,8 +572,16 @@
null /* gatewayInfo */,
null /* connectionManagerPhoneAccount */,
phoneAccountHandle,
- true /* isIncoming */,
- false /* isConference */);
+ Call.CALL_DIRECTION_INCOMING /* callDirection */,
+ false /* forceAttachToExistingConnection */,
+ false /* isConference */
+ );
+
+ call.initAnalytics();
+ if (mForegroundCall != null) {
+ mForegroundCall.getAnalytics().setCallIsInterrupted(true);
+ call.getAnalytics().setCallIsAdditional(true);
+ }
call.setIntentExtras(extras);
// TODO: Move this to be a part of addCall()
@@ -515,6 +593,7 @@
Uri handle = extras.getParcelable(TelecomManager.EXTRA_UNKNOWN_CALL_HANDLE);
Log.i(this, "addNewUnknownCall with handle: %s", Log.pii(handle));
Call call = new Call(
+ getNextCallId(),
mContext,
this,
mLock,
@@ -525,11 +604,14 @@
null /* gatewayInfo */,
null /* connectionManagerPhoneAccount */,
phoneAccountHandle,
+ Call.CALL_DIRECTION_UNKNOWN /* callDirection */,
// Use onCreateIncomingConnection in TelephonyConnectionService, so that we attach
// to the existing connection instead of trying to create a new one.
- true /* isIncoming */,
- false /* isConference */);
- call.setIsUnknown(true);
+ true /* forceAttachToExistingConnection */,
+ false /* isConference */
+ );
+ call.initAnalytics();
+
call.setIntentExtras(extras);
call.addListener(this);
call.startCreateConnection(mPhoneAccountRegistrar);
@@ -549,8 +631,8 @@
return TextUtils.equals(number1, number2);
}
- private Call getNewOutgoingCall(Uri handle) {
- // First check to see if we can reuse any of the calls that are waiting to disconnect.
+ private Call reuseOutgoingCall(Uri handle) {
+ // Check to see if we can reuse any of the calls that are waiting to disconnect.
// See {@link Call#abort} and {@link #onCanceledViaNewOutgoingCall} for more information.
Call reusedCall = null;
for (Call pendingCall : mPendingCallsToDisconnect) {
@@ -563,25 +645,8 @@
pendingCall.disconnect();
}
}
- if (reusedCall != null) {
- return reusedCall;
- }
- // Create a call with original handle. The handle may be changed when the call is attached
- // to a connection service, but in most cases will remain the same.
- return new Call(
- mContext,
- this,
- mLock,
- mConnectionServiceRepository,
- mContactsAsyncHelper,
- mCallerInfoAsyncQueryFactory,
- handle,
- null /* gatewayInfo */,
- null /* connectionManagerPhoneAccount */,
- null /* phoneAccountHandle */,
- false /* isIncoming */,
- false /* isConference */);
+ return reusedCall;
}
/**
@@ -591,13 +656,40 @@
* @param phoneAccountHandle The phone account which contains the component name of the
* connection service to use for this call.
* @param extras The optional extras Bundle passed with the intent used for the incoming call.
+ * @param initiatingUser {@link UserHandle} of user that place the outgoing call.
*/
- Call startOutgoingCall(Uri handle, PhoneAccountHandle phoneAccountHandle, Bundle extras) {
- Call call = getNewOutgoingCall(handle);
+ Call startOutgoingCall(Uri handle, PhoneAccountHandle phoneAccountHandle, Bundle extras,
+ UserHandle initiatingUser) {
+ boolean isReusedCall = true;
+ Call call = reuseOutgoingCall(handle);
+
+ // Create a call with original handle. The handle may be changed when the call is attached
+ // to a connection service, but in most cases will remain the same.
+ if (call == null) {
+ call = new Call(getNextCallId(), mContext,
+ this,
+ mLock,
+ mConnectionServiceRepository,
+ mContactsAsyncHelper,
+ mCallerInfoAsyncQueryFactory,
+ handle,
+ null /* gatewayInfo */,
+ null /* connectionManagerPhoneAccount */,
+ null /* phoneAccountHandle */,
+ Call.CALL_DIRECTION_OUTGOING /* callDirection */,
+ false /* forceAttachToExistingConnection */,
+ false /* isConference */
+ );
+ call.setInitiatingUser(initiatingUser);
+
+ call.initAnalytics();
+
+ isReusedCall = false;
+ }
List<PhoneAccountHandle> accounts =
- mPhoneAccountRegistrar.getCallCapablePhoneAccounts(handle.getScheme(), false);
-
+ mPhoneAccountRegistrar.getCallCapablePhoneAccounts(handle.getScheme(), false,
+ initiatingUser);
Log.v(this, "startOutgoingCall found accounts = " + accounts);
if (mForegroundCall != null) {
@@ -628,7 +720,8 @@
// No preset account, check if default exists that supports the URI scheme for the
// handle.
phoneAccountHandle =
- mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(handle.getScheme());
+ mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(handle.getScheme(),
+ initiatingUser);
}
call.setTargetPhoneAccount(phoneAccountHandle);
@@ -636,13 +729,16 @@
boolean isPotentialInCallMMICode = isPotentialInCallMMICode(handle);
// Do not support any more live calls. Our options are to move a call to hold, disconnect
- // a call, or cancel this call altogether.
- if (!isPotentialInCallMMICode && !makeRoomForOutgoingCall(call, call.isEmergencyCall())) {
+ // a call, or cancel this call altogether. If a call is being reused, then it has already
+ // passed the makeRoomForOutgoingCall check once and will fail the second time due to the
+ // call transitioning into the CONNECTING state.
+ if (!isPotentialInCallMMICode && (!isReusedCall &&
+ !makeRoomForOutgoingCall(call, call.isEmergencyCall()))) {
// just cancel at this point.
Log.i(this, "No remaining room for outgoing call: %s", call);
if (mCalls.contains(call)) {
// This call can already exist if it is a reused call,
- // See {@link #getNewOutgoingCall}.
+ // See {@link #reuseOutgoingCall}.
call.disconnect();
}
return null;
@@ -670,7 +766,7 @@
call.addListener(this);
} else if (!mCalls.contains(call)) {
// We check if mCalls already contains the call because we could potentially be reusing
- // a call which was previously added (See {@link #getNewOutgoingCall}).
+ // a call which was previously added (See {@link #reuseOutgoingCall}).
addCall(call);
}
@@ -705,6 +801,9 @@
call.setHandle(uriHandle);
call.setGatewayInfo(gatewayInfo);
+ // Auto-enable speakerphone if the originating intent specified to do so, or if the call
+ // is a video call.
+ call.setStartWithSpeakerphoneOn(speakerphoneOn || isSpeakerphoneAutoEnabled(videoState));
call.setVideoState(videoState);
if (speakerphoneOn) {
@@ -732,8 +831,8 @@
// Otherwise the connection will be initiated when the account is set by the user.
call.startCreateConnection(mPhoneAccountRegistrar);
} else if (mPhoneAccountRegistrar.getCallCapablePhoneAccounts(
- requireCallCapableAccountByHandle ? call.getHandle().getScheme() : null, false)
- .isEmpty()) {
+ requireCallCapableAccountByHandle ? call.getHandle().getScheme() : null, false,
+ call.getInitiatingUser()).isEmpty()) {
// If there are no call capable accounts, disconnect the call.
markCallAsDisconnected(call, new DisconnectCause(DisconnectCause.CANCELED,
"No registered PhoneAccounts"));
@@ -747,7 +846,8 @@
* @param call The call to conference.
* @param otherCall The other call to conference with.
*/
- void conference(Call call, Call otherCall) {
+ @VisibleForTesting
+ public void conference(Call call, Call otherCall) {
call.conferenceWith(otherCall);
}
@@ -759,7 +859,8 @@
* @param call The call to answer.
* @param videoState The video state in which to answer the call.
*/
- void answerCall(Call call, int videoState) {
+ @VisibleForTesting
+ public void answerCall(Call call, int videoState) {
if (!mCalls.contains(call)) {
Log.i(this, "Request to answer a non-existent call %s", call);
} else {
@@ -800,15 +901,32 @@
// We do not update the UI until we get confirmation of the answer() through
// {@link #markCallAsActive}.
call.answer(videoState);
- if (VideoProfile.isVideo(videoState) &&
- !mWiredHeadsetManager.isPluggedIn() &&
- !mCallAudioManager.isBluetoothDeviceAvailable() &&
- isSpeakerEnabledForVideoCalls()) {
+ if (isSpeakerphoneAutoEnabled(videoState)) {
call.setStartWithSpeakerphoneOn(true);
}
}
}
+ /**
+ * Determines if the speakerphone should be automatically enabled for the call. Speakerphone
+ * should be enabled if the call is a video call and bluetooth or the wired headset are not in
+ * use.
+ *
+ * @param videoState The video state of the call.
+ * @return {@code true} if the speakerphone should be enabled.
+ */
+ private boolean isSpeakerphoneAutoEnabled(int videoState) {
+ return VideoProfile.isVideo(videoState) &&
+ !mWiredHeadsetManager.isPluggedIn() &&
+ !mBluetoothManager.isBluetoothAvailable() &&
+ isSpeakerEnabledForVideoCalls();
+ }
+
+ /**
+ * Determines if the speakerphone should be automatically enabled for video calls.
+ *
+ * @return {@code true} if the speakerphone should automatically be enabled.
+ */
private static boolean isSpeakerEnabledForVideoCalls() {
return (SystemProperties.getInt(TelephonyProperties.PROPERTY_VIDEOCALL_AUDIO_OUTPUT,
PhoneConstants.AUDIO_OUTPUT_DEFAULT) ==
@@ -820,7 +938,8 @@
* app through {@link InCallAdapter} after Telecom notifies it of an incoming call followed by
* the user opting to reject said call.
*/
- void rejectCall(Call call, boolean rejectWithMessage, String textMessage) {
+ @VisibleForTesting
+ public void rejectCall(Call call, boolean rejectWithMessage, String textMessage) {
if (!mCalls.contains(call)) {
Log.i(this, "Request to reject a non-existent call %s", call);
} else {
@@ -836,7 +955,8 @@
*
* @param digit The DTMF digit to play.
*/
- void playDtmfTone(Call call, char digit) {
+ @VisibleForTesting
+ public void playDtmfTone(Call call, char digit) {
if (!mCalls.contains(call)) {
Log.i(this, "Request to play DTMF in a non-existent call %s", call);
} else {
@@ -848,7 +968,8 @@
/**
* Instructs Telecom to stop the currently playing DTMF tone, if any.
*/
- void stopDtmfTone(Call call) {
+ @VisibleForTesting
+ public void stopDtmfTone(Call call) {
if (!mCalls.contains(call)) {
Log.i(this, "Request to stop DTMF in a non-existent call %s", call);
} else {
@@ -873,7 +994,8 @@
* in-call app through {@link InCallAdapter} for an ongoing call. This is usually triggered by
* the user hitting the end-call button.
*/
- void disconnectCall(Call call) {
+ @VisibleForTesting
+ public void disconnectCall(Call call) {
Log.v(this, "disconnectCall %s", call);
if (!mCalls.contains(call)) {
@@ -901,7 +1023,8 @@
* in-call app through {@link InCallAdapter} for an ongoing call. This is usually triggered by
* the user hitting the hold button during an active call.
*/
- void holdCall(Call call) {
+ @VisibleForTesting
+ public void holdCall(Call call) {
if (!mCalls.contains(call)) {
Log.w(this, "Unknown call (%s) asked to be put on hold", call);
} else {
@@ -915,7 +1038,8 @@
* the in-call app through {@link InCallAdapter} for an ongoing call. This is usually triggered
* by the user hitting the hold button during a held call.
*/
- void unholdCall(Call call) {
+ @VisibleForTesting
+ public void unholdCall(Call call) {
if (!mCalls.contains(call)) {
Log.w(this, "Unknown call (%s) asked to be removed from hold", call);
} else {
@@ -930,6 +1054,20 @@
}
}
+ @Override
+ public void onExtrasChanged(Call call) {
+ if (call.getExtras() != null
+ && call.getExtras().containsKey(TelecomManager.EXTRA_CALL_TECHNOLOGY_TYPE)) {
+
+ Integer analyticsCallTechnology = sAnalyticsTechnologyMap.get(
+ call.getExtras().getInt(TelecomManager.EXTRA_CALL_TECHNOLOGY_TYPE));
+ if (analyticsCallTechnology == null) {
+ analyticsCallTechnology = Analytics.THIRD_PARTY_PHONE;
+ }
+ call.getAnalytics().addCallTechnology(analyticsCallTechnology);
+ }
+ }
+
/** Called by the in-call UI to change the mute state. */
void mute(boolean shouldMute) {
mCallAudioManager.mute(shouldMute);
@@ -978,13 +1116,16 @@
}
if (setDefault) {
- mPhoneAccountRegistrar.setUserSelectedOutgoingPhoneAccount(account);
+ mPhoneAccountRegistrar
+ .setUserSelectedOutgoingPhoneAccount(account, call.getInitiatingUser());
}
}
}
/** Called when the audio state changes. */
- void onCallAudioStateChanged(CallAudioState oldAudioState, CallAudioState newAudioState) {
+ @VisibleForTesting
+ public void onCallAudioStateChanged(CallAudioState oldAudioState, CallAudioState
+ newAudioState) {
Log.v(this, "onAudioStateChanged, audioState: %s -> %s", oldAudioState, newAudioState);
for (CallsManagerListener listener : mListeners) {
listener.onCallAudioStateChanged(oldAudioState, newAudioState);
@@ -1130,7 +1271,8 @@
return getFirstCallWithState(CallState.RINGING);
}
- Call getActiveCall() {
+ @VisibleForTesting
+ public Call getActiveCall() {
return getFirstCallWithState(CallState.ACTIVE);
}
@@ -1138,11 +1280,13 @@
return getFirstCallWithState(CallState.DIALING);
}
- Call getHeldCall() {
+ @VisibleForTesting
+ public Call getHeldCall() {
return getFirstCallWithState(CallState.ON_HOLD);
}
- int getNumHeldCalls() {
+ @VisibleForTesting
+ public int getNumHeldCalls() {
int count = 0;
for (Call call : mCalls) {
if (call.getParentCall() == null && call.getState() == CallState.ON_HOLD) {
@@ -1152,7 +1296,8 @@
return count;
}
- Call getOutgoingCall() {
+ @VisibleForTesting
+ public Call getOutgoingCall() {
return getFirstCallWithState(OUTGOING_CALL_STATES);
}
@@ -1193,6 +1338,7 @@
}
Call createConferenceCall(
+ String callId,
PhoneAccountHandle phoneAccount,
ParcelableConference parcelableConference) {
@@ -1204,6 +1350,7 @@
parcelableConference.getConnectTimeMillis();
Call call = new Call(
+ callId,
mContext,
this,
mLock,
@@ -1214,7 +1361,8 @@
null /* gatewayInfo */,
null /* connectionManagerPhoneAccount */,
phoneAccount,
- false /* isIncoming */,
+ Call.CALL_DIRECTION_UNDEFINED /* callDirection */,
+ false /* forceAttachToExistingConnection */,
true /* isConference */,
connectTime);
@@ -1267,7 +1415,7 @@
call.addListener(this);
mCalls.add(call);
- // TODO: Update mForegroundCall prior to invoking
+ updateCallsManagerState();
// onCallAdded for calls which immediately take the foreground (like the first call).
for (CallsManagerListener listener : mListeners) {
if (Log.SYSTRACE_DEBUG) {
@@ -1278,7 +1426,6 @@
Trace.endSection();
}
}
- updateCallsManagerState();
Trace.endSection();
}
@@ -1300,6 +1447,7 @@
// Only broadcast changes for calls that are being tracked.
if (shouldNotify) {
+ updateCallsManagerState();
for (CallsManagerListener listener : mListeners) {
if (Log.SYSTRACE_DEBUG) {
Trace.beginSection(listener.getClass().toString() + " onCallRemoved");
@@ -1309,7 +1457,6 @@
Trace.endSection();
}
}
- updateCallsManagerState();
}
Trace.endSection();
}
@@ -1340,6 +1487,7 @@
Trace.beginSection("onCallStateChanged");
// Only broadcast state change for calls that are being tracked.
if (mCalls.contains(call)) {
+ updateCallsManagerState();
for (CallsManagerListener listener : mListeners) {
if (Log.SYSTRACE_DEBUG) {
Trace.beginSection(listener.getClass().toString() + " onCallStateChanged");
@@ -1349,7 +1497,6 @@
Trace.endSection();
}
}
- updateCallsManagerState();
}
Trace.endSection();
}
@@ -1433,7 +1580,7 @@
* MMI codes which can be dialed when one or more calls are in progress.
* <P>
* Checks for numbers formatted similar to the MMI codes defined in:
- * {@link com.android.internal.telephony.gsm.GSMPhone#handleInCallMmiCommands(String)}
+ * {@link com.android.internal.telephony.GsmCdmaPhone#handleInCallMmiCommands(String)}
* and
* {@link com.android.internal.telephony.imsphone.ImsPhone#handleInCallMmiCommands(String)}
*
@@ -1508,12 +1655,16 @@
// Disconnect the current outgoing call if it's not an emergency call. If the
// user tries to make two outgoing calls to different emergency call numbers,
// we will try to connect the first outgoing call.
+ call.getAnalytics().setCallIsAdditional(true);
+ outgoingCall.getAnalytics().setCallIsInterrupted(true);
outgoingCall.disconnect();
return true;
}
if (outgoingCall.getState() == CallState.SELECT_PHONE_ACCOUNT) {
// If there is an orphaned call in the {@link CallState#SELECT_PHONE_ACCOUNT}
// state, just disconnect it since the user has explicitly started a new call.
+ call.getAnalytics().setCallIsAdditional(true);
+ outgoingCall.getAnalytics().setCallIsInterrupted(true);
outgoingCall.disconnect();
return true;
}
@@ -1525,6 +1676,8 @@
if (isEmergency) {
// Kill the current active call, this is easier then trying to disconnect a
// holding call and hold an active call.
+ call.getAnalytics().setCallIsAdditional(true);
+ liveCall.getAnalytics().setCallIsInterrupted(true);
liveCall.disconnect();
return true;
}
@@ -1554,6 +1707,8 @@
// how to handle the new call relative to the current one.
if (Objects.equals(liveCallPhoneAccount, call.getTargetPhoneAccount())) {
Log.i(this, "makeRoomForOutgoingCall: phoneAccount matches.");
+ call.getAnalytics().setCallIsAdditional(true);
+ liveCall.getAnalytics().setCallIsInterrupted(true);
return true;
} else if (call.getTargetPhoneAccount() == null) {
// Without a phone account, we can't say reliably that the call will fail.
@@ -1568,6 +1723,8 @@
// Try to hold the live call before attempting the new outgoing call.
if (liveCall.can(Connection.CAPABILITY_HOLD)) {
Log.i(this, "makeRoomForOutgoingCall: holding live call.");
+ call.getAnalytics().setCallIsAdditional(true);
+ liveCall.getAnalytics().setCallIsInterrupted(true);
liveCall.hold();
return true;
}
@@ -1613,6 +1770,7 @@
*/
Call createCallForExistingConnection(String callId, ParcelableConnection connection) {
Call call = new Call(
+ callId,
mContext,
this,
mLock,
@@ -1623,10 +1781,14 @@
null /* gatewayInfo */,
null /* connectionManagerPhoneAccount */,
connection.getPhoneAccount(), /* targetPhoneAccountHandle */
- false /* isIncoming */,
+ Call.CALL_DIRECTION_UNDEFINED /* callDirection */,
+ false /* forceAttachToExistingConnection */,
false /* isConference */,
connection.getConnectTimeMillis() /* connectTimeMillis */);
+ call.initAnalytics();
+ call.getAnalytics().setCreatedFromExistingConnection(true);
+
setCallState(call, Call.getStateFromConnectionState(connection.getState()),
"existing connection");
call.setConnectionCapabilities(connection.getConnectionCapabilities());
@@ -1640,6 +1802,15 @@
}
/**
+ * @return A new unique telecom call Id.
+ */
+ private String getNextCallId() {
+ synchronized(mLock) {
+ return TELECOM_CALL_ID_PREFIX + (++mCallId);
+ }
+ }
+
+ /**
* Dumps the state of the {@link CallsManager}.
*
* @param pw The {@code IndentingPrintWriter} to write the state to.
diff --git a/src/com/android/server/telecom/ConnectionServiceRepository.java b/src/com/android/server/telecom/ConnectionServiceRepository.java
index a587b59..4685704 100644
--- a/src/com/android/server/telecom/ConnectionServiceRepository.java
+++ b/src/com/android/server/telecom/ConnectionServiceRepository.java
@@ -21,6 +21,7 @@
import android.os.UserHandle;
import android.util.Pair;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
import java.util.HashMap;
@@ -28,7 +29,8 @@
/**
* Searches for and returns connection services.
*/
-final class ConnectionServiceRepository {
+@VisibleForTesting
+public class ConnectionServiceRepository {
private final HashMap<Pair<ComponentName, UserHandle>, ConnectionServiceWrapper> mServiceCache =
new HashMap<>();
private final PhoneAccountRegistrar mPhoneAccountRegistrar;
@@ -57,7 +59,8 @@
mCallsManager = callsManager;
}
- ConnectionServiceWrapper getService(ComponentName componentName, UserHandle userHandle) {
+ @VisibleForTesting
+ public ConnectionServiceWrapper getService(ComponentName componentName, UserHandle userHandle) {
Pair<ComponentName, UserHandle> cacheKey = Pair.create(componentName, userHandle);
ConnectionServiceWrapper service = mServiceCache.get(cacheKey);
if (service == null) {
diff --git a/src/com/android/server/telecom/ConnectionServiceWrapper.java b/src/com/android/server/telecom/ConnectionServiceWrapper.java
index 8e58f22..56fbd5c 100644
--- a/src/com/android/server/telecom/ConnectionServiceWrapper.java
+++ b/src/com/android/server/telecom/ConnectionServiceWrapper.java
@@ -37,6 +37,7 @@
import android.telecom.TelecomManager;
import android.telecom.VideoProfile;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telecom.IConnectionService;
import com.android.internal.telecom.IConnectionServiceAdapter;
import com.android.internal.telecom.IVideoProvider;
@@ -57,220 +58,214 @@
* {@link IConnectionService} directly and instead should use this class to invoke methods of
* {@link IConnectionService}.
*/
-final class ConnectionServiceWrapper extends ServiceBinder {
+@VisibleForTesting
+public class ConnectionServiceWrapper extends ServiceBinder {
private final class Adapter extends IConnectionServiceAdapter.Stub {
@Override
- public void handleCreateConnectionComplete(
- String callId,
- ConnectionRequest request,
+ public void handleCreateConnectionComplete(String callId, ConnectionRequest request,
ParcelableConnection connection) {
+ Log.startSession("CSW.hCCC");
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
logIncoming("handleCreateConnectionComplete %s", callId);
- if (mCallIdMapper.isValidCallId(callId)) {
- ConnectionServiceWrapper.this
- .handleCreateConnectionComplete(callId, request, connection);
- }
+ ConnectionServiceWrapper.this
+ .handleCreateConnectionComplete(callId, request, connection);
}
} finally {
Binder.restoreCallingIdentity(token);
+ Log.endSession();
}
}
@Override
public void setActive(String callId) {
+ Log.startSession("CSW.sA");
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
logIncoming("setActive %s", callId);
- if (mCallIdMapper.isValidCallId(callId) || mCallIdMapper
- .isValidConferenceId(callId)) {
- Call call = mCallIdMapper.getCall(callId);
- if (call != null) {
- mCallsManager.markCallAsActive(call);
- } else {
- // Log.w(this, "setActive, unknown call id: %s", msg.obj);
- }
+ Call call = mCallIdMapper.getCall(callId);
+ if (call != null) {
+ mCallsManager.markCallAsActive(call);
+ } else {
+ // Log.w(this, "setActive, unknown call id: %s", msg.obj);
}
}
} finally {
Binder.restoreCallingIdentity(token);
+ Log.endSession();
}
}
@Override
public void setRinging(String callId) {
+ Log.startSession("CSW.sR");
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
logIncoming("setRinging %s", callId);
- if (mCallIdMapper.isValidCallId(callId)) {
- Call call = mCallIdMapper.getCall(callId);
- if (call != null) {
- mCallsManager.markCallAsRinging(call);
- } else {
- // Log.w(this, "setRinging, unknown call id: %s", msg.obj);
- }
+ Call call = mCallIdMapper.getCall(callId);
+ if (call != null) {
+ mCallsManager.markCallAsRinging(call);
+ } else {
+ // Log.w(this, "setRinging, unknown call id: %s", msg.obj);
}
}
} finally {
Binder.restoreCallingIdentity(token);
+ Log.endSession();
}
}
@Override
public void setVideoProvider(String callId, IVideoProvider videoProvider) {
+ Log.startSession("CSW.sVP");
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
logIncoming("setVideoProvider %s", callId);
- if (mCallIdMapper.isValidCallId(callId)
- || mCallIdMapper.isValidConferenceId(callId)) {
- Call call = mCallIdMapper.getCall(callId);
- if (call != null) {
- call.setVideoProvider(videoProvider);
- }
+ Call call = mCallIdMapper.getCall(callId);
+ if (call != null) {
+ call.setVideoProvider(videoProvider);
}
}
} finally {
Binder.restoreCallingIdentity(token);
+ Log.endSession();
}
}
@Override
public void setDialing(String callId) {
+ Log.startSession("CSW.sD");
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
logIncoming("setDialing %s", callId);
- if (mCallIdMapper.isValidCallId(callId)) {
- Call call = mCallIdMapper.getCall(callId);
- if (call != null) {
- mCallsManager.markCallAsDialing(call);
- } else {
- // Log.w(this, "setDialing, unknown call id: %s", msg.obj);
- }
+ Call call = mCallIdMapper.getCall(callId);
+ if (call != null) {
+ mCallsManager.markCallAsDialing(call);
+ } else {
+ // Log.w(this, "setDialing, unknown call id: %s", msg.obj);
}
}
} finally {
Binder.restoreCallingIdentity(token);
+ Log.endSession();
}
}
@Override
public void setDisconnected(String callId, DisconnectCause disconnectCause) {
+ Log.startSession("CSW.sD");
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
logIncoming("setDisconnected %s %s", callId, disconnectCause);
- if (mCallIdMapper.isValidCallId(callId) || mCallIdMapper
- .isValidConferenceId(callId)) {
- Call call = mCallIdMapper.getCall(callId);
- Log.d(this, "disconnect call %s %s", disconnectCause, call);
- if (call != null) {
- mCallsManager.markCallAsDisconnected(call, disconnectCause);
- } else {
- // Log.w(this, "setDisconnected, unknown call id: %s", args.arg1);
- }
+ Call call = mCallIdMapper.getCall(callId);
+ Log.d(this, "disconnect call %s %s", disconnectCause, call);
+ if (call != null) {
+ mCallsManager.markCallAsDisconnected(call, disconnectCause);
+ } else {
+ // Log.w(this, "setDisconnected, unknown call id: %s", args.arg1);
}
}
} finally {
Binder.restoreCallingIdentity(token);
+ Log.endSession();
}
}
@Override
public void setOnHold(String callId) {
+ Log.startSession("CSW.sOH");
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
logIncoming("setOnHold %s", callId);
- if (mCallIdMapper.isValidCallId(callId) || mCallIdMapper
- .isValidConferenceId(callId)) {
- Call call = mCallIdMapper.getCall(callId);
- if (call != null) {
- mCallsManager.markCallAsOnHold(call);
- } else {
- // Log.w(this, "setOnHold, unknown call id: %s", msg.obj);
- }
+ Call call = mCallIdMapper.getCall(callId);
+ if (call != null) {
+ mCallsManager.markCallAsOnHold(call);
+ } else {
+ // Log.w(this, "setOnHold, unknown call id: %s", msg.obj);
}
}
} finally {
Binder.restoreCallingIdentity(token);
+ Log.endSession();
}
}
@Override
public void setRingbackRequested(String callId, boolean ringback) {
+ Log.startSession("CSW.SRR");
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
logIncoming("setRingbackRequested %s %b", callId, ringback);
- if (mCallIdMapper.isValidCallId(callId)) {
- Call call = mCallIdMapper.getCall(callId);
- if (call != null) {
- call.setRingbackRequested(ringback);
- } else {
- // Log.w(this, "setRingback, unknown call id: %s", args.arg1);
- }
+ Call call = mCallIdMapper.getCall(callId);
+ if (call != null) {
+ call.setRingbackRequested(ringback);
+ } else {
+ // Log.w(this, "setRingback, unknown call id: %s", args.arg1);
}
}
} finally {
Binder.restoreCallingIdentity(token);
+ Log.endSession();
}
}
@Override
public void removeCall(String callId) {
+ Log.startSession("CSW.rC");
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
logIncoming("removeCall %s", callId);
- if (mCallIdMapper.isValidCallId(callId) || mCallIdMapper
- .isValidConferenceId(callId)) {
- Call call = mCallIdMapper.getCall(callId);
- if (call != null) {
- if (call.isAlive()) {
- mCallsManager.markCallAsDisconnected(
- call, new DisconnectCause(DisconnectCause.REMOTE));
- } else {
- mCallsManager.markCallAsRemoved(call);
- }
+ Call call = mCallIdMapper.getCall(callId);
+ if (call != null) {
+ if (call.isAlive()) {
+ mCallsManager.markCallAsDisconnected(
+ call, new DisconnectCause(DisconnectCause.REMOTE));
+ } else {
+ mCallsManager.markCallAsRemoved(call);
}
}
}
} finally {
Binder.restoreCallingIdentity(token);
+ Log.endSession();
}
}
@Override
public void setConnectionCapabilities(String callId, int connectionCapabilities) {
+ Log.startSession("CSW.sCC");
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
logIncoming("setConnectionCapabilities %s %d", callId, connectionCapabilities);
- if (mCallIdMapper.isValidCallId(callId) || mCallIdMapper
- .isValidConferenceId(callId)) {
- Call call = mCallIdMapper.getCall(callId);
- if (call != null) {
- call.setConnectionCapabilities(connectionCapabilities);
- } else {
- // Log.w(ConnectionServiceWrapper.this,
- // "setConnectionCapabilities, unknown call id: %s", msg.obj);
- }
+ Call call = mCallIdMapper.getCall(callId);
+ if (call != null) {
+ call.setConnectionCapabilities(connectionCapabilities);
+ } else {
+ // Log.w(ConnectionServiceWrapper.this,
+ // "setConnectionCapabilities, unknown call id: %s", msg.obj);
}
}
} finally {
Binder.restoreCallingIdentity(token);
+ Log.endSession();
}
}
@Override
public void setIsConferenced(String callId, String conferenceCallId) {
+ Log.startSession("CSW.sIC");
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -290,43 +285,44 @@
}
} finally {
Binder.restoreCallingIdentity(token);
+ Log.endSession();
}
}
@Override
public void setConferenceMergeFailed(String callId) {
+ Log.startSession("CSW.sCMF");
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
logIncoming("setConferenceMergeFailed %s", callId);
- if (mCallIdMapper.isValidCallId(callId)) {
- // TODO: we should move the UI for indication a merge failure here
- // from CallNotifier.onSuppServiceFailed(). This way the InCallUI can
- // deliver the message anyway that they want. b/20530631.
- Call call = mCallIdMapper.getCall(callId);
- if (call != null) {
- // Just refresh the connection capabilities so that the UI
- // is forced to reenable the merge button as the capability
- // is still on the connection. Note when b/20530631 is fixed, we need
- // to revisit this fix to remove this hacky way of unhiding the merge
- // button (side effect of reprocessing the capabilities) and plumb
- // the failure event all the way to InCallUI instead of stopping
- // it here. That way we can also handle the UI of notifying that
- // the merged has failed.
- call.setConnectionCapabilities(call.getConnectionCapabilities(), true);
- } else {
- Log.w(this, "setConferenceMergeFailed, unknown call id: %s", callId);
- }
+ // TODO: we should move the UI for indication a merge failure here
+ // from CallNotifier.onSuppServiceFailed(). This way the InCallUI can
+ // deliver the message anyway that they want. b/20530631.
+ Call call = mCallIdMapper.getCall(callId);
+ if (call != null) {
+ // Just refresh the connection capabilities so that the UI
+ // is forced to reenable the merge button as the capability
+ // is still on the connection. Note when b/20530631 is fixed, we need
+ // to revisit this fix to remove this hacky way of unhiding the merge
+ // button (side effect of reprocessing the capabilities) and plumb
+ // the failure event all the way to InCallUI instead of stopping
+ // it here. That way we can also handle the UI of notifying that
+ // the merged has failed.
+ call.setConnectionCapabilities(call.getConnectionCapabilities(), true);
+ } else {
+ Log.w(this, "setConferenceMergeFailed, unknown call id: %s", callId);
}
-
}
} finally {
Binder.restoreCallingIdentity(token);
+ Log.endSession();
}
}
@Override
public void addConferenceCall(String callId, ParcelableConference parcelableConference) {
+ Log.startSession("CSW.aCC");
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -358,7 +354,7 @@
parcelableConference.getPhoneAccount() != null) {
phAcc = parcelableConference.getPhoneAccount();
}
- Call conferenceCall = mCallsManager.createConferenceCall(
+ Call conferenceCall = mCallsManager.createConferenceCall(callId,
phAcc, parcelableConference);
mCallIdMapper.addCall(conferenceCall, callId);
conferenceCall.setConnectionService(ConnectionServiceWrapper.this);
@@ -375,206 +371,208 @@
}
} finally {
Binder.restoreCallingIdentity(token);
+ Log.endSession();
}
}
@Override
public void onPostDialWait(String callId, String remaining) throws RemoteException {
+ Log.startSession("CSW.oPDW");
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
logIncoming("onPostDialWait %s %s", callId, remaining);
- if (mCallIdMapper.isValidCallId(callId)) {
- Call call = mCallIdMapper.getCall(callId);
- if (call != null) {
- call.onPostDialWait(remaining);
- } else {
- // Log.w(this, "onPostDialWait, unknown call id: %s", args.arg1);
- }
+ Call call = mCallIdMapper.getCall(callId);
+ if (call != null) {
+ call.onPostDialWait(remaining);
+ } else {
+ // Log.w(this, "onPostDialWait, unknown call id: %s", args.arg1);
}
}
} finally {
Binder.restoreCallingIdentity(token);
+ Log.endSession();
}
}
@Override
public void onPostDialChar(String callId, char nextChar) throws RemoteException {
+ Log.startSession("CSW.oPDC");
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
logIncoming("onPostDialChar %s %s", callId, nextChar);
- if (mCallIdMapper.isValidCallId(callId)) {
- Call call = mCallIdMapper.getCall(callId);
- if (call != null) {
- call.onPostDialChar(nextChar);
- } else {
- // Log.w(this, "onPostDialChar, unknown call id: %s", args.arg1);
- }
+ Call call = mCallIdMapper.getCall(callId);
+ if (call != null) {
+ call.onPostDialChar(nextChar);
+ } else {
+ // Log.w(this, "onPostDialChar, unknown call id: %s", args.arg1);
}
}
} finally {
Binder.restoreCallingIdentity(token);
+ Log.endSession();
}
}
@Override
public void queryRemoteConnectionServices(RemoteServiceCallback callback) {
+ final UserHandle callingUserHandle = Binder.getCallingUserHandle();
+ Log.startSession("CSW.qRCS");
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
logIncoming("queryRemoteConnectionServices %s", callback);
- ConnectionServiceWrapper.this.queryRemoteConnectionServices(callback);
+ ConnectionServiceWrapper.this
+ .queryRemoteConnectionServices(callingUserHandle, callback);
}
} finally {
Binder.restoreCallingIdentity(token);
+ Log.endSession();
}
}
@Override
public void setVideoState(String callId, int videoState) {
+ Log.startSession("CSW.sVS");
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
logIncoming("setVideoState %s %d", callId, videoState);
- if (mCallIdMapper.isValidCallId(callId)
- || mCallIdMapper.isValidConferenceId(callId)) {
- Call call = mCallIdMapper.getCall(callId);
- if (call != null) {
- call.setVideoState(videoState);
- }
+ Call call = mCallIdMapper.getCall(callId);
+ if (call != null) {
+ call.setVideoState(videoState);
}
}
} finally {
Binder.restoreCallingIdentity(token);
+ Log.endSession();
}
}
@Override
public void setIsVoipAudioMode(String callId, boolean isVoip) {
+ Log.startSession("CSW.sIVAM");
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
logIncoming("setIsVoipAudioMode %s %b", callId, isVoip);
- if (mCallIdMapper.isValidCallId(callId)) {
- Call call = mCallIdMapper.getCall(callId);
- if (call != null) {
- call.setIsVoipAudioMode(isVoip);
- }
+ Call call = mCallIdMapper.getCall(callId);
+ if (call != null) {
+ call.setIsVoipAudioMode(isVoip);
}
}
} finally {
Binder.restoreCallingIdentity(token);
+ Log.endSession();
}
}
@Override
public void setStatusHints(String callId, StatusHints statusHints) {
+ Log.startSession("CSW.sSH");
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
logIncoming("setStatusHints %s %s", callId, statusHints);
- if (mCallIdMapper.isValidCallId(callId)
- || mCallIdMapper.isValidConferenceId(callId)) {
- Call call = mCallIdMapper.getCall(callId);
- if (call != null) {
- call.setStatusHints(statusHints);
- }
+ Call call = mCallIdMapper.getCall(callId);
+ if (call != null) {
+ call.setStatusHints(statusHints);
}
}
} finally {
Binder.restoreCallingIdentity(token);
+ Log.endSession();
}
}
@Override
public void setExtras(String callId, Bundle extras) {
+ Log.startSession("CSW.sE");
long token = Binder.clearCallingIdentity();
try {
- synchronized(mLock) {
+ synchronized (mLock) {
logIncoming("setExtras %s %s", callId, extras);
- if (mCallIdMapper.isValidCallId(callId)
- || mCallIdMapper.isValidConferenceId(callId)) {
- Call call = mCallIdMapper.getCall(callId);
- if (call != null) {
- call.setExtras(extras);
- }
+ Call call = mCallIdMapper.getCall(callId);
+ if (call != null) {
+ call.setExtras(extras);
}
}
} finally {
Binder.restoreCallingIdentity(token);
+ Log.endSession();
}
}
@Override
public void setAddress(String callId, Uri address, int presentation) {
+ Log.startSession("CSW.sA");
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
logIncoming("setAddress %s %s %d", callId, address, presentation);
- if (mCallIdMapper.isValidCallId(callId)) {
- Call call = mCallIdMapper.getCall(callId);
- if (call != null) {
- call.setHandle(address, presentation);
- }
+ Call call = mCallIdMapper.getCall(callId);
+ if (call != null) {
+ call.setHandle(address, presentation);
}
}
} finally {
Binder.restoreCallingIdentity(token);
+ Log.endSession();
}
}
@Override
public void setCallerDisplayName(
String callId, String callerDisplayName, int presentation) {
+ Log.startSession("CSW.sCDN");
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
logIncoming("setCallerDisplayName %s %s %d", callId, callerDisplayName,
presentation);
- if (mCallIdMapper.isValidCallId(callId)) {
- Call call = mCallIdMapper.getCall(callId);
- if (call != null) {
- call.setCallerDisplayName(callerDisplayName, presentation);
- }
+ Call call = mCallIdMapper.getCall(callId);
+ if (call != null) {
+ call.setCallerDisplayName(callerDisplayName, presentation);
}
}
} finally {
Binder.restoreCallingIdentity(token);
+ Log.endSession();
}
}
@Override
public void setConferenceableConnections(
String callId, List<String> conferenceableCallIds) {
+ Log.startSession("CSW.sCC");
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
logIncoming("setConferenceableConnections %s %s", callId,
conferenceableCallIds);
- if (mCallIdMapper.isValidCallId(callId) ||
- mCallIdMapper.isValidConferenceId(callId)) {
- Call call = mCallIdMapper.getCall(callId);
- if (call != null) {
- List<Call> conferenceableCalls =
- new ArrayList<>(conferenceableCallIds.size());
- for (String otherId : conferenceableCallIds) {
- Call otherCall = mCallIdMapper.getCall(otherId);
- if (otherCall != null && otherCall != call) {
- conferenceableCalls.add(otherCall);
- }
+ Call call = mCallIdMapper.getCall(callId);
+ if (call != null) {
+ List<Call> conferenceableCalls =
+ new ArrayList<>(conferenceableCallIds.size());
+ for (String otherId : conferenceableCallIds) {
+ Call otherCall = mCallIdMapper.getCall(otherId);
+ if (otherCall != null && otherCall != call) {
+ conferenceableCalls.add(otherCall);
}
- call.setConferenceableCalls(conferenceableCalls);
}
+ call.setConferenceableCalls(conferenceableCalls);
}
}
} finally {
Binder.restoreCallingIdentity(token);
+ Log.endSession();
}
}
@Override
public void addExistingConnection(String callId, ParcelableConnection connection) {
+ Log.startSession("CSW.aEC");
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -586,12 +584,13 @@
}
} finally {
Binder.restoreCallingIdentity(token);
+ Log.endSession();
}
}
}
private final Adapter mAdapter = new Adapter();
- private final CallIdMapper mCallIdMapper = new CallIdMapper("ConnectionService");
+ private final CallIdMapper mCallIdMapper = new CallIdMapper();
private final Map<String, CreateConnectionResponse> mPendingResponses = new HashMap<>();
private Binder2 mBinder = new Binder2();
@@ -642,7 +641,8 @@
/**
* Creates a new connection for a new outgoing call or to attach to an existing incoming call.
*/
- void createConnection(final Call call, final CreateConnectionResponse response) {
+ @VisibleForTesting
+ public void createConnection(final Call call, final CreateConnectionResponse response) {
Log.d(this, "createConnection(%s) via %s.", call, getComponentName());
BindCallback callback = new BindCallback() {
@Override
@@ -672,8 +672,9 @@
call.getTargetPhoneAccount(),
call.getHandle(),
extras,
- call.getVideoState()),
- call.isIncoming(),
+ call.getVideoState(),
+ callId),
+ call.shouldAttachToExistingConnection(),
call.isUnknown());
} catch (RemoteException e) {
Log.e(this, e, "Failure to createConnection -- %s", getComponentName());
@@ -745,8 +746,9 @@
}
}
- /** @see IConnectionService#onCallAudioStateChanged(String,CallAudioState) */
- void onCallAudioStateChanged(Call activeCall, CallAudioState audioState) {
+ /** @see IConnectionService#onCallAudioStateChanged(String, CallAudioState) */
+ @VisibleForTesting
+ public void onCallAudioStateChanged(Call activeCall, CallAudioState audioState) {
final String callId = mCallIdMapper.getCallId(activeCall);
if (callId != null && isServiceValid("onCallAudioStateChanged")) {
try {
@@ -803,7 +805,7 @@
}
}
- /** @see IConnectionService#playDtmfTone(String,char) */
+ /** @see IConnectionService#playDtmfTone(String, char) */
void playDtmfTone(Call call, char digit) {
final String callId = mCallIdMapper.getCallId(call);
if (callId != null && isServiceValid("playDtmfTone")) {
@@ -820,7 +822,7 @@
final String callId = mCallIdMapper.getCallId(call);
if (callId != null && isServiceValid("stopDtmfTone")) {
try {
- logOutgoing("stopDtmfTone %s",callId);
+ logOutgoing("stopDtmfTone %s", callId);
mServiceInterface.stopDtmfTone(callId);
} catch (RemoteException e) {
}
@@ -980,10 +982,11 @@
Log.d(this, "Telecom -> ConnectionService: " + msg, params);
}
- private void queryRemoteConnectionServices(final RemoteServiceCallback callback) {
+ private void queryRemoteConnectionServices(final UserHandle userHandle,
+ final RemoteServiceCallback callback) {
// Only give remote connection services to this connection service if it is listed as
// the connection manager.
- PhoneAccountHandle simCallManager = mPhoneAccountRegistrar.getSimCallManager();
+ PhoneAccountHandle simCallManager = mPhoneAccountRegistrar.getSimCallManager(userHandle);
Log.d(this, "queryRemoteConnectionServices finds simCallManager = %s", simCallManager);
if (simCallManager == null ||
!simCallManager.getComponentName().equals(getComponentName())) {
@@ -994,7 +997,7 @@
// Make a list of ConnectionServices that are listed as being associated with SIM accounts
final Set<ConnectionServiceWrapper> simServices = Collections.newSetFromMap(
new ConcurrentHashMap<ConnectionServiceWrapper, Boolean>(8, 0.9f, 1));
- for (PhoneAccountHandle handle : mPhoneAccountRegistrar.getSimPhoneAccounts()) {
+ for (PhoneAccountHandle handle : mPhoneAccountRegistrar.getSimPhoneAccounts(userHandle)) {
ConnectionServiceWrapper service = mConnectionServiceRepository.getService(
handle.getComponentName(), handle.getUserHandle());
if (service != null) {
diff --git a/src/com/android/server/telecom/CreateConnectionProcessor.java b/src/com/android/server/telecom/CreateConnectionProcessor.java
index b846470..d82240e 100644
--- a/src/com/android/server/telecom/CreateConnectionProcessor.java
+++ b/src/com/android/server/telecom/CreateConnectionProcessor.java
@@ -17,23 +17,21 @@
package com.android.server.telecom;
import android.content.Context;
+import android.os.UserHandle;
import android.telecom.DisconnectCause;
import android.telecom.ParcelableConnection;
-import android.telecom.Phone;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
-import android.telephony.TelephonyManager;
-import android.telephony.PhoneStateListener;
-import android.telephony.ServiceState;
// TODO: Needed for move to system service: import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
-import java.util.Set;
import java.util.Objects;
/**
@@ -43,7 +41,8 @@
* to the user
* - a connection service cancels the process, in which case the call is aborted
*/
-final class CreateConnectionProcessor {
+@VisibleForTesting
+public class CreateConnectionProcessor implements CreateConnectionResponse {
// Describes information required to attempt to make a phone call
private static class CallAttemptRecord {
@@ -91,33 +90,35 @@
private final ConnectionServiceRepository mRepository;
private List<CallAttemptRecord> mAttemptRecords;
private Iterator<CallAttemptRecord> mAttemptRecordIterator;
- private CreateConnectionResponse mResponse;
+ private CreateConnectionResponse mCallResponse;
private DisconnectCause mLastErrorDisconnectCause;
private final PhoneAccountRegistrar mPhoneAccountRegistrar;
private final Context mContext;
- private boolean mShouldUseConnectionManager = true;
private CreateConnectionTimeout mTimeout;
+ private ConnectionServiceWrapper mService;
- CreateConnectionProcessor(
+ @VisibleForTesting
+ public CreateConnectionProcessor(
Call call, ConnectionServiceRepository repository, CreateConnectionResponse response,
PhoneAccountRegistrar phoneAccountRegistrar, Context context) {
Log.v(this, "CreateConnectionProcessor created for Call = %s", call);
mCall = call;
mRepository = repository;
- mResponse = response;
+ mCallResponse = response;
mPhoneAccountRegistrar = phoneAccountRegistrar;
mContext = context;
}
boolean isProcessingComplete() {
- return mResponse == null;
+ return mCallResponse == null;
}
boolean isCallTimedOut() {
return mTimeout != null && mTimeout.isCallTimedOut();
}
- void process() {
+ @VisibleForTesting
+ public void process() {
Log.v(this, "process");
clearTimeout();
mAttemptRecords = new ArrayList<>();
@@ -138,7 +139,7 @@
void continueProcessingIfPossible(CreateConnectionResponse response,
DisconnectCause disconnectCause) {
Log.v(this, "continueProcessingIfPossible");
- mResponse = response;
+ mCallResponse = response;
mLastErrorDisconnectCause = disconnectCause;
attemptNextPhoneAccount();
}
@@ -148,8 +149,8 @@
// Clear the response first to prevent attemptNextConnectionService from attempting any
// more services.
- CreateConnectionResponse response = mResponse;
- mResponse = null;
+ CreateConnectionResponse response = mCallResponse;
+ mCallResponse = null;
clearTimeout();
ConnectionServiceWrapper service = mCall.getConnectionService();
@@ -190,33 +191,27 @@
}
}
- if (mResponse != null && attempt != null) {
+ if (mCallResponse != null && attempt != null) {
Log.i(this, "Trying attempt %s", attempt);
PhoneAccountHandle phoneAccount = attempt.connectionManagerPhoneAccount;
- ConnectionServiceWrapper service =
- mRepository.getService(
- phoneAccount.getComponentName(),
- phoneAccount.getUserHandle());
- if (service == null) {
+ mService = mRepository.getService(phoneAccount.getComponentName(),
+ phoneAccount.getUserHandle());
+ if (mService == null) {
Log.i(this, "Found no connection service for attempt %s", attempt);
attemptNextPhoneAccount();
} else {
mCall.setConnectionManagerPhoneAccount(attempt.connectionManagerPhoneAccount);
mCall.setTargetPhoneAccount(attempt.targetPhoneAccount);
- mCall.setConnectionService(service);
- setTimeoutIfNeeded(service, attempt);
+ mCall.setConnectionService(mService);
+ setTimeoutIfNeeded(mService, attempt);
- service.createConnection(mCall, new Response(service));
+ mService.createConnection(mCall, this);
}
} else {
Log.v(this, "attemptNextPhoneAccount, no more accounts, failing");
- if (mResponse != null) {
- clearTimeout();
- mResponse.handleCreateConnectionFailure(mLastErrorDisconnectCause != null ?
- mLastErrorDisconnectCause : new DisconnectCause(DisconnectCause.ERROR));
- mResponse = null;
- mCall.clearConnectionService();
- }
+ DisconnectCause disconnectCause = mLastErrorDisconnectCause != null ?
+ mLastErrorDisconnectCause : new DisconnectCause(DisconnectCause.ERROR);
+ notifyCallConnectionFailure(disconnectCause);
}
}
@@ -240,10 +235,6 @@
}
private boolean shouldSetConnectionManager() {
- if (!mShouldUseConnectionManager) {
- return false;
- }
-
if (mAttemptRecords.size() == 0) {
return false;
}
@@ -254,7 +245,8 @@
return false;
}
- PhoneAccountHandle connectionManager = mPhoneAccountRegistrar.getSimCallManager();
+ PhoneAccountHandle connectionManager =
+ mPhoneAccountRegistrar.getSimCallManagerFromCall(mCall);
if (connectionManager == null) {
return false;
}
@@ -266,8 +258,8 @@
// Connection managers are only allowed to manage SIM subscriptions.
// TODO: Should this really be checking the "calling user" test for phone account?
- PhoneAccount targetPhoneAccount = mPhoneAccountRegistrar.getPhoneAccountCheckCallingUser(
- targetPhoneAccountHandle);
+ PhoneAccount targetPhoneAccount = mPhoneAccountRegistrar
+ .getPhoneAccountUnchecked(targetPhoneAccountHandle);
if (targetPhoneAccount == null) {
Log.d(this, "shouldSetConnectionManager, phone account not found");
return false;
@@ -285,10 +277,10 @@
private void adjustAttemptsForConnectionManager() {
if (shouldSetConnectionManager()) {
CallAttemptRecord record = new CallAttemptRecord(
- mPhoneAccountRegistrar.getSimCallManager(),
+ mPhoneAccountRegistrar.getSimCallManagerFromCall(mCall),
mAttemptRecords.get(0).targetPhoneAccount);
Log.v(this, "setConnectionManager, changing %s -> %s", mAttemptRecords.get(0), record);
- mAttemptRecords.set(0, record);
+ mAttemptRecords.add(0, record);
} else {
Log.v(this, "setConnectionManager, not changing");
}
@@ -296,11 +288,14 @@
// If we are possibly attempting to call a local emergency number, ensure that the
// plain PSTN connection services are listed, and nothing else.
- private void adjustAttemptsForEmergency() {
+ private void adjustAttemptsForEmergency() {
if (mCall.isEmergencyCall()) {
Log.i(this, "Emergency number detected");
mAttemptRecords.clear();
- List<PhoneAccount> allAccounts = mPhoneAccountRegistrar.getAllPhoneAccounts();
+ // Phone accounts in profile do not handle emergency call, use phone accounts in
+ // current user.
+ List<PhoneAccount> allAccounts = mPhoneAccountRegistrar
+ .getAllPhoneAccountsOfCurrentUser();
if (allAccounts.isEmpty()) {
// If the list of phone accounts is empty at this point, it means Telephony hasn't
@@ -312,7 +307,6 @@
allAccounts.add(TelephonyUtil.getDefaultEmergencyPhoneAccount());
}
-
// First, add SIM phone accounts which can place emergency calls.
for (PhoneAccount phoneAccount : allAccounts) {
if (phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS) &&
@@ -327,18 +321,17 @@
}
// Next, add the connection manager account as a backup if it can place emergency calls.
- PhoneAccountHandle callManagerHandle = mPhoneAccountRegistrar.getSimCallManager();
- if (mShouldUseConnectionManager && callManagerHandle != null) {
+ PhoneAccountHandle callManagerHandle =
+ mPhoneAccountRegistrar.getSimCallManagerOfCurrentUser();
+ if (callManagerHandle != null) {
// TODO: Should this really be checking the "calling user" test for phone account?
PhoneAccount callManager = mPhoneAccountRegistrar
- .getPhoneAccountCheckCallingUser(callManagerHandle);
+ .getPhoneAccountUnchecked(callManagerHandle);
if (callManager != null && callManager.hasCapabilities(
PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS)) {
CallAttemptRecord callAttemptRecord = new CallAttemptRecord(callManagerHandle,
- mPhoneAccountRegistrar.
- getOutgoingPhoneAccountForScheme(mCall.getHandle().getScheme())
- );
-
+ mPhoneAccountRegistrar.getOutgoingPhoneAccountForSchemeOfCurrentUser(
+ mCall.getHandle().getScheme()));
if (!mAttemptRecords.contains(callAttemptRecord)) {
Log.i(this, "Will try Connection Manager account %s for emergency",
callManager);
@@ -359,69 +352,77 @@
return result;
}
- private class Response implements CreateConnectionResponse {
- private final ConnectionServiceWrapper mService;
- Response(ConnectionServiceWrapper service) {
- mService = service;
+ private void notifyCallConnectionFailure(DisconnectCause errorDisconnectCause) {
+ if (mCallResponse != null) {
+ clearTimeout();
+ mCallResponse.handleCreateConnectionFailure(errorDisconnectCause);
+ mCallResponse = null;
+ mCall.clearConnectionService();
}
+ }
- @Override
- public void handleCreateConnectionSuccess(
- CallIdMapper idMapper,
- ParcelableConnection connection) {
- if (mResponse == null) {
- // Nobody is listening for this connection attempt any longer; ask the responsible
- // ConnectionService to tear down any resources associated with the call
- mService.abort(mCall);
- } else {
- // Success -- share the good news and remember that we are no longer interested
- // in hearing about any more attempts
- mResponse.handleCreateConnectionSuccess(idMapper, connection);
- mResponse = null;
- // If there's a timeout running then don't clear it. The timeout can be triggered
- // after the call has successfully been created but before it has become active.
- }
+ @Override
+ public void handleCreateConnectionSuccess(
+ CallIdMapper idMapper,
+ ParcelableConnection connection) {
+ if (mCallResponse == null) {
+ // Nobody is listening for this connection attempt any longer; ask the responsible
+ // ConnectionService to tear down any resources associated with the call
+ mService.abort(mCall);
+ } else {
+ // Success -- share the good news and remember that we are no longer interested
+ // in hearing about any more attempts
+ mCallResponse.handleCreateConnectionSuccess(idMapper, connection);
+ mCallResponse = null;
+ // If there's a timeout running then don't clear it. The timeout can be triggered
+ // after the call has successfully been created but before it has become active.
}
+ }
- private boolean shouldFallbackToNoConnectionManager(DisconnectCause cause) {
- PhoneAccountHandle handle = mCall.getConnectionManagerPhoneAccount();
- if (handle == null || !handle.equals(mPhoneAccountRegistrar.getSimCallManager())) {
- return false;
- }
-
- ConnectionServiceWrapper connectionManager = mCall.getConnectionService();
- if (connectionManager == null) {
- return false;
- }
-
- if (cause.getCode() == DisconnectCause.CONNECTION_MANAGER_NOT_SUPPORTED) {
- Log.d(CreateConnectionProcessor.this, "Connection manager declined to handle the "
- + "call, falling back to not using a connection manager");
- return true;
- }
-
- if (!connectionManager.isServiceValid("createConnection")) {
- Log.d(CreateConnectionProcessor.this, "Connection manager unbound while trying "
- + "create a connection, falling back to not using a connection manager");
- return true;
- }
-
+ private boolean shouldFailCallIfConnectionManagerFails(DisconnectCause cause) {
+ // Connection Manager does not exist or does not match registered Connection Manager
+ // Since Connection manager is a proxy for SIM, fall back to SIM
+ PhoneAccountHandle handle = mCall.getConnectionManagerPhoneAccount();
+ if (handle == null || !handle.equals(mPhoneAccountRegistrar.getSimCallManagerFromCall(
+ mCall))) {
return false;
}
- @Override
- public void handleCreateConnectionFailure(DisconnectCause errorDisconnectCause) {
- // Failure of some sort; record the reasons for failure and try again if possible
- Log.d(CreateConnectionProcessor.this, "Connection failed: (%s)", errorDisconnectCause);
- mLastErrorDisconnectCause = errorDisconnectCause;
- if (shouldFallbackToNoConnectionManager(errorDisconnectCause)) {
- mShouldUseConnectionManager = false;
- // Restart from the beginning.
- process();
- } else {
- attemptNextPhoneAccount();
- }
+ // The Call's Connection Service does not exist
+ ConnectionServiceWrapper connectionManager = mCall.getConnectionService();
+ if (connectionManager == null) {
+ return true;
}
+
+ // In this case, fall back to a sim because connection manager declined
+ if (cause.getCode() == DisconnectCause.CONNECTION_MANAGER_NOT_SUPPORTED) {
+ Log.d(CreateConnectionProcessor.this, "Connection manager declined to handle the "
+ + "call, falling back to not using a connection manager");
+ return false;
+ }
+
+ if (!connectionManager.isServiceValid("createConnection")) {
+ Log.d(CreateConnectionProcessor.this, "Connection manager unbound while trying "
+ + "create a connection, falling back to not using a connection manager");
+ return false;
+ }
+
+ // Do not fall back from connection manager and simply fail call if the failure reason is
+ // other
+ Log.d(CreateConnectionProcessor.this, "Connection Manager denied call with the following " +
+ "error: " + cause.getReason() + ". Not falling back to SIM.");
+ return true;
+ }
+
+ @Override
+ public void handleCreateConnectionFailure(DisconnectCause errorDisconnectCause) {
+ // Failure of some sort; record the reasons for failure and try again if possible
+ Log.d(CreateConnectionProcessor.this, "Connection failed: (%s)", errorDisconnectCause);
+ if(shouldFailCallIfConnectionManagerFails(errorDisconnectCause)){
+ notifyCallConnectionFailure(errorDisconnectCause);
+ return;
+ }
+ attemptNextPhoneAccount();
}
}
diff --git a/src/com/android/server/telecom/CreateConnectionResponse.java b/src/com/android/server/telecom/CreateConnectionResponse.java
index 08c0cfc..8e3d0cf 100644
--- a/src/com/android/server/telecom/CreateConnectionResponse.java
+++ b/src/com/android/server/telecom/CreateConnectionResponse.java
@@ -19,10 +19,13 @@
import android.telecom.DisconnectCause;
import android.telecom.ParcelableConnection;
+import com.android.internal.annotations.VisibleForTesting;
+
/**
* A callback for providing the result of creating a connection.
*/
-interface CreateConnectionResponse {
+@VisibleForTesting
+public interface CreateConnectionResponse {
void handleCreateConnectionSuccess(CallIdMapper idMapper, ParcelableConnection connection);
void handleCreateConnectionFailure(DisconnectCause disconnectCaused);
}
diff --git a/src/com/android/server/telecom/CreateConnectionTimeout.java b/src/com/android/server/telecom/CreateConnectionTimeout.java
index 06dc9ed..52a4643 100644
--- a/src/com/android/server/telecom/CreateConnectionTimeout.java
+++ b/src/com/android/server/telecom/CreateConnectionTimeout.java
@@ -19,6 +19,7 @@
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
+import android.os.UserHandle;
import android.telecom.PhoneAccountHandle;
import android.telephony.TelephonyManager;
@@ -54,7 +55,8 @@
// If there's no connection manager to fallback on then there's no point in having a
// timeout.
- PhoneAccountHandle connectionManager = mPhoneAccountRegistrar.getSimCallManager();
+ PhoneAccountHandle connectionManager =
+ mPhoneAccountRegistrar.getSimCallManagerFromCall(mCall);
if (!accounts.contains(connectionManager)) {
return false;
}
diff --git a/src/com/android/server/telecom/DialerCodeReceiver.java b/src/com/android/server/telecom/DialerCodeReceiver.java
new file mode 100644
index 0000000..8732222
--- /dev/null
+++ b/src/com/android/server/telecom/DialerCodeReceiver.java
@@ -0,0 +1,66 @@
+/*
+ * 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.server.telecom;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.telecom.TelecomManager;
+
+/**
+ * Receiver for "secret codes" broadcast by Dialer.
+ */
+public class DialerCodeReceiver extends BroadcastReceiver {
+ // Copied from TelephonyIntents.java.
+ public static final String SECRET_CODE_ACTION = "android.provider.Telephony.SECRET_CODE";
+
+ // Enables extended logging for a period of time.
+ public static final String TELECOM_SECRET_CODE_DEBUG_ON = "823241";
+
+ // Disables extended logging.
+ public static final String TELECOM_SECRET_CODE_DEBUG_OFF = "823240";
+
+ // Writes a MARK to the Telecom log.
+ public static final String TELECOM_SECRET_CODE_MARK = "826275";
+
+ private final CallsManager mCallsManager;
+
+ DialerCodeReceiver(CallsManager callsManager) {
+ mCallsManager = callsManager;
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (SECRET_CODE_ACTION.equals(intent.getAction()) && intent.getData() != null &&
+ intent.getData().getHost() != null) {
+ if (intent.getData().getHost().equals(TELECOM_SECRET_CODE_DEBUG_ON)) {
+ Log.i("DialerCodeReceiver", "Secret code used to enable extended logging mode");
+ Log.setIsExtendedLoggingEnabled(true);
+ } else if (intent.getData().getHost().equals(TELECOM_SECRET_CODE_DEBUG_OFF)) {
+ Log.i("DialerCodeReceiver", "Secret code used to disable extended logging mode");
+ Log.setIsExtendedLoggingEnabled(false);
+ } else if (intent.getData().getHost().equals(TELECOM_SECRET_CODE_MARK)) {
+ Log.i("DialerCodeReceiver", "Secret code used to mark logs.");
+
+ // If there is an active call, add the "log mark" for that call; otherwise we will
+ // add a non-call event.
+ Call currentCall = mCallsManager.getActiveCall();
+ Log.event(currentCall, Log.Events.USER_LOG_MARK);
+ }
+ }
+ }
+}
diff --git a/src/com/android/server/telecom/DockManager.java b/src/com/android/server/telecom/DockManager.java
index e6ad446..1048e2b 100644
--- a/src/com/android/server/telecom/DockManager.java
+++ b/src/com/android/server/telecom/DockManager.java
@@ -21,6 +21,7 @@
import android.content.Intent;
import android.content.IntentFilter;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
import java.util.Collections;
@@ -28,8 +29,10 @@
import java.util.concurrent.ConcurrentHashMap;
/** Listens for and caches car dock state. */
-class DockManager {
- interface Listener {
+@VisibleForTesting
+public class DockManager {
+ @VisibleForTesting
+ public interface Listener {
void onDockChanged(boolean isDocked);
}
@@ -65,7 +68,8 @@
context.registerReceiver(mReceiver, intentFilter);
}
- void addListener(Listener listener) {
+ @VisibleForTesting
+ public void addListener(Listener listener) {
mListeners.add(listener);
}
diff --git a/src/com/android/server/telecom/InCallAdapter.java b/src/com/android/server/telecom/InCallAdapter.java
index 9239288..5ec5889 100644
--- a/src/com/android/server/telecom/InCallAdapter.java
+++ b/src/com/android/server/telecom/InCallAdapter.java
@@ -41,11 +41,12 @@
@Override
public void answerCall(String callId, int videoState) {
- long token = Binder.clearCallingIdentity();
try {
- synchronized (mLock) {
- Log.d(this, "answerCall(%s,%d)", callId, videoState);
- if (mCallIdMapper.isValidCallId(callId)) {
+ Log.startSession("ICA.aC");
+ long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ Log.d(this, "answerCall(%s,%d)", callId, videoState);
Call call = mCallIdMapper.getCall(callId);
if (call != null) {
mCallsManager.answerCall(call, videoState);
@@ -53,19 +54,22 @@
Log.w(this, "answerCall, unknown call id: %s", callId);
}
}
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
} finally {
- Binder.restoreCallingIdentity(token);
+ Log.endSession();
}
}
@Override
public void rejectCall(String callId, boolean rejectWithMessage, String textMessage) {
- long token = Binder.clearCallingIdentity();
try {
- synchronized (mLock) {
- Log.d(this, "rejectCall(%s,%b,%s)", callId, rejectWithMessage, textMessage);
- if (mCallIdMapper.isValidCallId(callId)) {
+ Log.startSession("ICA.aC");
+ long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ Log.d(this, "rejectCall(%s,%b,%s)", callId, rejectWithMessage, textMessage);
Call call = mCallIdMapper.getCall(callId);
if (call != null) {
mCallsManager.rejectCall(call, rejectWithMessage, textMessage);
@@ -73,19 +77,22 @@
Log.w(this, "setRingback, unknown call id: %s", callId);
}
}
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
} finally {
- Binder.restoreCallingIdentity(token);
+ Log.endSession();
}
}
@Override
public void playDtmfTone(String callId, char digit) {
- long token = Binder.clearCallingIdentity();
try {
- synchronized (mLock) {
- Log.d(this, "playDtmfTone(%s,%c)", callId, digit);
- if (mCallIdMapper.isValidCallId(callId)) {
+ Log.startSession("ICA.pDT");
+ long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ Log.d(this, "playDtmfTone(%s,%c)", callId, digit);
Call call = mCallIdMapper.getCall(callId);
if (call != null) {
mCallsManager.playDtmfTone(call, digit);
@@ -93,19 +100,22 @@
Log.w(this, "playDtmfTone, unknown call id: %s", callId);
}
}
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
} finally {
- Binder.restoreCallingIdentity(token);
+ Log.endSession();
}
}
@Override
public void stopDtmfTone(String callId) {
- long token = Binder.clearCallingIdentity();
try {
- synchronized (mLock) {
- Log.d(this, "stopDtmfTone(%s)", callId);
- if (mCallIdMapper.isValidCallId(callId)) {
+ Log.startSession("ICA.sDT");
+ long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ Log.d(this, "stopDtmfTone(%s)", callId);
Call call = mCallIdMapper.getCall(callId);
if (call != null) {
mCallsManager.stopDtmfTone(call);
@@ -113,19 +123,22 @@
Log.w(this, "stopDtmfTone, unknown call id: %s", callId);
}
}
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
} finally {
- Binder.restoreCallingIdentity(token);
+ Log.endSession();
}
}
@Override
public void postDialContinue(String callId, boolean proceed) {
- long token = Binder.clearCallingIdentity();
try {
- synchronized (mLock) {
- Log.d(this, "postDialContinue(%s)", callId);
- if (mCallIdMapper.isValidCallId(callId)) {
+ Log.startSession("ICA.pDC");
+ long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ Log.d(this, "postDialContinue(%s)", callId);
Call call = mCallIdMapper.getCall(callId);
if (call != null) {
mCallsManager.postDialContinue(call, proceed);
@@ -133,19 +146,22 @@
Log.w(this, "postDialContinue, unknown call id: %s", callId);
}
}
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
} finally {
- Binder.restoreCallingIdentity(token);
+ Log.endSession();
}
}
@Override
public void disconnectCall(String callId) {
- long token = Binder.clearCallingIdentity();
try {
- synchronized (mLock) {
- Log.v(this, "disconnectCall: %s", callId);
- if (mCallIdMapper.isValidCallId(callId)) {
+ Log.startSession("ICA.dC");
+ long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ Log.v(this, "disconnectCall: %s", callId);
Call call = mCallIdMapper.getCall(callId);
if (call != null) {
mCallsManager.disconnectCall(call);
@@ -153,18 +169,21 @@
Log.w(this, "disconnectCall, unknown call id: %s", callId);
}
}
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
} finally {
- Binder.restoreCallingIdentity(token);
+ Log.endSession();
}
}
@Override
public void holdCall(String callId) {
- long token = Binder.clearCallingIdentity();
try {
- synchronized (mLock) {
- if (mCallIdMapper.isValidCallId(callId)) {
+ Log.startSession("ICA.hC");
+ long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
Call call = mCallIdMapper.getCall(callId);
if (call != null) {
mCallsManager.holdCall(call);
@@ -172,18 +191,21 @@
Log.w(this, "holdCall, unknown call id: %s", callId);
}
}
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
} finally {
- Binder.restoreCallingIdentity(token);
+ Log.endSession();
}
}
@Override
public void unholdCall(String callId) {
- long token = Binder.clearCallingIdentity();
try {
- synchronized (mLock) {
- if (mCallIdMapper.isValidCallId(callId)) {
+ Log.startSession("ICA.uC");
+ long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
Call call = mCallIdMapper.getCall(callId);
if (call != null) {
mCallsManager.unholdCall(call);
@@ -191,19 +213,22 @@
Log.w(this, "unholdCall, unknown call id: %s", callId);
}
}
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
} finally {
- Binder.restoreCallingIdentity(token);
+ Log.endSession();
}
}
@Override
public void phoneAccountSelected(String callId, PhoneAccountHandle accountHandle,
boolean setDefault) {
- long token = Binder.clearCallingIdentity();
try {
- synchronized (mLock) {
- if (mCallIdMapper.isValidCallId(callId)) {
+ Log.startSession("ICA.pAS");
+ long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
Call call = mCallIdMapper.getCall(callId);
if (call != null) {
mCallsManager.phoneAccountSelected(call, accountHandle, setDefault);
@@ -211,43 +236,55 @@
Log.w(this, "phoneAccountSelected, unknown call id: %s", callId);
}
}
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
} finally {
- Binder.restoreCallingIdentity(token);
+ Log.endSession();
}
}
@Override
public void mute(boolean shouldMute) {
- long token = Binder.clearCallingIdentity();
try {
- synchronized (mLock) {
- mCallsManager.mute(shouldMute);
+ Log.startSession("ICA.m");
+ long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ mCallsManager.mute(shouldMute);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
} finally {
- Binder.restoreCallingIdentity(token);
+ Log.endSession();
}
}
@Override
public void setAudioRoute(int route) {
- long token = Binder.clearCallingIdentity();
try {
- synchronized (mLock) {
- mCallsManager.setAudioRoute(route);
+ Log.startSession("ICA.sAR");
+ long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ mCallsManager.setAudioRoute(route);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
} finally {
- Binder.restoreCallingIdentity(token);
+ Log.endSession();
}
}
@Override
public void conference(String callId, String otherCallId) {
- long token = Binder.clearCallingIdentity();
try {
- synchronized (mLock) {
- if (mCallIdMapper.isValidCallId(callId) &&
- mCallIdMapper.isValidCallId(otherCallId)) {
+ Log.startSession("ICA.c");
+ long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
Call call = mCallIdMapper.getCall(callId);
Call otherCall = mCallIdMapper.getCall(otherCallId);
if (call != null && otherCall != null) {
@@ -255,20 +292,22 @@
} else {
Log.w(this, "conference, unknown call id: %s or %s", callId, otherCallId);
}
-
}
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
} finally {
- Binder.restoreCallingIdentity(token);
+ Log.endSession();
}
}
@Override
public void splitFromConference(String callId) {
- long token = Binder.clearCallingIdentity();
try {
- synchronized (mLock) {
- if (mCallIdMapper.isValidCallId(callId)) {
+ Log.startSession("ICA.sFC");
+ long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
Call call = mCallIdMapper.getCall(callId);
if (call != null) {
call.splitFromConference();
@@ -276,18 +315,21 @@
Log.w(this, "splitFromConference, unknown call id: %s", callId);
}
}
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
} finally {
- Binder.restoreCallingIdentity(token);
+ Log.endSession();
}
}
@Override
public void mergeConference(String callId) {
- long token = Binder.clearCallingIdentity();
try {
- synchronized (mLock) {
- if (mCallIdMapper.isValidCallId(callId)) {
+ Log.startSession("ICA.mC");
+ long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
Call call = mCallIdMapper.getCall(callId);
if (call != null) {
call.mergeConference();
@@ -295,18 +337,21 @@
Log.w(this, "mergeConference, unknown call id: %s", callId);
}
}
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
} finally {
- Binder.restoreCallingIdentity(token);
+ Log.endSession();
}
}
@Override
public void swapConference(String callId) {
- long token = Binder.clearCallingIdentity();
try {
- synchronized (mLock) {
- if (mCallIdMapper.isValidCallId(callId)) {
+ Log.startSession("ICA.sC");
+ long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
Call call = mCallIdMapper.getCall(callId);
if (call != null) {
call.swapConference();
@@ -314,33 +359,45 @@
Log.w(this, "swapConference, unknown call id: %s", callId);
}
}
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
} finally {
- Binder.restoreCallingIdentity(token);
+ Log.endSession();
}
}
@Override
public void turnOnProximitySensor() {
- long token = Binder.clearCallingIdentity();
try {
- synchronized (mLock) {
- mCallsManager.turnOnProximitySensor();
+ Log.startSession("tOPS");
+ long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ mCallsManager.turnOnProximitySensor();
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
} finally {
- Binder.restoreCallingIdentity(token);
+ Log.endSession();
}
}
@Override
public void turnOffProximitySensor(boolean screenOnImmediately) {
- long token = Binder.clearCallingIdentity();
try {
- synchronized (mLock) {
- mCallsManager.turnOffProximitySensor(screenOnImmediately);
+ Log.startSession("ICA.tOPS");
+ long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ mCallsManager.turnOffProximitySensor(screenOnImmediately);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
} finally {
- Binder.restoreCallingIdentity(token);
+ Log.endSession();
}
}
}
diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java
index 9562ea7..1ebf2eb 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -66,14 +66,18 @@
private class InCallServiceConnection implements ServiceConnection {
/** {@inheritDoc} */
@Override public void onServiceConnected(ComponentName name, IBinder service) {
+ Log.startSession("ICSC.oSC");
Log.d(this, "onServiceConnected: %s", name);
onConnected(name, service);
+ Log.endSession();
}
/** {@inheritDoc} */
@Override public void onServiceDisconnected(ComponentName name) {
+ Log.startSession("ICSC.oSD");
Log.d(this, "onDisconnected: %s", name);
onDisconnected(name);
+ Log.endSession();
}
}
@@ -146,7 +150,7 @@
*/
private ComponentName mInCallUIComponentName;
- private final CallIdMapper mCallIdMapper = new CallIdMapper("InCall");
+ private final CallIdMapper mCallIdMapper = new CallIdMapper();
/** The {@link ComponentName} of the default InCall UI. */
private final ComponentName mSystemInCallComponentName;
@@ -198,15 +202,21 @@
/** Let's add a 2 second delay before we send unbind to the services to hopefully
* give them enough time to process all the pending messages.
*/
+ final Session subsession = Log.createSubsession();
Handler handler = new Handler(Looper.getMainLooper());
final Runnable runnableUnbind = new Runnable() {
@Override
public void run() {
- synchronized (mLock) {
- // Check again to make sure there are no active calls.
- if (mCallsManager.getCalls().isEmpty()) {
- unbindFromServices();
+ try {
+ Log.continueSession(subsession, "ICC.oCR");
+ synchronized (mLock) {
+ // Check again to make sure there are no active calls.
+ if (mCallsManager.getCalls().isEmpty()) {
+ unbindFromServices();
+ }
}
+ } finally {
+ Log.endSession();
}
}
};
diff --git a/src/com/android/server/telecom/InCallTonePlayer.java b/src/com/android/server/telecom/InCallTonePlayer.java
index 0d2e3c4..4fa4389 100644
--- a/src/com/android/server/telecom/InCallTonePlayer.java
+++ b/src/com/android/server/telecom/InCallTonePlayer.java
@@ -21,27 +21,34 @@
import android.os.Handler;
import android.os.Looper;
+import com.android.internal.annotations.VisibleForTesting;
+
/**
* Play a call-related tone (ringback, busy signal, etc.) through ToneGenerator. To use, create an
* instance using InCallTonePlayer.Factory (passing in the TONE_* constant for the tone you want)
* and start() it. Implemented on top of {@link Thread} so that the tone plays in its own thread.
*/
-public final class InCallTonePlayer extends Thread {
+public class InCallTonePlayer extends Thread {
/**
* Factory used to create InCallTonePlayers. Exists to aid with testing mocks.
*/
public static class Factory {
private final CallAudioManager mCallAudioManager;
+ private final CallAudioRoutePeripheralAdapter mCallAudioRoutePeripheralAdapter;
private final TelecomSystem.SyncRoot mLock;
- Factory(CallAudioManager callAudioManager, TelecomSystem.SyncRoot lock) {
+ Factory(CallAudioManager callAudioManager,
+ CallAudioRoutePeripheralAdapter callAudioRoutePeripheralAdapter,
+ TelecomSystem.SyncRoot lock) {
mCallAudioManager = callAudioManager;
+ mCallAudioRoutePeripheralAdapter = callAudioRoutePeripheralAdapter;
mLock = lock;
}
- InCallTonePlayer createPlayer(int tone) {
- return new InCallTonePlayer(tone, mCallAudioManager, mLock);
+ public InCallTonePlayer createPlayer(int tone) {
+ return new InCallTonePlayer(tone, mCallAudioManager,
+ mCallAudioRoutePeripheralAdapter, mLock);
}
}
@@ -83,6 +90,7 @@
private static int sTonesPlaying = 0;
private final CallAudioManager mCallAudioManager;
+ private final CallAudioRoutePeripheralAdapter mCallAudioRoutePeripheralAdapter;
private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
@@ -103,10 +111,12 @@
private InCallTonePlayer(
int toneId,
CallAudioManager callAudioManager,
+ CallAudioRoutePeripheralAdapter callAudioRoutePeripheralAdapter,
TelecomSystem.SyncRoot lock) {
mState = STATE_OFF;
mToneId = toneId;
mCallAudioManager = callAudioManager;
+ mCallAudioRoutePeripheralAdapter = callAudioRoutePeripheralAdapter;
mLock = lock;
}
@@ -195,7 +205,7 @@
}
int stream = AudioManager.STREAM_VOICE_CALL;
- if (mCallAudioManager.isBluetoothAudioOn()) {
+ if (mCallAudioRoutePeripheralAdapter.isBluetoothAudioOn()) {
stream = AudioManager.STREAM_BLUETOOTH_SCO;
}
@@ -236,8 +246,9 @@
cleanUpTonePlayer();
}
}
-
- void startTone() {
+
+ @VisibleForTesting
+ public void startTone() {
sTonesPlaying++;
if (sTonesPlaying == 1) {
mCallAudioManager.setIsTonePlaying(true);
@@ -249,7 +260,8 @@
/**
* Stops the tone.
*/
- void stopTone() {
+ @VisibleForTesting
+ public void stopTone() {
synchronized (this) {
if (mState == STATE_ON) {
Log.d(this, "Stopping the tone %d.", mToneId);
diff --git a/src/com/android/server/telecom/InCallWakeLockController.java b/src/com/android/server/telecom/InCallWakeLockController.java
index a6c63c3..8abacd9 100644
--- a/src/com/android/server/telecom/InCallWakeLockController.java
+++ b/src/com/android/server/telecom/InCallWakeLockController.java
@@ -41,8 +41,6 @@
PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
mFullWakeLock = powerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, TAG);
mFullWakeLock.setReferenceCounted(false);
-
- callsManager.addListener(this);
}
@Override
diff --git a/src/com/android/server/telecom/Log.java b/src/com/android/server/telecom/Log.java
index e6e85ba..075f4bb 100644
--- a/src/com/android/server/telecom/Log.java
+++ b/src/com/android/server/telecom/Log.java
@@ -16,25 +16,33 @@
package com.android.server.telecom;
+import android.content.Context;
import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
import android.telecom.PhoneAccount;
import android.telephony.PhoneNumberUtils;
import android.text.TextUtils;
+import android.util.Base64;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
+import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
+import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.IllegalFormatException;
+import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;
/**
@@ -82,6 +90,7 @@
public static final String MUTE = "MUTE";
public static final String AUDIO_ROUTE = "AUDIO_ROUTE";
public static final String ERROR_LOG = "ERROR";
+ public static final String USER_LOG_MARK = "USER_LOG_MARK";
public static final String SILENCE = "SILENCE";
/**
@@ -104,11 +113,13 @@
public static class CallEvent {
public String eventId;
+ public String sessionId;
public long time;
public Object data;
- public CallEvent(String eventId, long time, Object data) {
+ public CallEvent(String eventId, String sessionId, long time, Object data) {
this.eventId = eventId;
+ this.sessionId = sessionId;
this.time = time;
this.data = data;
}
@@ -119,27 +130,25 @@
private static int sNextId = 1;
private final List<CallEvent> mEvents = new LinkedList<>();
private final Call mCall;
- private final int mId;
public CallEventRecord(Call call) {
mCall = call;
- mId = ++sNextId;
}
public Call getCall() {
return mCall;
}
- public void addEvent(String event, Object data) {
- mEvents.add(new CallEvent(event, System.currentTimeMillis(), data));
- Log.i("Event", "Call %d: %s, %s", mId, event, data);
+ public void addEvent(String event, String sessionId, Object data) {
+ mEvents.add(new CallEvent(event, sessionId, System.currentTimeMillis(), data));
+ Log.i("Event", "Call %s: %s, %s", mCall.getId(), event, data);
}
public void dump(IndentingPrintWriter pw) {
Map<String, CallEvent> pendingResponses = new HashMap<>();
pw.print("Call ");
- pw.print(mId);
+ pw.print(mCall.getId());
pw.print(" [");
pw.print(sDateFormat.format(new Date(mCall.getCreationTimeMillis())));
pw.print("]");
@@ -165,6 +174,8 @@
pw.print(sDateFormat.format(new Date(event.time)));
pw.print(" - ");
+ pw.print(event.sessionId);
+ pw.print(":");
pw.print(event.eventId);
if (event.data != null) {
pw.print(" (");
@@ -175,7 +186,7 @@
// ID instead.
CallEventRecord record = mCallEventRecordMap.get(data);
if (record != null) {
- data = "Call " + record.mId;
+ data = "Call " + record.mCall.getId();
}
}
@@ -200,10 +211,18 @@
}
public static final int MAX_CALLS_TO_CACHE = 5; // Arbitrarily chosen.
+ public static final int MAX_CALLS_TO_CACHE_DEBUG = 20; // Arbitrarily chosen.
+ private static final long EXTENDED_LOGGING_DURATION_MILLIS = 60000 * 30; // 30 minutes
+
+ private static final boolean LOG_DBG = false;
+
+ // Currently using 3 letters, So don't exceed 64^3
+ private static final long SESSION_ID_ROLLOVER_THRESHOLD = 262144;
// Generic tag for all In Call logging
@VisibleForTesting
public static String TAG = "Telecom";
+ public static String LOGGING_TAG = "Logging";
public static final boolean FORCE_LOGGING = false; /* STOP SHIP if true */
public static final boolean SYSTRACE_DEBUG = false; /* STOP SHIP if true */
@@ -214,43 +233,355 @@
public static final boolean ERROR = isLoggable(android.util.Log.ERROR);
private static final Map<Call, CallEventRecord> mCallEventRecordMap = new HashMap<>();
- private static final LinkedBlockingQueue<CallEventRecord> mCallEventRecords =
+ private static LinkedBlockingQueue<CallEventRecord> mCallEventRecords =
new LinkedBlockingQueue<CallEventRecord>(MAX_CALLS_TO_CACHE);
- private Log() {}
+ private static Context mContext = null;
+ // Synchronized in all method calls
+ private static int sCodeEntryCounter = 0;
+ @VisibleForTesting
+ public static ConcurrentHashMap<Integer, Session> sSessionMapper = new ConcurrentHashMap<>(100);
+ @VisibleForTesting
+ public static Handler sSessionCleanupHandler = new Handler(Looper.getMainLooper());
+ @VisibleForTesting
+ public static Runnable sCleanStaleSessions = new Runnable() {
+ @Override
+ public void run() {
+ cleanupStaleSessions(getSessionCleanupTimeoutMs());
+ }
+ };
+
+ // Set the logging container to be the system's. This will only change when being mocked
+ // during testing.
+ private static SystemLoggingContainer systemLogger = new SystemLoggingContainer();
+
+ /**
+ * Tracks whether user-activated extended logging is enabled.
+ */
+ private static boolean mIsUserExtendedLoggingEnabled = false;
+
+ /**
+ * The time when user-activated extended logging should be ended. Used to determine when
+ * extended logging should automatically be disabled.
+ */
+ private static long mUserExtendedLoggingStopTime = 0;
+
+ private Log() {
+ }
+
+ public static void setContext(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Enable or disable extended telecom logging.
+ *
+ * @param isExtendedLoggingEnabled {@code true} if extended logging should be enabled,
+ * {@code false} if it should be disabled.
+ */
+ public static void setIsExtendedLoggingEnabled(boolean isExtendedLoggingEnabled) {
+ // If the state hasn't changed, bail early.
+ if (mIsUserExtendedLoggingEnabled == isExtendedLoggingEnabled) {
+ return;
+ }
+
+ // Resize the event queue.
+ int newSize = isExtendedLoggingEnabled ? MAX_CALLS_TO_CACHE_DEBUG : MAX_CALLS_TO_CACHE;
+ LinkedBlockingQueue<CallEventRecord> oldEventLog = mCallEventRecords;
+ mCallEventRecords = new LinkedBlockingQueue<CallEventRecord>(newSize);
+ mCallEventRecordMap.clear();
+
+ // Copy the existing queue into the new one.
+ for (CallEventRecord event : oldEventLog) {
+ addCallEventRecord(event);
+ }
+
+ mIsUserExtendedLoggingEnabled = isExtendedLoggingEnabled;
+ if (mIsUserExtendedLoggingEnabled) {
+ mUserExtendedLoggingStopTime = System.currentTimeMillis()
+ + EXTENDED_LOGGING_DURATION_MILLIS;
+ } else {
+ mUserExtendedLoggingStopTime = 0;
+ }
+ }
@VisibleForTesting
public static void setTag(String tag) {
TAG = tag;
}
+ @VisibleForTesting
+ public static void setLoggingContainer(SystemLoggingContainer logger) {
+ systemLogger = logger;
+ }
+
+ // Overridden in LogTest to skip query to ContentProvider
+ public interface ISessionCleanupTimeoutMs {
+ long get();
+ }
+
+ @VisibleForTesting
+ public static ISessionCleanupTimeoutMs sSessionCleanupTimeoutMs =
+ new ISessionCleanupTimeoutMs() {
+ @Override
+ public long get() {
+ return Timeouts.getStaleSessionCleanupTimeoutMillis(
+ mContext.getContentResolver());
+ }
+ };
+
+ private static long getSessionCleanupTimeoutMs() {
+ return sSessionCleanupTimeoutMs.get();
+ }
+
+ private static synchronized void resetStaleSessionTimer() {
+ sSessionCleanupHandler.removeCallbacksAndMessages(null);
+ sSessionCleanupHandler.postDelayed(sCleanStaleSessions, getSessionCleanupTimeoutMs());
+ }
+
+ /**
+ * Call at an entry point to the Telecom code to track the session. This code must be
+ * accompanied by a Log.endSession().
+ */
+ public static synchronized void startSession(String shortMethodName) {
+ resetStaleSessionTimer();
+ int threadId = getCallingThreadId();
+ Session activeSession = sSessionMapper.get(threadId);
+ // We have called startSession within an active session that has not ended... Register this
+ // session as a subsession.
+ if (activeSession != null) {
+ Log.d(LOGGING_TAG, "Log.startSession was called with session active. Creating " +
+ "a subsession...");
+ Session childSession = createSubsession();
+ continueSession(childSession, shortMethodName);
+ return;
+ }
+ Session newSession = new Session(getNextSessionID(), shortMethodName,
+ System.currentTimeMillis(), threadId);
+ sSessionMapper.put(threadId, newSession);
+
+ Log.i(LOGGING_TAG, Session.START_SESSION + " " + newSession.toString());
+ }
+
+
+ /**
+ * Notifies the logging system that a subsession will be run at a later point and
+ * allocates the resources. Returns a session object that must be used in
+ * Log.continueSession(...) to start the subsession.
+ */
+ public static synchronized Session createSubsession() {
+ int threadId = getCallingThreadId();
+ Session threadSession = sSessionMapper.get(threadId);
+ if (threadSession == null) {
+ Log.d(LOGGING_TAG, "Log.createSubsession was called with no session active.");
+ return null;
+ }
+ // Start execution time of the session will be overwritten in continueSession(...).
+ Session newSubsession = new Session(threadSession.getNextChildId(),
+ threadSession.getShortMethodName(), System.currentTimeMillis(), threadId);
+ threadSession.addChild(newSubsession);
+ newSubsession.setParentSession(threadSession);
+
+ Log.i(LOGGING_TAG, Session.CREATE_SUBSESSION + " " + newSubsession.toString());
+ return newSubsession;
+ }
+
+ /**
+ * Cancels a subsession that had Log.createSubsession() called on it, but will never have
+ * Log.continueSession(...) called on it due to an error. Allows the subsession to be cleaned
+ * gracefully instead of being removed by the sSessionCleanupHandler forcefully later.
+ */
+ public static synchronized void cancelSubsession(Session subsession) {
+ if (subsession == null) {
+ return;
+ }
+
+ subsession.markSessionCompleted(0);
+ endParentSessions(subsession);
+ }
+
+ /**
+ * Starts the subsession that was created in Log.CreateSubsession. The Log.endSession() method
+ * must be called at the end of this method. The full session will complete when all
+ * subsessions are completed.
+ */
+ public static synchronized void continueSession(Session subsession, String shortMethodName) {
+ if (subsession == null) {
+ return;
+ }
+ resetStaleSessionTimer();
+ String callingMethodName = subsession.getShortMethodName();
+ subsession.setShortMethodName(callingMethodName + "->" + shortMethodName);
+ subsession.setExecutionStartTimeMs(System.currentTimeMillis());
+ Session threadSession = subsession.getParentSession();
+ if (threadSession == null) {
+ Log.d(LOGGING_TAG, "Log.continueSession was called with no session active for " +
+ "method %s.", shortMethodName);
+ return;
+ }
+
+ sSessionMapper.put(getCallingThreadId(), subsession);
+ Log.i(LOGGING_TAG, Session.CONTINUE_SUBSESSION + " " + subsession.toString());
+ }
+
+ public static void checkIsThreadLogged() {
+ int threadId = getCallingThreadId();
+ Session threadSession = sSessionMapper.get(threadId);
+ if (threadSession == null) {
+ android.util.Log.e(LOGGING_TAG, "Logging Thread Check Failed!", new Exception());
+ }
+ }
+
+ /**
+ * Ends the current session/subsession. Must be called after a Log.startSession(...) and
+ * Log.continueSession(...) call.
+ */
+ public static synchronized void endSession() {
+ int threadId = getCallingThreadId();
+ Session completedSession = sSessionMapper.get(threadId);
+ if (completedSession == null) {
+ Log.w(LOGGING_TAG, "Log.endSession was called with no session active.");
+ return;
+ }
+
+ completedSession.markSessionCompleted(System.currentTimeMillis());
+ Log.i(LOGGING_TAG, Session.END_SUBSESSION + " dur: " +
+ completedSession.getLocalExecutionTime() + " mS");
+ // Remove after completed so that reference still exists for logging the end events
+ Session parentSession = completedSession.getParentSession();
+ sSessionMapper.remove(threadId);
+ endParentSessions(completedSession);
+ // If this subsession was started from a parent session using Log.startSession, return the
+ // ThreadID back to the parent after completion.
+ if (parentSession != null && !parentSession.isSessionCompleted() &&
+ completedSession.getThreadId() == parentSession.getThreadId()) {
+ sSessionMapper.put(threadId, parentSession);
+ }
+ }
+
+ // Recursively deletes all complete parent sessions of the current subsession if it is a leaf.
+ private static void endParentSessions(Session subsession) {
+ // Session is not completed or not currently a leaf, so we can not remove because a child is
+ // still running
+ if (!subsession.isSessionCompleted() || subsession.getChildSessions().size() != 0) {
+ return;
+ }
+
+ Session parentSession = subsession.getParentSession();
+ if (parentSession != null) {
+ subsession.setParentSession(null);
+ parentSession.removeChild(subsession);
+ endParentSessions(parentSession);
+ } else {
+ // All of the subsessions have been completed and it is time to report on the full
+ // running time of the session.
+ long fullSessionTimeMs =
+ System.currentTimeMillis() - subsession.getExecutionStartTimeMilliseconds();
+ Log.i(LOGGING_TAG, Session.END_SESSION + " " + subsession.toString() + " dur: " +
+ fullSessionTimeMs + " ms");
+ }
+ }
+
+ private synchronized static String getNextSessionID() {
+ Integer nextId = sCodeEntryCounter++;
+ if (nextId >= SESSION_ID_ROLLOVER_THRESHOLD) {
+ restartSessionCounter();
+ nextId = sCodeEntryCounter++;
+ }
+ return getBase64Encoding(nextId);
+ }
+
+ @VisibleForTesting
+ public synchronized static void restartSessionCounter() {
+ sCodeEntryCounter = 0;
+ }
+
+ @VisibleForTesting
+ public static String getBase64Encoding(int number) {
+ byte[] idByteArray = ByteBuffer.allocate(4).putInt(number).array();
+ idByteArray = Arrays.copyOfRange(idByteArray, 2, 4);
+ return Base64.encodeToString(idByteArray, Base64.NO_WRAP | Base64.NO_PADDING);
+ }
+
+ public static int getCallingThreadId() {
+ return android.os.Process.myTid();
+ }
+
public static void event(Call call, String event) {
event(call, event, null);
}
public static void event(Call call, String event, Object data) {
+ Session currentSession = sSessionMapper.get(getCallingThreadId());
+ String currentSessionID = currentSession != null ? currentSession.toString() : "";
+
if (call == null) {
Log.i(TAG, "Non-call EVENT: %s, %s", event, data);
return;
}
synchronized (mCallEventRecords) {
if (!mCallEventRecordMap.containsKey(call)) {
- // First remove the oldest entry if no new ones exist.
- if (mCallEventRecords.remainingCapacity() == 0) {
- CallEventRecord record = mCallEventRecords.poll();
- if (record != null) {
- mCallEventRecordMap.remove(record.getCall());
- }
- }
-
- // Now add a new entry
CallEventRecord newRecord = new CallEventRecord(call);
- mCallEventRecords.add(newRecord);
- mCallEventRecordMap.put(call, newRecord);
+ addCallEventRecord(newRecord);
}
CallEventRecord record = mCallEventRecordMap.get(call);
- record.addEvent(event, data);
+ record.addEvent(event, currentSessionID, data);
+ }
+ }
+
+ @VisibleForTesting
+ public static void cleanupStaleSessions(long timeoutMs) {
+ String logMessage = "Stale Sessions Cleaned:\n";
+ boolean isSessionsStale = false;
+ long currentTimeMs = System.currentTimeMillis();
+ // Remove references that are in the Session Mapper (causing GC to occur) on
+ // sessions that are lasting longer than LOGGING_SESSION_TIMEOUT_MS.
+ // If this occurs, then there is most likely a Session active that never had
+ // Log.endSession called on it.
+ for (Iterator<ConcurrentHashMap.Entry<Integer, Session>> it =
+ sSessionMapper.entrySet().iterator(); it.hasNext(); ) {
+ ConcurrentHashMap.Entry<Integer, Session> entry = it.next();
+ Session session = entry.getValue();
+ if (currentTimeMs - session.getExecutionStartTimeMilliseconds() > timeoutMs) {
+ it.remove();
+ logMessage += session.printFullSessionTree() + "\n";
+ isSessionsStale = true;
+ }
+ }
+ if (isSessionsStale) {
+ Log.w(LOGGING_TAG, logMessage);
+ }
+ }
+
+ private static void addCallEventRecord(CallEventRecord newRecord) {
+ Call call = newRecord.getCall();
+
+ // First remove the oldest entry if no new ones exist.
+ if (mCallEventRecords.remainingCapacity() == 0) {
+ CallEventRecord record = mCallEventRecords.poll();
+ if (record != null) {
+ mCallEventRecordMap.remove(record.getCall());
+ }
+ }
+
+ // Now add a new entry
+ mCallEventRecords.add(newRecord);
+ mCallEventRecordMap.put(call, newRecord);
+ }
+
+ /**
+ * If user enabled extended logging is enabled and the time limit has passed, disables the
+ * extended logging.
+ */
+ private static void maybeDisableLogging() {
+ if (!mIsUserExtendedLoggingEnabled) {
+ return;
+ }
+
+ if (mUserExtendedLoggingStopTime < System.currentTimeMillis()) {
+ mUserExtendedLoggingStopTime = 0;
+ mIsUserExtendedLoggingEnabled = false;
}
}
@@ -259,83 +590,95 @@
}
public static void d(String prefix, String format, Object... args) {
- if (DEBUG) {
- android.util.Slog.d(TAG, buildMessage(prefix, format, args));
+ if (mIsUserExtendedLoggingEnabled) {
+ maybeDisableLogging();
+ systemLogger.i(TAG, buildMessage(prefix, format, args));
+ } else if (DEBUG) {
+ systemLogger.d(TAG, buildMessage(prefix, format, args));
}
}
public static void d(Object objectPrefix, String format, Object... args) {
- if (DEBUG) {
- android.util.Slog.d(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args));
+ if (mIsUserExtendedLoggingEnabled) {
+ maybeDisableLogging();
+ systemLogger.i(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args));
+ } else if (DEBUG) {
+ systemLogger.d(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args));
}
}
public static void i(String prefix, String format, Object... args) {
if (INFO) {
- android.util.Slog.i(TAG, buildMessage(prefix, format, args));
+ systemLogger.i(TAG, buildMessage(prefix, format, args));
}
}
public static void i(Object objectPrefix, String format, Object... args) {
if (INFO) {
- android.util.Slog.i(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args));
+ systemLogger.i(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args));
}
}
public static void v(String prefix, String format, Object... args) {
- if (VERBOSE) {
- android.util.Slog.v(TAG, buildMessage(prefix, format, args));
+ if (mIsUserExtendedLoggingEnabled) {
+ maybeDisableLogging();
+ systemLogger.i(TAG, buildMessage(prefix, format, args));
+ } else if (VERBOSE) {
+ systemLogger.v(TAG, buildMessage(prefix, format, args));
}
}
public static void v(Object objectPrefix, String format, Object... args) {
- if (VERBOSE) {
- android.util.Slog.v(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args));
+ if (mIsUserExtendedLoggingEnabled) {
+ maybeDisableLogging();
+ systemLogger.i(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args));
+ } else if (VERBOSE) {
+ systemLogger.v(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args));
}
}
public static void w(String prefix, String format, Object... args) {
if (WARN) {
- android.util.Slog.w(TAG, buildMessage(prefix, format, args));
+ systemLogger.w(TAG, buildMessage(prefix, format, args));
}
}
public static void w(Object objectPrefix, String format, Object... args) {
if (WARN) {
- android.util.Slog.w(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args));
+ systemLogger.w(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args));
}
}
public static void e(String prefix, Throwable tr, String format, Object... args) {
if (ERROR) {
- android.util.Slog.e(TAG, buildMessage(prefix, format, args), tr);
+ systemLogger.e(TAG, buildMessage(prefix, format, args), tr);
}
}
public static void e(Object objectPrefix, Throwable tr, String format, Object... args) {
if (ERROR) {
- android.util.Slog.e(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args),
+ systemLogger.e(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args),
tr);
}
}
public static void wtf(String prefix, Throwable tr, String format, Object... args) {
- android.util.Slog.wtf(TAG, buildMessage(prefix, format, args), tr);
+ systemLogger.wtf(TAG, buildMessage(prefix, format, args), tr);
}
public static void wtf(Object objectPrefix, Throwable tr, String format, Object... args) {
- android.util.Slog.wtf(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args),
+ systemLogger.wtf(TAG, buildMessage(getPrefixFromObject(objectPrefix), format, args),
tr);
}
public static void wtf(String prefix, String format, Object... args) {
String msg = buildMessage(prefix, format, args);
- android.util.Slog.wtf(TAG, msg, new IllegalStateException(msg));
+ systemLogger.wtf(TAG, msg, new IllegalStateException(msg));
}
public static void wtf(Object objectPrefix, String format, Object... args) {
String msg = buildMessage(getPrefixFromObject(objectPrefix), format, args);
- android.util.Slog.wtf(TAG, msg, new IllegalStateException(msg));
+ systemLogger.wtf(TAG, msg, new IllegalStateException(msg));
}
public static String piiHandle(Object pii) {
@@ -426,6 +769,15 @@
}
private static String buildMessage(String prefix, String format, Object... args) {
+ if (LOG_DBG) {
+ checkIsThreadLogged();
+ }
+ // Incorporate thread ID and calling method into prefix
+ Session currentSession = sSessionMapper.get(getCallingThreadId());
+ if (currentSession != null) {
+ prefix = prefix + " " + currentSession.toString();
+ }
+
String msg;
try {
msg = (args == null || args.length == 0) ? format
diff --git a/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java b/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
index 17ccdb1..d9c6c33 100644
--- a/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
+++ b/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
@@ -96,53 +96,60 @@
@Override
public void onReceive(Context context, Intent intent) {
- Trace.beginSection("onReceiveNewOutgoingCallBroadcast");
- Log.v(this, "onReceive: %s", intent);
+ try {
+ Log.startSession("NOCBIR.oR");
+ Trace.beginSection("onReceiveNewOutgoingCallBroadcast");
+ Log.v(this, "onReceive: %s", intent);
- // Once the NEW_OUTGOING_CALL broadcast is finished, the resultData is used as the
- // actual number to call. (If null, no call will be placed.)
- String resultNumber = getResultData();
- Log.i(this, "Received new-outgoing-call-broadcast for %s with data %s", mCall,
- Log.pii(resultNumber));
+ // Once the NEW_OUTGOING_CALL broadcast is finished, the resultData is used as the
+ // actual number to call. (If null, no call will be placed.)
+ String resultNumber = getResultData();
+ Log.i(this, "Received new-outgoing-call-broadcast for %s with data %s", mCall,
+ Log.pii(resultNumber));
- boolean endEarly = false;
- if (resultNumber == null) {
- Log.v(this, "Call cancelled (null number), returning...");
- endEarly = true;
- } else if (PhoneNumberUtils.isPotentialLocalEmergencyNumber(mContext, resultNumber)) {
- Log.w(this, "Cannot modify outgoing call to emergency number %s.", resultNumber);
- endEarly = true;
- }
-
- if (endEarly) {
- if (mCall != null) {
- mCall.disconnect(true /* wasViaNewOutgoingCall */);
+ boolean endEarly = false;
+ if (resultNumber == null) {
+ Log.v(this, "Call cancelled (null number), returning...");
+ endEarly = true;
+ } else if (PhoneNumberUtils.isPotentialLocalEmergencyNumber(
+ mContext, resultNumber)) {
+ Log.w(this, "Cannot modify outgoing call to emergency number %s.",
+ resultNumber);
+ endEarly = true;
}
+
+ if (endEarly) {
+ if (mCall != null) {
+ mCall.disconnect(true /* wasViaNewOutgoingCall */);
+ }
+ return;
+ }
+
+ Uri resultHandleUri = Uri.fromParts(PhoneNumberUtils.isUriNumber(resultNumber) ?
+ PhoneAccount.SCHEME_SIP : PhoneAccount.SCHEME_TEL, resultNumber, null);
+
+ Uri originalUri = mIntent.getData();
+
+ if (originalUri.getSchemeSpecificPart().equals(resultNumber)) {
+ Log.v(this, "Call number unmodified after new outgoing call intent broadcast.");
+ } else {
+ Log.v(this, "Retrieved modified handle after outgoing call intent broadcast: "
+ + "Original: %s, Modified: %s",
+ Log.pii(originalUri),
+ Log.pii(resultHandleUri));
+ }
+
+ GatewayInfo gatewayInfo = getGateWayInfoFromIntent(intent, resultHandleUri);
+ mCallsManager.placeOutgoingCall(mCall, resultHandleUri, gatewayInfo,
+ mIntent.getBooleanExtra(TelecomManager.EXTRA_START_CALL_WITH_SPEAKERPHONE,
+ false),
+ mIntent.getIntExtra(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
+ VideoProfile.STATE_AUDIO_ONLY));
+
+ } finally {
Trace.endSection();
- return;
+ Log.endSession();
}
-
- Uri resultHandleUri = Uri.fromParts(PhoneNumberUtils.isUriNumber(resultNumber) ?
- PhoneAccount.SCHEME_SIP : PhoneAccount.SCHEME_TEL, resultNumber, null);
-
- Uri originalUri = mIntent.getData();
-
- if (originalUri.getSchemeSpecificPart().equals(resultNumber)) {
- Log.v(this, "Call number unmodified after new outgoing call intent broadcast.");
- } else {
- Log.v(this, "Retrieved modified handle after outgoing call intent broadcast: "
- + "Original: %s, Modified: %s",
- Log.pii(originalUri),
- Log.pii(resultHandleUri));
- }
-
- GatewayInfo gatewayInfo = getGateWayInfoFromIntent(intent, resultHandleUri);
- mCallsManager.placeOutgoingCall(mCall, resultHandleUri, gatewayInfo,
- mIntent.getBooleanExtra(TelecomManager.EXTRA_START_CALL_WITH_SPEAKERPHONE,
- false),
- mIntent.getIntExtra(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
- VideoProfile.STATE_AUDIO_ONLY));
- Trace.endSection();
}
}
diff --git a/src/com/android/server/telecom/PhoneAccountRegistrar.java b/src/com/android/server/telecom/PhoneAccountRegistrar.java
index aab193e..709c044 100644
--- a/src/com/android/server/telecom/PhoneAccountRegistrar.java
+++ b/src/com/android/server/telecom/PhoneAccountRegistrar.java
@@ -16,7 +16,6 @@
package com.android.server.telecom;
-import android.app.ActivityManager;
import android.Manifest;
import android.content.ComponentName;
import android.content.Context;
@@ -29,7 +28,7 @@
import android.graphics.BitmapFactory;
import android.graphics.drawable.Icon;
import android.net.Uri;
-import android.os.Binder;
+import android.os.Bundle;
import android.os.PersistableBundle;
import android.os.Process;
import android.os.UserHandle;
@@ -74,7 +73,9 @@
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
/**
@@ -95,20 +96,16 @@
* 3) The user running the app that is requesting the phone account information.
*
* For example, I have a device with 2 users, primary (A) and secondary (B), and the secondary user
- * has a work profile running as another user (B2). Lets say that user B opens the phone settings
- * (not currently supported, but theoretically speaking), and phone settings queries for a phone
- * account list. Lets also say that an app running in the work profile has registered a phone
- * account. This means that:
- *
- * Since phone settings always runs as the primary user, We have the following situation:
- * User A (settings) is requesting a list of phone accounts while the active user is User B, and
- * that list contains a phone account for profile User B2.
+ * has a work profile running as another user (B2). Each user/profile only have the visibility of
+ * phone accounts owned by them. Lets say, user B (settings) is requesting a list of phone accounts,
+ * and the list only contains phone accounts owned by user B and accounts with
+ * {@link PhoneAccount#CAPABILITY_MULTI_USER}.
*
* In practice, (2) is stored with the phone account handle and is part of the handle's ID. (1) is
* saved in {@link #mCurrentUserHandle} and (3) we get from Binder.getCallingUser(). We check these
* users for visibility before returning any phone accounts.
*/
-public final class PhoneAccountRegistrar {
+public class PhoneAccountRegistrar {
public static final PhoneAccountHandle NO_ACCOUNT_SELECTED =
new PhoneAccountHandle(new ComponentName("null", "null"), "NO_ACCOUNT_SELECTED");
@@ -121,7 +118,7 @@
private static final String FILE_NAME = "phone-account-registrar-state.xml";
@VisibleForTesting
- public static final int EXPECTED_STATE_VERSION = 8;
+ public static final int EXPECTED_STATE_VERSION = 9;
/** Keep in sync with the same in SipSettings.java */
private static final String SIP_SHARED_PREFERENCES = "SIP_PREFERENCES";
@@ -168,7 +165,7 @@
* @return The value of the subscription id or -1 if it does not exist or is not valid.
*/
public int getSubscriptionIdForPhoneAccount(PhoneAccountHandle accountHandle) {
- PhoneAccount account = getPhoneAccountCheckCallingUser(accountHandle);
+ PhoneAccount account = getPhoneAccountUnchecked(accountHandle);
if (account != null && account.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) {
TelephonyManager tm =
@@ -186,19 +183,21 @@
* @param uriScheme The URI scheme for the outgoing call.
* @return The {@link PhoneAccountHandle} to use.
*/
- public PhoneAccountHandle getOutgoingPhoneAccountForScheme(String uriScheme) {
- final PhoneAccountHandle userSelected = getUserSelectedOutgoingPhoneAccount();
+ public PhoneAccountHandle getOutgoingPhoneAccountForScheme(String uriScheme,
+ UserHandle userHandle) {
+ final PhoneAccountHandle userSelected = getUserSelectedOutgoingPhoneAccount(userHandle);
if (userSelected != null) {
// If there is a default PhoneAccount, ensure it supports calls to handles with the
// specified uriScheme.
- final PhoneAccount userSelectedAccount = getPhoneAccountCheckCallingUser(userSelected);
+ final PhoneAccount userSelectedAccount = getPhoneAccountUnchecked(userSelected);
if (userSelectedAccount.supportsUriScheme(uriScheme)) {
return userSelected;
}
}
- List<PhoneAccountHandle> outgoing = getCallCapablePhoneAccounts(uriScheme, false);
+ List<PhoneAccountHandle> outgoing = getCallCapablePhoneAccounts(uriScheme, false,
+ userHandle);
switch (outgoing.size()) {
case 0:
// There are no accounts, so there can be no default
@@ -212,14 +211,28 @@
}
}
+ public PhoneAccountHandle getOutgoingPhoneAccountForSchemeOfCurrentUser(String uriScheme) {
+ return getOutgoingPhoneAccountForScheme(uriScheme, mCurrentUserHandle);
+ }
+
/**
* @return The user-selected outgoing {@link PhoneAccount}, or null if it hasn't been set (or
* if it was set by another user).
*/
- PhoneAccountHandle getUserSelectedOutgoingPhoneAccount() {
- PhoneAccount account = getPhoneAccountCheckCallingUser(mState.defaultOutgoing);
+ PhoneAccountHandle getUserSelectedOutgoingPhoneAccount(UserHandle userHandle) {
+ if (userHandle == null) {
+ return null;
+ }
+ DefaultPhoneAccountHandle defaultPhoneAccountHandle = mState.defaultOutgoingAccountHandles
+ .get(userHandle);
+ if (defaultPhoneAccountHandle == null) {
+ return null;
+ }
+ // Make sure the account is still registered and owned by the user.
+ PhoneAccount account = getPhoneAccount(defaultPhoneAccountHandle.phoneAccountHandle,
+ userHandle);
if (account != null) {
- return mState.defaultOutgoing;
+ return defaultPhoneAccountHandle.phoneAccountHandle;
}
return null;
}
@@ -228,13 +241,16 @@
* Sets the phone account with which to place all calls by default. Set by the user
* within phone settings.
*/
- public void setUserSelectedOutgoingPhoneAccount(PhoneAccountHandle accountHandle) {
+ public void setUserSelectedOutgoingPhoneAccount(PhoneAccountHandle accountHandle,
+ UserHandle userHandle) {
+ if (userHandle == null) {
+ return;
+ }
if (accountHandle == null) {
// Asking to clear the default outgoing is a valid request
- mState.defaultOutgoing = null;
+ mState.defaultOutgoingAccountHandles.remove(userHandle);
} else {
- // TODO: Do we really want to return for *any* user?
- PhoneAccount account = getPhoneAccount(accountHandle);
+ PhoneAccount account = getPhoneAccount(accountHandle, userHandle);
if (account == null) {
Log.w(this, "Trying to set nonexistent default outgoing %s",
accountHandle);
@@ -254,7 +270,8 @@
mSubscriptionManager.setDefaultVoiceSubId(subId);
}
- mState.defaultOutgoing = accountHandle;
+ mState.defaultOutgoingAccountHandles
+ .put(userHandle, new DefaultPhoneAccountHandle(userHandle, accountHandle));
}
write();
@@ -262,28 +279,8 @@
}
boolean isUserSelectedSmsPhoneAccount(PhoneAccountHandle accountHandle) {
- return getSubscriptionIdForPhoneAccount(accountHandle) ==
- SubscriptionManager.getDefaultSmsSubId();
- }
-
- /**
- * Returns the {@link PhoneAccountHandle} corresponding to the currently active SIM Call
- * Manager. SIM Call Manager returned corresponds to the following priority order:
- * 1. If a SIM Call Manager {@link PhoneAccount} is registered for the same package as the
- * default dialer, then that one is returned.
- * 2. If there is a SIM Call Manager {@link PhoneAccount} registered which matches the
- * carrier configuration's default, then that one is returned.
- * 3. Otherwise, we return null.
- */
- public PhoneAccountHandle getSimCallManager() {
- long token = Binder.clearCallingIdentity();
- int user;
- try {
- user = ActivityManager.getCurrentUser();
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- return getSimCallManager(user);
+ return getSubscriptionIdForPhoneAccount(accountHandle) == SubscriptionManager
+ .getDefaultSmsSubId();
}
public ComponentName getSystemSimCallManagerComponent() {
@@ -299,6 +296,10 @@
? null : ComponentName.unflattenFromString(defaultSimCallManager);
}
+ public PhoneAccountHandle getSimCallManagerOfCurrentUser() {
+ return getSimCallManager(mCurrentUserHandle);
+ }
+
/**
* Returns the {@link PhoneAccountHandle} corresponding to the currently active SIM Call
* Manager. SIM Call Manager returned corresponds to the following priority order:
@@ -308,9 +309,10 @@
* carrier configuration's default, then that one is returned.
* 3. Otherwise, we return null.
*/
- public PhoneAccountHandle getSimCallManager(int user) {
+ public PhoneAccountHandle getSimCallManager(UserHandle userHandle) {
// Get the default dialer in case it has a connection manager associated with it.
- String dialerPackage = DefaultDialerManager.getDefaultDialerApplication(mContext, user);
+ String dialerPackage = DefaultDialerManager
+ .getDefaultDialerApplication(mContext, userHandle.getIdentifier());
// Check carrier config.
ComponentName systemSimCallManagerComponent = getSystemSimCallManagerComponent();
@@ -322,7 +324,7 @@
// loop through and look for any connection manager in the same package.
List<PhoneAccountHandle> allSimCallManagers = getPhoneAccountHandles(
PhoneAccount.CAPABILITY_CONNECTION_MANAGER, null, null,
- true /* includeDisabledAccounts */);
+ true /* includeDisabledAccounts */, userHandle);
for (PhoneAccountHandle accountHandle : allSimCallManagers) {
ComponentName component = accountHandle.getComponentName();
@@ -350,6 +352,23 @@
}
/**
+ * If it is a outgoing call, sim call manager of call-initiating user is returned.
+ * Otherwise, we return the sim call manager of the user associated with the
+ * target phone account.
+ * @return phone account handle of sim call manager based on the ongoing call.
+ */
+ public PhoneAccountHandle getSimCallManagerFromCall(Call call) {
+ if (call == null) {
+ return null;
+ }
+ UserHandle userHandle = call.getInitiatingUser();
+ if (userHandle == null) {
+ userHandle = call.getTargetPhoneAccount().getUserHandle();
+ }
+ return getSimCallManager(userHandle);
+ }
+
+ /**
* Update the current UserHandle to track when users are switched. This will allow the
* PhoneAccountRegistar to self-filter the PhoneAccounts to make sure we don't leak anything
* across users.
@@ -373,7 +392,7 @@
* otherwise.
*/
public boolean enablePhoneAccount(PhoneAccountHandle accountHandle, boolean isEnabled) {
- PhoneAccount account = getPhoneAccount(accountHandle);
+ PhoneAccount account = getPhoneAccountUnchecked(accountHandle);
if (account == null) {
Log.w(this, "Could not find account to enable: " + accountHandle);
return false;
@@ -391,11 +410,17 @@
return true;
}
- private boolean isVisibleForUser(PhoneAccount account) {
+ private boolean isVisibleForUser(PhoneAccount account, UserHandle userHandle,
+ boolean acrossProfiles) {
if (account == null) {
return false;
}
+ if (userHandle == null) {
+ Log.w(this, "userHandle is null in isVisibleForUser");
+ return false;
+ }
+
// If this PhoneAccount has CAPABILITY_MULTI_USER, it should be visible to all users and
// all profiles. Only Telephony and SIP accounts should have this capability.
if (account.hasCapabilities(PhoneAccount.CAPABILITY_MULTI_USER)) {
@@ -408,34 +433,22 @@
}
if (mCurrentUserHandle == null) {
+ // In case we need to have emergency phone calls from the lock screen.
Log.d(this, "Current user is null; assuming true");
return true;
}
- if (phoneAccountUserHandle.equals(Binder.getCallingUserHandle())) {
- return true;
+ if (acrossProfiles) {
+ return UserManager.get(mContext).isSameProfileGroup(userHandle.getIdentifier(),
+ phoneAccountUserHandle.getIdentifier());
+ } else {
+ return phoneAccountUserHandle.equals(userHandle);
}
-
- // Special check for work profiles.
- // Unlike in TelecomServiceImpl, we only care about *profiles* here. We want to make sure
- // that we don't resolve PhoneAccount across *users*, but resolving across *profiles* is
- // fine.
- if (UserHandle.getCallingUserId() == UserHandle.USER_OWNER) {
- List<UserInfo> profileUsers =
- mUserManager.getProfiles(mCurrentUserHandle.getIdentifier());
- for (UserInfo profileInfo : profileUsers) {
- if (profileInfo.getUserHandle().equals(phoneAccountUserHandle)) {
- return true;
- }
- }
- }
-
- return false;
}
private List<ResolveInfo> resolveComponent(PhoneAccountHandle phoneAccountHandle) {
return resolveComponent(phoneAccountHandle.getComponentName(),
- phoneAccountHandle.getUserHandle());
+ phoneAccountHandle.getUserHandle());
}
private List<ResolveInfo> resolveComponent(ComponentName componentName,
@@ -461,12 +474,16 @@
*
* @return The list of {@link PhoneAccountHandle}s.
*/
- public List<PhoneAccountHandle> getAllPhoneAccountHandles() {
- return getPhoneAccountHandles(0, null, null, false);
+ public List<PhoneAccountHandle> getAllPhoneAccountHandles(UserHandle userHandle) {
+ return getPhoneAccountHandles(0, null, null, false, userHandle);
}
- public List<PhoneAccount> getAllPhoneAccounts() {
- return getPhoneAccounts(0, null, null, false);
+ public List<PhoneAccount> getAllPhoneAccounts(UserHandle userHandle) {
+ return getPhoneAccounts(0, null, null, false, userHandle);
+ }
+
+ public List<PhoneAccount> getAllPhoneAccountsOfCurrentUser() {
+ return getAllPhoneAccounts(mCurrentUserHandle);
}
/**
@@ -477,30 +494,40 @@
* @return The phone account handles.
*/
public List<PhoneAccountHandle> getCallCapablePhoneAccounts(
- String uriScheme, boolean includeDisabledAccounts) {
+ String uriScheme, boolean includeDisabledAccounts, UserHandle userHandle) {
return getPhoneAccountHandles(
PhoneAccount.CAPABILITY_CALL_PROVIDER,
PhoneAccount.CAPABILITY_EMERGENCY_CALLS_ONLY /*excludedCapabilities*/,
- uriScheme, null, includeDisabledAccounts);
+ uriScheme, null, includeDisabledAccounts, userHandle);
+ }
+
+ public List<PhoneAccountHandle> getCallCapablePhoneAccountsOfCurrentUser(
+ String uriScheme, boolean includeDisabledAccounts) {
+ return getCallCapablePhoneAccounts(uriScheme, includeDisabledAccounts, mCurrentUserHandle);
}
/**
* Retrieves a list of all the SIM-based phone accounts.
*/
- public List<PhoneAccountHandle> getSimPhoneAccounts() {
+ public List<PhoneAccountHandle> getSimPhoneAccounts(UserHandle userHandle) {
return getPhoneAccountHandles(
PhoneAccount.CAPABILITY_CALL_PROVIDER | PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION,
- null, null, false);
+ null, null, false, userHandle);
}
- /**
- * Retrieves a list of all phone accounts registered by a specified package.
- *
- * @param packageName The name of the package that registered the phone accounts.
- * @return The phone account handles.
- */
- public List<PhoneAccountHandle> getPhoneAccountsForPackage(String packageName) {
- return getPhoneAccountHandles(0, null, packageName, false);
+ public List<PhoneAccountHandle> getSimPhoneAccountsOfCurrentUser() {
+ return getSimPhoneAccounts(mCurrentUserHandle);
+ }
+
+ /**
+ * Retrieves a list of all phone accounts registered by a specified package.
+ *
+ * @param packageName The name of the package that registered the phone accounts.
+ * @return The phone account handles.
+ */
+ public List<PhoneAccountHandle> getPhoneAccountsForPackage(String packageName,
+ UserHandle userHandle) {
+ return getPhoneAccountHandles(0, null, packageName, false, userHandle);
}
// TODO: Should we implement an artificial limit for # of accounts associated with a single
@@ -533,7 +560,7 @@
// source app provides or else an third party app could enable itself.
boolean isEnabled = false;
- PhoneAccount oldAccount = getPhoneAccount(account.getAccountHandle());
+ PhoneAccount oldAccount = getPhoneAccountUnchecked(account.getAccountHandle());
if (oldAccount != null) {
mState.accounts.remove(oldAccount);
isEnabled = oldAccount.isEnabled();
@@ -553,7 +580,7 @@
}
public void unregisterPhoneAccount(PhoneAccountHandle accountHandle) {
- PhoneAccount account = getPhoneAccount(accountHandle);
+ PhoneAccount account = getPhoneAccountUnchecked(accountHandle);
if (account != null) {
if (mState.accounts.remove(account)) {
write();
@@ -696,7 +723,7 @@
* @param handle
* @return The corresponding phone account if one exists.
*/
- PhoneAccount getPhoneAccount(PhoneAccountHandle handle) {
+ public PhoneAccount getPhoneAccountUnchecked(PhoneAccountHandle handle) {
for (PhoneAccount m : mState.accounts) {
if (Objects.equals(handle, m.getAccountHandle())) {
return m;
@@ -710,21 +737,31 @@
* account before returning it. The current user is the active user on the actual android
* device.
*/
- public PhoneAccount getPhoneAccountCheckCallingUser(PhoneAccountHandle handle) {
- PhoneAccount account = getPhoneAccount(handle);
- if (account != null && isVisibleForUser(account)) {
+ public PhoneAccount getPhoneAccount(PhoneAccountHandle handle, UserHandle userHandle) {
+ return getPhoneAccount(handle, userHandle, /* acrossProfiles */ false);
+ }
+
+ public PhoneAccount getPhoneAccount(PhoneAccountHandle handle,
+ UserHandle userHandle, boolean acrossProfiles) {
+ PhoneAccount account = getPhoneAccountUnchecked(handle);
+ if (account != null && (isVisibleForUser(account, userHandle, acrossProfiles))) {
return account;
}
return null;
}
+ public PhoneAccount getPhoneAccountOfCurrentUser(PhoneAccountHandle handle) {
+ return getPhoneAccount(handle, mCurrentUserHandle);
+ }
+
private List<PhoneAccountHandle> getPhoneAccountHandles(
int capabilities,
String uriScheme,
String packageName,
- boolean includeDisabledAccounts) {
+ boolean includeDisabledAccounts,
+ UserHandle userHandle) {
return getPhoneAccountHandles(capabilities, 0 /*excludedCapabilities*/, uriScheme,
- packageName, includeDisabledAccounts);
+ packageName, includeDisabledAccounts, userHandle);
}
/**
@@ -736,12 +773,13 @@
int excludedCapabilities,
String uriScheme,
String packageName,
- boolean includeDisabledAccounts) {
+ boolean includeDisabledAccounts,
+ UserHandle userHandle) {
List<PhoneAccountHandle> handles = new ArrayList<>();
for (PhoneAccount account : getPhoneAccounts(
capabilities, excludedCapabilities, uriScheme, packageName,
- includeDisabledAccounts)) {
+ includeDisabledAccounts, userHandle)) {
handles.add(account.getAccountHandle());
}
return handles;
@@ -751,9 +789,10 @@
int capabilities,
String uriScheme,
String packageName,
- boolean includeDisabledAccounts) {
+ boolean includeDisabledAccounts,
+ UserHandle userHandle) {
return getPhoneAccounts(capabilities, 0 /*excludedCapabilities*/, uriScheme, packageName,
- includeDisabledAccounts);
+ includeDisabledAccounts, userHandle);
}
/**
@@ -772,7 +811,8 @@
int excludedCapabilities,
String uriScheme,
String packageName,
- boolean includeDisabledAccounts) {
+ boolean includeDisabledAccounts,
+ UserHandle userHandle) {
List<PhoneAccount> accounts = new ArrayList<>(mState.accounts.size());
for (PhoneAccount m : mState.accounts) {
if (!(m.isEnabled() || includeDisabledAccounts)) {
@@ -804,7 +844,7 @@
// Not the right package name; skip this one.
continue;
}
- if (!isVisibleForUser(m)) {
+ if (!isVisibleForUser(m, userHandle, false)) {
// Account is not visible for the current user; skip this one.
continue;
}
@@ -823,10 +863,11 @@
@VisibleForTesting
public static class State {
/**
- * The account selected by the user to be employed by default for making outgoing calls.
- * If the user has not made such a selection, then this is null.
+ * Store the default phone account handle of users. If no record of a user can be found in
+ * the map, it means that no default phone account handle is set in that user.
*/
- public PhoneAccountHandle defaultOutgoing = null;
+ public final Map<UserHandle, DefaultPhoneAccountHandle> defaultOutgoingAccountHandles
+ = new ConcurrentHashMap<>();
/**
* The complete list of {@code PhoneAccount}s known to the Telecom subsystem.
@@ -840,6 +881,22 @@
}
/**
+ * The default {@link PhoneAccountHandle} of a user.
+ */
+ public static class DefaultPhoneAccountHandle {
+
+ public final UserHandle userHandle;
+
+ public final PhoneAccountHandle phoneAccountHandle;
+
+ public DefaultPhoneAccountHandle(UserHandle userHandle,
+ PhoneAccountHandle phoneAccountHandle) {
+ this.userHandle = userHandle;
+ this.phoneAccountHandle = phoneAccountHandle;
+ }
+ }
+
+ /**
* Dumps the state of the {@link CallsManager}.
*
* @param pw The {@code IndentingPrintWriter} to write the state to.
@@ -847,9 +904,11 @@
public void dump(IndentingPrintWriter pw) {
if (mState != null) {
pw.println("xmlVersion: " + mState.versionNumber);
- pw.println("defaultOutgoing: " + (mState.defaultOutgoing == null ? "none" :
- mState.defaultOutgoing));
- pw.println("simCallManager: " + getSimCallManager());
+ DefaultPhoneAccountHandle defaultPhoneAccountHandle
+ = mState.defaultOutgoingAccountHandles.get(Process.myUserHandle());
+ pw.println("defaultOutgoing: " + (defaultPhoneAccountHandle == null ? "none" :
+ defaultPhoneAccountHandle.phoneAccountHandle));
+ pw.println("simCallManager: " + getSimCallManager(mCurrentUserHandle));
pw.println("phoneAccounts:");
pw.increaseIndent();
for (PhoneAccount phoneAccount : mState.accounts) {
@@ -954,8 +1013,13 @@
@VisibleForTesting
public abstract static class XmlSerialization<T> {
- private static final String LENGTH_ATTRIBUTE = "length";
- private static final String VALUE_TAG = "value";
+ private static final String TAG_VALUE = "value";
+ private static final String ATTRIBUTE_LENGTH = "length";
+ private static final String ATTRIBUTE_KEY = "key";
+ private static final String ATTRIBUTE_VALUE_TYPE = "type";
+ private static final String VALUE_TYPE_STRING = "string";
+ private static final String VALUE_TYPE_INTEGER = "integer";
+ private static final String VALUE_TYPE_BOOLEAN = "boolean";
/**
* Write the supplied object to XML
@@ -996,16 +1060,51 @@
serializer.startTag(null, tagName);
if (values != null) {
- serializer.attribute(null, LENGTH_ATTRIBUTE, Objects.toString(values.size()));
+ serializer.attribute(null, ATTRIBUTE_LENGTH, Objects.toString(values.size()));
for (String toSerialize : values) {
- serializer.startTag(null, VALUE_TAG);
+ serializer.startTag(null, TAG_VALUE);
if (toSerialize != null ){
serializer.text(toSerialize);
}
- serializer.endTag(null, VALUE_TAG);
+ serializer.endTag(null, TAG_VALUE);
}
} else {
- serializer.attribute(null, LENGTH_ATTRIBUTE, "0");
+ serializer.attribute(null, ATTRIBUTE_LENGTH, "0");
+ }
+ serializer.endTag(null, tagName);
+ }
+
+ protected void writeBundle(String tagName, Bundle values, XmlSerializer serializer)
+ throws IOException {
+
+ serializer.startTag(null, tagName);
+ if (values != null) {
+ for (String key : values.keySet()) {
+ Object value = values.get(key);
+
+ if (value == null) {
+ continue;
+ }
+
+ String valueType;
+ if (value instanceof String) {
+ valueType = VALUE_TYPE_STRING;
+ } else if (value instanceof Integer) {
+ valueType = VALUE_TYPE_INTEGER;
+ } else if (value instanceof Boolean) {
+ valueType = VALUE_TYPE_BOOLEAN;
+ } else {
+ Log.w(this,
+ "PhoneAccounts support only string, integer and boolean extras TY.");
+ continue;
+ }
+
+ serializer.startTag(null, TAG_VALUE);
+ serializer.attribute(null, ATTRIBUTE_KEY, key);
+ serializer.attribute(null, ATTRIBUTE_VALUE_TYPE, valueType);
+ serializer.text(Objects.toString(value));
+ serializer.endTag(null, TAG_VALUE);
+ }
}
serializer.endTag(null, tagName);
}
@@ -1042,7 +1141,7 @@
protected List<String> readStringList(XmlPullParser parser)
throws IOException, XmlPullParserException {
- int length = Integer.parseInt(parser.getAttributeValue(null, LENGTH_ATTRIBUTE));
+ int length = Integer.parseInt(parser.getAttributeValue(null, ATTRIBUTE_LENGTH));
List<String> arrayEntries = new ArrayList<String>(length);
String value = null;
@@ -1052,7 +1151,7 @@
int outerDepth = parser.getDepth();
while (XmlUtils.nextElementWithin(parser, outerDepth)) {
- if (parser.getName().equals(VALUE_TAG)) {
+ if (parser.getName().equals(TAG_VALUE)) {
parser.next();
value = parser.getText();
arrayEntries.add(value);
@@ -1062,6 +1161,55 @@
return arrayEntries;
}
+ /**
+ * Reads a bundle from the XML parser.
+ *
+ * @param parser The XML parser.
+ * @return Bundle containing the parsed values.
+ * @throws IOException Exception related to IO.
+ * @throws XmlPullParserException Exception related to parsing.
+ */
+ protected Bundle readBundle(XmlPullParser parser)
+ throws IOException, XmlPullParserException {
+
+ Bundle bundle = null;
+ int outerDepth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+ if (parser.getName().equals(TAG_VALUE)) {
+ String valueType = parser.getAttributeValue(null, ATTRIBUTE_VALUE_TYPE);
+ String key = parser.getAttributeValue(null, ATTRIBUTE_KEY);
+ parser.next();
+ String value = parser.getText();
+
+ if (bundle == null) {
+ bundle = new Bundle();
+ }
+
+ // Do not write null values to the bundle.
+ if (value == null) {
+ continue;
+ }
+
+ if (VALUE_TYPE_STRING.equals(valueType)) {
+ bundle.putString(key, value);
+ } else if (VALUE_TYPE_INTEGER.equals(valueType)) {
+ try {
+ int intValue = Integer.parseInt(value);
+ bundle.putInt(key, intValue);
+ } catch (NumberFormatException nfe) {
+ Log.w(this, "Invalid integer PhoneAccount extra.");
+ }
+ } else if (VALUE_TYPE_BOOLEAN.equals(valueType)) {
+ boolean boolValue = Boolean.parseBoolean(value);
+ bundle.putBoolean(key, boolValue);
+ } else {
+ Log.w(this, "Invalid type " + valueType + " for PhoneAccount bundle.");
+ }
+ }
+ }
+ return bundle;
+ }
+
protected Bitmap readBitmap(XmlPullParser parser) {
byte[] imageByteArray = Base64.decode(parser.getText(), 0);
return BitmapFactory.decodeByteArray(imageByteArray, 0, imageByteArray.length);
@@ -1089,11 +1237,13 @@
serializer.startTag(null, CLASS_STATE);
serializer.attribute(null, VERSION, Objects.toString(EXPECTED_STATE_VERSION));
- if (o.defaultOutgoing != null) {
- serializer.startTag(null, DEFAULT_OUTGOING);
- sPhoneAccountHandleXml.writeToXml(o.defaultOutgoing, serializer, context);
- serializer.endTag(null, DEFAULT_OUTGOING);
+ serializer.startTag(null, DEFAULT_OUTGOING);
+ for (DefaultPhoneAccountHandle defaultPhoneAccountHandle : o
+ .defaultOutgoingAccountHandles.values()) {
+ sDefaultPhoneAcountHandleXml
+ .writeToXml(defaultPhoneAccountHandle, serializer, context);
}
+ serializer.endTag(null, DEFAULT_OUTGOING);
serializer.startTag(null, ACCOUNTS);
for (PhoneAccount m : o.accounts) {
@@ -1112,15 +1262,39 @@
State s = new State();
String rawVersion = parser.getAttributeValue(null, VERSION);
- s.versionNumber = TextUtils.isEmpty(rawVersion) ? 1 :
- Integer.parseInt(rawVersion);
+ s.versionNumber = TextUtils.isEmpty(rawVersion) ? 1 : Integer.parseInt(rawVersion);
int outerDepth = parser.getDepth();
while (XmlUtils.nextElementWithin(parser, outerDepth)) {
if (parser.getName().equals(DEFAULT_OUTGOING)) {
- parser.nextTag();
- s.defaultOutgoing = sPhoneAccountHandleXml.readFromXml(parser,
- s.versionNumber, context);
+ if (s.versionNumber < 9) {
+ // Migration old default phone account handle here by assuming the
+ // default phone account handle is belong to primary user.
+ parser.nextTag();
+ PhoneAccountHandle phoneAccountHandle = sPhoneAccountHandleXml
+ .readFromXml(parser, s.versionNumber, context);
+ UserManager userManager = UserManager.get(context);
+ UserInfo primaryUser = userManager.getPrimaryUser();
+ if (primaryUser != null) {
+ UserHandle userHandle = primaryUser.getUserHandle();
+ DefaultPhoneAccountHandle defaultPhoneAccountHandle
+ = new DefaultPhoneAccountHandle(userHandle,
+ phoneAccountHandle);
+ s.defaultOutgoingAccountHandles
+ .put(userHandle, defaultPhoneAccountHandle);
+ }
+ } else {
+ int defaultAccountHandlesDepth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, defaultAccountHandlesDepth)) {
+ DefaultPhoneAccountHandle accountHandle
+ = sDefaultPhoneAcountHandleXml
+ .readFromXml(parser, s.versionNumber, context);
+ if (accountHandle != null && s.accounts != null) {
+ s.defaultOutgoingAccountHandles
+ .put(accountHandle.userHandle, accountHandle);
+ }
+ }
+ }
} else if (parser.getName().equals(ACCOUNTS)) {
int accountsDepth = parser.getDepth();
while (XmlUtils.nextElementWithin(parser, accountsDepth)) {
@@ -1140,6 +1314,70 @@
};
@VisibleForTesting
+ public static final XmlSerialization<DefaultPhoneAccountHandle> sDefaultPhoneAcountHandleXml =
+ new XmlSerialization<DefaultPhoneAccountHandle>() {
+ private static final String CLASS_DEFAULT_OUTGOING_PHONE_ACCOUNT_HANDLE
+ = "default_outgoing_phone_account_handle";
+ private static final String USER_SERIAL_NUMBER = "user_serial_number";
+ private static final String ACCOUNT_HANDLE = "account_handle";
+
+ @Override
+ public void writeToXml(DefaultPhoneAccountHandle o, XmlSerializer serializer,
+ Context context) throws IOException {
+ if (o != null) {
+ final UserManager userManager = UserManager.get(context);
+ final long serialNumber = userManager.getSerialNumberForUser(o.userHandle);
+ if (serialNumber != -1) {
+ serializer.startTag(null, CLASS_DEFAULT_OUTGOING_PHONE_ACCOUNT_HANDLE);
+ writeLong(USER_SERIAL_NUMBER, serialNumber, serializer);
+ serializer.startTag(null, ACCOUNT_HANDLE);
+ sPhoneAccountHandleXml.writeToXml(o.phoneAccountHandle, serializer,
+ context);
+ serializer.endTag(null, ACCOUNT_HANDLE);
+ serializer.endTag(null, CLASS_DEFAULT_OUTGOING_PHONE_ACCOUNT_HANDLE);
+ }
+ }
+ }
+
+ @Override
+ public DefaultPhoneAccountHandle readFromXml(XmlPullParser parser, int version,
+ Context context)
+ throws IOException, XmlPullParserException {
+ if (parser.getName().equals(CLASS_DEFAULT_OUTGOING_PHONE_ACCOUNT_HANDLE)) {
+ int outerDepth = parser.getDepth();
+ PhoneAccountHandle accountHandle = null;
+ String userSerialNumberString = null;
+ while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+ if (parser.getName().equals(ACCOUNT_HANDLE)) {
+ parser.nextTag();
+ accountHandle = sPhoneAccountHandleXml.readFromXml(parser, version,
+ context);
+ } else if (parser.getName().equals(USER_SERIAL_NUMBER)) {
+ parser.next();
+ userSerialNumberString = parser.getText();
+ }
+ }
+ UserHandle userHandle = null;
+ if (userSerialNumberString != null) {
+ try {
+ long serialNumber = Long.parseLong(userSerialNumberString);
+ userHandle = UserManager.get(context)
+ .getUserForSerialNumber(serialNumber);
+ } catch (NumberFormatException e) {
+ Log.e(this, e,
+ "Could not parse UserHandle " + userSerialNumberString);
+ }
+ }
+ if (accountHandle != null && userHandle != null) {
+ return new DefaultPhoneAccountHandle(userHandle, accountHandle);
+ }
+ }
+ return null;
+ }
+ };
+
+
+ @VisibleForTesting
public static final XmlSerialization<PhoneAccount> sPhoneAccountXml =
new XmlSerialization<PhoneAccount>() {
private static final String CLASS_PHONE_ACCOUNT = "phone_account";
@@ -1156,6 +1394,7 @@
private static final String SHORT_DESCRIPTION = "short_description";
private static final String SUPPORTED_URI_SCHEMES = "supported_uri_schemes";
private static final String ICON = "icon";
+ private static final String EXTRAS = "extras";
private static final String ENABLED = "enabled";
@Override
@@ -1179,6 +1418,7 @@
writeTextIfNonNull(LABEL, o.getLabel(), serializer);
writeTextIfNonNull(SHORT_DESCRIPTION, o.getShortDescription(), serializer);
writeStringList(SUPPORTED_URI_SCHEMES, o.getSupportedUriSchemes(), serializer);
+ writeBundle(EXTRAS, o.getExtras(), serializer);
writeTextIfNonNull(ENABLED, o.isEnabled() ? "true" : "false" , serializer);
serializer.endTag(null, CLASS_PHONE_ACCOUNT);
@@ -1203,6 +1443,7 @@
List<String> supportedUriSchemes = null;
Icon icon = null;
boolean enabled = false;
+ Bundle extras = null;
while (XmlUtils.nextElementWithin(parser, outerDepth)) {
if (parser.getName().equals(ACCOUNT_HANDLE)) {
@@ -1248,6 +1489,9 @@
} else if (parser.getName().equals(ENABLED)) {
parser.next();
enabled = "true".equalsIgnoreCase(parser.getText());
+ } else if (parser.getName().equals(EXTRAS)) {
+ parser.next();
+ extras = readBundle(parser);
}
}
@@ -1313,6 +1557,7 @@
.setShortDescription(shortDescription)
.setSupportedUriSchemes(supportedUriSchemes)
.setHighlightColor(highlightColor)
+ .setExtras(extras)
.setIsEnabled(enabled);
if (icon != null) {
diff --git a/src/com/android/server/telecom/Ringer.java b/src/com/android/server/telecom/Ringer.java
index b57090d..da22e98 100644
--- a/src/com/android/server/telecom/Ringer.java
+++ b/src/com/android/server/telecom/Ringer.java
@@ -23,9 +23,9 @@
import android.media.AudioManager;
import android.net.Uri;
import android.os.Bundle;
-import android.os.SystemVibrator;
import android.os.Vibrator;
-import android.provider.Settings;
+
+import com.android.internal.annotations.VisibleForTesting;
import java.util.LinkedList;
import java.util.List;
@@ -34,7 +34,8 @@
* Controls the ringtone player.
* TODO: Turn this into a proper state machine: Ringing, CallWaiting, Stopped.
*/
-final class Ringer extends CallsManagerListenerBase {
+@VisibleForTesting
+public final class Ringer extends CallsManagerListenerBase {
private static final long[] VIBRATION_PATTERN = new long[] {
0, // No delay before starting
1000, // How long to vibrate
@@ -53,22 +54,23 @@
/** Indicate that we want the pattern to repeat at the step which turns on vibration. */
private static final int VIBRATION_PATTERN_REPEAT = 1;
- private final AsyncRingtonePlayer mRingtonePlayer;
-
/**
* Used to keep ordering of unanswered incoming calls. There can easily exist multiple incoming
* calls and explicit ordering is useful for maintaining the proper state of the ringer.
*/
private final List<Call> mRingingCalls = new LinkedList<>();
+ private final SystemSettingsUtil mSystemSettingsUtil;
private final CallAudioManager mCallAudioManager;
private final CallsManager mCallsManager;
private final InCallTonePlayer.Factory mPlayerFactory;
+ private final AsyncRingtonePlayer mRingtonePlayer;
private final Context mContext;
private final Vibrator mVibrator;
private int mState = STATE_STOPPED;
private InCallTonePlayer mCallWaitingPlayer;
+ private RingtoneFactory mRingtoneFactory;
/**
* Used to track the status of {@link #mVibrator} in the case of simultaneous incoming calls.
@@ -76,20 +78,27 @@
private boolean mIsVibrating = false;
/** Initializes the Ringer. */
- Ringer(
+ @VisibleForTesting
+ public Ringer(
CallAudioManager callAudioManager,
CallsManager callsManager,
InCallTonePlayer.Factory playerFactory,
- Context context) {
+ Context context,
+ SystemSettingsUtil systemSettingsUtil,
+ AsyncRingtonePlayer asyncRingtonePlayer,
+ RingtoneFactory ringtoneFactory,
+ Vibrator vibrator) {
+ mSystemSettingsUtil = systemSettingsUtil;
mCallAudioManager = callAudioManager;
mCallsManager = callsManager;
mPlayerFactory = playerFactory;
mContext = context;
// We don't rely on getSystemService(Context.VIBRATOR_SERVICE) to make sure this
// vibrator object will be isolated from others.
- mVibrator = new SystemVibrator(context);
- mRingtonePlayer = new AsyncRingtonePlayer(context);
+ mVibrator = vibrator;
+ mRingtonePlayer = asyncRingtonePlayer;
+ mRingtoneFactory = ringtoneFactory;
}
@Override
@@ -185,8 +194,7 @@
Call foregroundCall = mCallsManager.getForegroundCall();
Log.v(this, "startRingingOrCallWaiting, foregroundCall: %s.", foregroundCall);
- if (Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.THEATER_MODE_ON,
- 0) == 1) {
+ if (mSystemSettingsUtil.isTheaterModeOn(mContext)) {
return;
}
@@ -211,7 +219,8 @@
// call (for the purposes of direct-to-voicemail), the information about custom
// ringtones should be available by the time this code executes. We can safely
// request the custom ringtone from the call and expect it to be current.
- mRingtonePlayer.play(foregroundCall.getRingtone());
+ mRingtonePlayer.play(
+ mRingtoneFactory.getRingtone(foregroundCall.getRingtone()));
} else {
Log.v(this, "startRingingOrCallWaiting, skipping because volume is 0");
}
@@ -300,7 +309,6 @@
if (!mVibrator.hasVibrator()) {
return false;
}
- return Settings.System.getInt(context.getContentResolver(),
- Settings.System.VIBRATE_WHEN_RINGING, 0) != 0;
+ return mSystemSettingsUtil.canVibrateWhenRinging(context);
}
}
diff --git a/src/com/android/server/telecom/RingtoneFactory.java b/src/com/android/server/telecom/RingtoneFactory.java
new file mode 100644
index 0000000..5044a90
--- /dev/null
+++ b/src/com/android/server/telecom/RingtoneFactory.java
@@ -0,0 +1,52 @@
+/*
+ * 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.server.telecom;
+
+import android.content.Context;
+import android.media.AudioManager;
+import android.media.RingtoneManager;
+import android.media.Ringtone;
+import android.net.Uri;
+import android.provider.Settings;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Uses a Uri to obtain a {@link Ringtone} from the {@link RingtoneManager} that can be played
+ * by the system during an incoming call.
+ */
+@VisibleForTesting
+public class RingtoneFactory {
+
+ private final Context mContext;
+
+ public RingtoneFactory(Context context) {
+ mContext = context;
+ }
+
+ public Ringtone getRingtone(Uri ringtoneUri) {
+ if (ringtoneUri == null) {
+ ringtoneUri = Settings.System.DEFAULT_RINGTONE_URI;
+ }
+
+ Ringtone ringtone = RingtoneManager.getRingtone(mContext, ringtoneUri);
+ if (ringtone != null) {
+ ringtone.setStreamType(AudioManager.STREAM_RING);
+ }
+ return ringtone;
+ }
+}
diff --git a/src/com/android/server/telecom/ServiceBinder.java b/src/com/android/server/telecom/ServiceBinder.java
index 2e63512..18d6581 100644
--- a/src/com/android/server/telecom/ServiceBinder.java
+++ b/src/com/android/server/telecom/ServiceBinder.java
@@ -25,6 +25,7 @@
import android.text.TextUtils;
import android.util.ArraySet;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
import java.util.Collections;
@@ -114,36 +115,46 @@
@Override
public void onServiceConnected(ComponentName componentName, IBinder binder) {
- synchronized (mLock) {
- Log.i(this, "Service bound %s", componentName);
+ try {
+ Log.startSession("SBC.oSC");
+ synchronized (mLock) {
+ Log.i(this, "Service bound %s", componentName);
- Log.event(mCall, Log.Events.CS_BOUND, componentName);
- mCall = null;
+ Log.event(mCall, Log.Events.CS_BOUND, componentName);
+ mCall = null;
- // Unbind request was queued so unbind immediately.
- if (mIsBindingAborted) {
- clearAbort();
- logServiceDisconnected("onServiceConnected");
- mContext.unbindService(this);
- handleFailedConnection();
- return;
+ // Unbind request was queued so unbind immediately.
+ if (mIsBindingAborted) {
+ clearAbort();
+ logServiceDisconnected("onServiceConnected");
+ mContext.unbindService(this);
+ handleFailedConnection();
+ return;
+ }
+
+ mServiceConnection = this;
+ setBinder(binder);
+ handleSuccessfulConnection();
}
-
- mServiceConnection = this;
- setBinder(binder);
- handleSuccessfulConnection();
+ } finally {
+ Log.endSession();
}
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
- synchronized (mLock) {
- logServiceDisconnected("onServiceDisconnected");
+ try {
+ Log.startSession("SBC.oSD");
+ synchronized (mLock) {
+ logServiceDisconnected("onServiceDisconnected");
- mServiceConnection = null;
- clearAbort();
+ mServiceConnection = null;
+ clearAbort();
- handleServiceDisconnected();
+ handleServiceDisconnected();
+ }
+ } finally {
+ Log.endSession();
}
}
}
@@ -253,7 +264,8 @@
return mComponentName;
}
- final boolean isServiceValid(String actionName) {
+ @VisibleForTesting
+ public boolean isServiceValid(String actionName) {
if (mBinder == null) {
Log.w(this, "%s invoked while service is unbound", actionName);
return false;
diff --git a/src/com/android/server/telecom/Session.java b/src/com/android/server/telecom/Session.java
new file mode 100644
index 0000000..435c505
--- /dev/null
+++ b/src/com/android/server/telecom/Session.java
@@ -0,0 +1,193 @@
+/*
+ * 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.server.telecom;
+
+import android.annotation.NonNull;
+
+import java.util.ArrayList;
+
+/**
+ * The session that stores information about a thread's point of entry into the Telecom code that
+ * persists until the thread exits Telecom.
+ */
+public class Session {
+
+ public static final String START_SESSION = "START_SESSION";
+ public static final String CREATE_SUBSESSION = "CREATE_SUBSESSION";
+ public static final String CONTINUE_SUBSESSION = "CONTINUE_SUBSESSION";
+ public static final String END_SUBSESSION = "END_SUBSESSION";
+ public static final String END_SESSION = "END_SESSION";
+
+ public static final int UNDEFINED = -1;
+
+ private String mSessionId;
+ private String mShortMethodName;
+ private long mExecutionStartTimeMs;
+ private long mExecutionEndTimeMs = UNDEFINED;
+ private Session mParentSession;
+ private ArrayList<Session> mChildSessions;
+ private boolean mIsCompleted = false;
+ private int mChildCounter = 0;
+ private long mThreadId = 0;
+
+ public Session(String sessionId, String shortMethodName, long startTimeMs, long threadID) {
+ setSessionId(sessionId);
+ setShortMethodName(shortMethodName);
+ mExecutionStartTimeMs = startTimeMs;
+ mParentSession = null;
+ mChildSessions = new ArrayList<>(5);
+ mThreadId = threadID;
+ }
+
+ public void setSessionId(@NonNull String sessionId) {
+ if(sessionId == null) {
+ mSessionId = "?";
+ }
+ mSessionId = sessionId;
+ }
+
+ public String getShortMethodName() {
+ return mShortMethodName;
+ }
+
+ public void setShortMethodName(String shortMethodName) {
+ if(shortMethodName == null) {
+ shortMethodName = "";
+ }
+ mShortMethodName = shortMethodName;
+ }
+
+ public void setParentSession(Session parentSession) {
+ mParentSession = parentSession;
+ }
+
+ public void addChild(Session childSession) {
+ if(childSession != null) {
+ mChildSessions.add(childSession);
+ }
+ }
+
+ public void removeChild(Session child) {
+ if(child != null) {
+ mChildSessions.remove(child);
+ }
+ }
+
+ public long getExecutionStartTimeMilliseconds() {
+ return mExecutionStartTimeMs;
+ }
+
+ public void setExecutionStartTimeMs(long startTimeMs) {
+ mExecutionStartTimeMs = startTimeMs;
+ }
+
+ public Session getParentSession() {
+ return mParentSession;
+ }
+
+ public ArrayList<Session> getChildSessions() {
+ return mChildSessions;
+ }
+
+ public boolean isSessionCompleted() {
+ return mIsCompleted;
+ }
+
+ // Mark this session complete. This will be deleted by Log when all subsessions are complete
+ // as well.
+ public void markSessionCompleted(long executionEndTimeMs) {
+ mExecutionEndTimeMs = executionEndTimeMs;
+ mIsCompleted = true;
+ }
+
+ public long getLocalExecutionTime() {
+ if(mExecutionEndTimeMs == UNDEFINED) {
+ return UNDEFINED;
+ }
+ return mExecutionEndTimeMs - mExecutionStartTimeMs;
+ }
+
+ public synchronized String getNextChildId() {
+ return String.valueOf(mChildCounter++);
+ }
+
+ public long getThreadId () {
+ return mThreadId;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof Session)) {
+ return false;
+ }
+ if (obj == this) {
+ return true;
+ }
+ Session otherSession = (Session) obj;
+ return (mSessionId.equals(otherSession.mSessionId)) &&
+ (mShortMethodName.equals(otherSession.mShortMethodName)) &&
+ mExecutionStartTimeMs == otherSession.mExecutionStartTimeMs &&
+ mParentSession == otherSession.mParentSession &&
+ mChildSessions.equals(otherSession.mChildSessions) &&
+ mIsCompleted == otherSession.mIsCompleted &&
+ mExecutionEndTimeMs == otherSession.mExecutionEndTimeMs &&
+ mChildCounter == otherSession.mChildCounter &&
+ mThreadId == otherSession.mThreadId;
+ }
+
+ // Builds full session id recursively
+ private String getFullSessionId() {
+ if(mParentSession == null) {
+ return mSessionId;
+ } else {
+ return mParentSession.getFullSessionId() + "_" + mSessionId;
+ }
+ }
+
+ // Print out the full Session tree from any subsession node
+ public String printFullSessionTree() {
+ // Get to the top of the tree
+ Session topNode = this;
+ while(topNode.getParentSession() != null) {
+ topNode = topNode.getParentSession();
+ }
+ return topNode.printSessionTree();
+ }
+
+ // Recursively move down session tree using DFS, but print out each node when it is reached.
+ public String printSessionTree() {
+ StringBuilder sb = new StringBuilder();
+ printSessionTree(0, sb);
+ return sb.toString();
+ }
+
+ private void printSessionTree(int tabI, StringBuilder sb) {
+ sb.append(toString());
+ for (Session child : mChildSessions) {
+ sb.append("\n");
+ for(int i = 0; i <= tabI; i++) {
+ sb.append("\t");
+ }
+ child.printSessionTree(tabI + 1, sb);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return mShortMethodName + "@" + getFullSessionId();
+ }
+}
diff --git a/src/com/android/server/telecom/StatusBarNotifier.java b/src/com/android/server/telecom/StatusBarNotifier.java
index c8c3c18..d8ede59 100644
--- a/src/com/android/server/telecom/StatusBarNotifier.java
+++ b/src/com/android/server/telecom/StatusBarNotifier.java
@@ -19,12 +19,15 @@
import android.app.StatusBarManager;
import android.content.Context;
+import com.android.internal.annotations.VisibleForTesting;
+
// TODO: Needed for move to system service: import com.android.internal.R;
/**
* Manages the special status bar notifications used by the phone app.
*/
-final class StatusBarNotifier extends CallsManagerListenerBase {
+@VisibleForTesting
+public class StatusBarNotifier extends CallsManagerListenerBase {
private static final String SLOT_MUTE = "mute";
private static final String SLOT_SPEAKERPHONE = "speakerphone";
@@ -50,7 +53,8 @@
}
}
- void notifyMute(boolean isMuted) {
+ @VisibleForTesting
+ public void notifyMute(boolean isMuted) {
// Never display anything if there are no calls.
if (!mCallsManager.hasAnyCalls()) {
isMuted = false;
@@ -74,7 +78,8 @@
mIsShowingMute = isMuted;
}
- void notifySpeakerphone(boolean isSpeakerphone) {
+ @VisibleForTesting
+ public void notifySpeakerphone(boolean isSpeakerphone) {
// Never display anything if there are no calls.
if (!mCallsManager.hasAnyCalls()) {
isSpeakerphone = false;
diff --git a/src/com/android/server/telecom/SystemLoggingContainer.java b/src/com/android/server/telecom/SystemLoggingContainer.java
new file mode 100644
index 0000000..0b65b09
--- /dev/null
+++ b/src/com/android/server/telecom/SystemLoggingContainer.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.server.telecom;
+
+/**
+ * Create a container for the Android Logging class so that it can be mocked in testing.
+ */
+public class SystemLoggingContainer {
+
+ public void v(String TAG, String msg) {
+ android.util.Slog.v(TAG, msg);
+ }
+
+ public void d(String TAG, String msg) {
+ android.util.Slog.d(TAG, msg);
+ }
+
+ public void i(String TAG, String msg) {
+ android.util.Slog.i(TAG, msg);
+ }
+
+ public void w(String TAG, String msg) {
+ android.util.Slog.w(TAG, msg);
+ }
+
+ public void e(String TAG, String msg, Throwable tr) {
+ android.util.Slog.e(TAG, msg, tr);
+ }
+
+ public void wtf(String TAG, String msg, Throwable tr) {
+ android.util.Slog.wtf(TAG, msg, tr);
+ }
+}
diff --git a/src/com/android/server/telecom/SystemSettingsUtil.java b/src/com/android/server/telecom/SystemSettingsUtil.java
new file mode 100644
index 0000000..3c75e4d
--- /dev/null
+++ b/src/com/android/server/telecom/SystemSettingsUtil.java
@@ -0,0 +1,39 @@
+/*
+ * 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.server.telecom;
+
+import android.content.Context;
+import android.provider.Settings;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Accesses the Global System settings for more control during testing.
+ */
+@VisibleForTesting
+public class SystemSettingsUtil {
+
+ public boolean isTheaterModeOn(Context context) {
+ return Settings.Global.getInt(context.getContentResolver(), Settings.Global.THEATER_MODE_ON,
+ 0) == 1;
+ }
+
+ public boolean canVibrateWhenRinging(Context context) {
+ return Settings.System.getInt(context.getContentResolver(),
+ Settings.System.VIBRATE_WHEN_RINGING, 0) != 0;
+ }
+}
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index 1650d58..30fcb65 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -34,16 +34,16 @@
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
-import android.os.IBinder;
+import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
import android.telecom.DefaultDialerManager;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
+import android.telecom.VideoProfile;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
-import android.text.TextUtils;
// TODO: Needed for move to system service: import com.android.internal.R;
import com.android.internal.telecom.ITelecomService;
@@ -62,35 +62,33 @@
public class TelecomServiceImpl {
private static final String PERMISSION_PROCESS_PHONE_ACCOUNT_REGISTRATION =
"android.permission.PROCESS_PHONE_ACCOUNT_REGISTRATION";
+ private static final int DEFAULT_VIDEO_STATE = -1;
private final ITelecomService.Stub mBinderImpl = new ITelecomService.Stub() {
@Override
public PhoneAccountHandle getDefaultOutgoingPhoneAccount(String uriScheme,
String callingPackage) {
- synchronized (mLock) {
- if (!canReadPhoneState(callingPackage, "getDefaultOutgoingPhoneAccount")) {
- return null;
- }
-
- long token = Binder.clearCallingIdentity();
- try {
- PhoneAccountHandle defaultOutgoingPhoneAccount =
- mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(uriScheme);
- // Make sure that the calling user can see this phone account.
- // TODO: Does this isVisible check actually work considering we are clearing
- // the calling identity?
- if (defaultOutgoingPhoneAccount != null
- && !isVisibleToCaller(defaultOutgoingPhoneAccount)) {
- Log.w(this, "No account found for the calling user");
+ try {
+ Log.startSession("TSI.gDOPA");
+ synchronized (mLock) {
+ if (!canReadPhoneState(callingPackage, "getDefaultOutgoingPhoneAccount")) {
return null;
}
- return defaultOutgoingPhoneAccount;
- } catch (Exception e) {
- Log.e(this, e, "getDefaultOutgoingPhoneAccount");
- throw e;
- } finally {
- Binder.restoreCallingIdentity(token);
+
+ final UserHandle callingUserHandle = Binder.getCallingUserHandle();
+ long token = Binder.clearCallingIdentity();
+ try {
+ return mPhoneAccountRegistrar
+ .getOutgoingPhoneAccountForScheme(uriScheme, callingUserHandle);
+ } catch (Exception e) {
+ Log.e(this, e, "getDefaultOutgoingPhoneAccount");
+ throw e;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
+ } finally {
+ Log.endSession();
}
}
@@ -98,94 +96,109 @@
public PhoneAccountHandle getUserSelectedOutgoingPhoneAccount() {
synchronized (mLock) {
try {
- PhoneAccountHandle userSelectedOutgoingPhoneAccount =
- mPhoneAccountRegistrar.getUserSelectedOutgoingPhoneAccount();
- // Make sure that the calling user can see this phone account.
- if (!isVisibleToCaller(userSelectedOutgoingPhoneAccount)) {
- Log.w(this, "No account found for the calling user");
- return null;
- }
- return userSelectedOutgoingPhoneAccount;
+ Log.startSession("TSI.gUSOPA");
+ final UserHandle callingUserHandle = Binder.getCallingUserHandle();
+ return mPhoneAccountRegistrar.getUserSelectedOutgoingPhoneAccount(
+ callingUserHandle);
} catch (Exception e) {
Log.e(this, e, "getUserSelectedOutgoingPhoneAccount");
throw e;
+ } finally {
+ Log.endSession();
}
}
}
@Override
public void setUserSelectedOutgoingPhoneAccount(PhoneAccountHandle accountHandle) {
- synchronized (mLock) {
- enforceModifyPermission();
-
- long token = Binder.clearCallingIdentity();
- try {
- mPhoneAccountRegistrar.setUserSelectedOutgoingPhoneAccount(accountHandle);
- } catch (Exception e) {
- Log.e(this, e, "setUserSelectedOutgoingPhoneAccount");
- throw e;
- } finally {
- Binder.restoreCallingIdentity(token);
+ try {
+ Log.startSession("TSI.sUSOPA");
+ synchronized (mLock) {
+ enforceModifyPermission();
+ UserHandle callingUserHandle = Binder.getCallingUserHandle();
+ long token = Binder.clearCallingIdentity();
+ try {
+ mPhoneAccountRegistrar.setUserSelectedOutgoingPhoneAccount(
+ accountHandle, callingUserHandle);
+ } catch (Exception e) {
+ Log.e(this, e, "setUserSelectedOutgoingPhoneAccount");
+ throw e;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
+ } finally {
+ Log.endSession();
}
}
@Override
public List<PhoneAccountHandle> getCallCapablePhoneAccounts(
boolean includeDisabledAccounts, String callingPackage) {
- if (!canReadPhoneState(callingPackage, "getDefaultOutgoingPhoneAccount")) {
- return Collections.emptyList();
- }
-
- synchronized (mLock) {
- long token = Binder.clearCallingIdentity();
- try {
- // TODO: Does this isVisible check actually work considering we are clearing
- // the calling identity?
- return filterForAccountsVisibleToCaller(
- mPhoneAccountRegistrar.getCallCapablePhoneAccounts(
- null, includeDisabledAccounts));
- } catch (Exception e) {
- Log.e(this, e, "getCallCapablePhoneAccounts");
- throw e;
- } finally {
- Binder.restoreCallingIdentity(token);
+ try {
+ Log.startSession("TSI.gCCPA");
+ if (!canReadPhoneState(callingPackage, "getDefaultOutgoingPhoneAccount")) {
+ return Collections.emptyList();
}
+ synchronized (mLock) {
+ final UserHandle callingUserHandle = Binder.getCallingUserHandle();
+ long token = Binder.clearCallingIdentity();
+ try {
+ return mPhoneAccountRegistrar.getCallCapablePhoneAccounts(null,
+ includeDisabledAccounts, callingUserHandle);
+ } catch (Exception e) {
+ Log.e(this, e, "getCallCapablePhoneAccounts");
+ throw e;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ } finally {
+ Log.endSession();
}
}
@Override
public List<PhoneAccountHandle> getPhoneAccountsSupportingScheme(String uriScheme,
String callingPackage) {
- synchronized (mLock) {
- if (!canReadPhoneState(callingPackage, "getPhoneAccountsSupportingScheme")) {
- return Collections.emptyList();
+ try {
+ Log.startSession("TSI.gPASS");
+ synchronized (mLock) {
+ if (!canReadPhoneState(callingPackage, "getPhoneAccountsSupportingScheme")) {
+ return Collections.emptyList();
+ }
+ final UserHandle callingUserHandle = Binder.getCallingUserHandle();
+ long token = Binder.clearCallingIdentity();
+ try {
+ return mPhoneAccountRegistrar.getCallCapablePhoneAccounts(uriScheme, false,
+ callingUserHandle);
+ } catch (Exception e) {
+ Log.e(this, e, "getPhoneAccountsSupportingScheme %s", uriScheme);
+ throw e;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
-
- long token = Binder.clearCallingIdentity();
- try {
- // TODO: Does this isVisible check actually work considering we are clearing
- // the calling identity?
- return filterForAccountsVisibleToCaller(
- mPhoneAccountRegistrar.getCallCapablePhoneAccounts(uriScheme, false));
- } catch (Exception e) {
- Log.e(this, e, "getPhoneAccountsSupportingScheme %s", uriScheme);
- throw e;
- } finally {
- Binder.restoreCallingIdentity(token);
- }
+ } finally {
+ Log.endSession();
}
}
@Override
public List<PhoneAccountHandle> getPhoneAccountsForPackage(String packageName) {
synchronized (mLock) {
+ final UserHandle callingUserHandle = Binder.getCallingUserHandle();
+ long token = Binder.clearCallingIdentity();
try {
- return filterForAccountsVisibleToCaller(
- mPhoneAccountRegistrar.getPhoneAccountsForPackage(packageName));
+ Log.startSession("TSI.gPAFP");
+ return mPhoneAccountRegistrar.getPhoneAccountsForPackage(packageName,
+ callingUserHandle);
} catch (Exception e) {
Log.e(this, e, "getPhoneAccountsForPackage %s", packageName);
throw e;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ Log.endSession();
}
}
}
@@ -193,16 +206,23 @@
@Override
public PhoneAccount getPhoneAccount(PhoneAccountHandle accountHandle) {
synchronized (mLock) {
+ final UserHandle callingUserHandle = Binder.getCallingUserHandle();
+ long token = Binder.clearCallingIdentity();
try {
- if (!isVisibleToCaller(accountHandle)) {
- Log.d(this, "%s is not visible for the calling user [gPA]", accountHandle);
- return null;
- }
- // TODO: Do we really want to return for *any* user?
- return mPhoneAccountRegistrar.getPhoneAccount(accountHandle);
+ Log.startSession("TSI.gPA");
+ // In ideal case, we should not resolve the handle across profiles. But given
+ // the fact that profile's call is handled by its parent user's in-call UI,
+ // parent user's in call UI need to be able to get phone account from the
+ // profile's phone account handle.
+ return mPhoneAccountRegistrar
+ .getPhoneAccount(accountHandle, callingUserHandle,
+ /* acrossProfiles */ true);
} catch (Exception e) {
Log.e(this, e, "getPhoneAccount %s", accountHandle);
throw e;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ Log.endSession();
}
}
}
@@ -211,11 +231,14 @@
public int getAllPhoneAccountsCount() {
synchronized (mLock) {
try {
+ Log.startSession("TSI.gAPAC");
// This list is pre-filtered for the calling user.
return getAllPhoneAccounts().size();
} catch (Exception e) {
Log.e(this, e, "getAllPhoneAccountsCount");
throw e;
+ } finally {
+ Log.endSession();
}
}
}
@@ -223,20 +246,17 @@
@Override
public List<PhoneAccount> getAllPhoneAccounts() {
synchronized (mLock) {
+ final UserHandle callingUserHandle = Binder.getCallingUserHandle();
+ long token = Binder.clearCallingIdentity();
try {
- List<PhoneAccount> allPhoneAccounts = mPhoneAccountRegistrar
- .getAllPhoneAccounts();
- List<PhoneAccount> profilePhoneAccounts = new ArrayList<>(
- allPhoneAccounts.size());
- for (PhoneAccount phoneAccount : allPhoneAccounts) {
- if (isVisibleToCaller(phoneAccount)) {
- profilePhoneAccounts.add(phoneAccount);
- }
- }
- return profilePhoneAccounts;
+ Log.startSession("TSI.gAPA");
+ return mPhoneAccountRegistrar.getAllPhoneAccounts(callingUserHandle);
} catch (Exception e) {
Log.e(this, e, "getAllPhoneAccounts");
throw e;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ Log.endSession();
}
}
}
@@ -244,92 +264,104 @@
@Override
public List<PhoneAccountHandle> getAllPhoneAccountHandles() {
synchronized (mLock) {
+ final UserHandle callingUserHandle = Binder.getCallingUserHandle();
+ long token = Binder.clearCallingIdentity();
try {
- return filterForAccountsVisibleToCaller(
- mPhoneAccountRegistrar.getAllPhoneAccountHandles());
+ Log.startSession("TSI.gAPAH");
+ return mPhoneAccountRegistrar.getAllPhoneAccountHandles(callingUserHandle);
} catch (Exception e) {
Log.e(this, e, "getAllPhoneAccounts");
throw e;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ Log.endSession();
}
}
}
@Override
public PhoneAccountHandle getSimCallManager() {
- long token = Binder.clearCallingIdentity();
- int user;
try {
- user = ActivityManager.getCurrentUser();
+ Log.startSession("TSI.gSCM");
+ long token = Binder.clearCallingIdentity();
+ int user;
+ try {
+ user = ActivityManager.getCurrentUser();
+ return getSimCallManagerForUser(user);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
} finally {
- Binder.restoreCallingIdentity(token);
+ Log.endSession();
}
- return getSimCallManagerForUser(user);
}
@Override
public PhoneAccountHandle getSimCallManagerForUser(int user) {
synchronized (mLock) {
try {
- PhoneAccountHandle accountHandle = null;
-
+ Log.startSession("TSI.gSCMFU");
+ final int callingUid = Binder.getCallingUid();
long token = Binder.clearCallingIdentity();
try {
- accountHandle = mPhoneAccountRegistrar.getSimCallManager(user);
+ if (user != ActivityManager.getCurrentUser()) {
+ enforceCrossUserPermission(callingUid);
+ }
+ return mPhoneAccountRegistrar.getSimCallManager(UserHandle.of(user));
} finally {
- // We restore early so that isVisibleToCaller invocation below uses the
- // right user context.
Binder.restoreCallingIdentity(token);
}
-
- if (!isVisibleToCaller(accountHandle)) {
- Log.d(this, "%s is not visible for the calling user [gsCM]", accountHandle);
- return null;
- }
- return accountHandle;
} catch (Exception e) {
Log.e(this, e, "getSimCallManager");
throw e;
+ } finally {
+ Log.endSession();
}
}
}
@Override
public void registerPhoneAccount(PhoneAccount account) {
- synchronized (mLock) {
- if (!mContext.getApplicationContext().getResources().getBoolean(
- com.android.internal.R.bool.config_voice_capable)) {
- Log.w(this, "registerPhoneAccount not allowed on non-voice capable device.");
- return;
- }
- try {
- enforcePhoneAccountModificationForPackage(
- account.getAccountHandle().getComponentName().getPackageName());
- if (account.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) {
- enforceRegisterSimSubscriptionPermission();
+ try {
+ Log.startSession("TSI.rPA");
+ synchronized (mLock) {
+ if (!mContext.getApplicationContext().getResources().getBoolean(
+ com.android.internal.R.bool.config_voice_capable)) {
+ Log.w(this,
+ "registerPhoneAccount not allowed on non-voice capable device.");
+ return;
}
- if (account.hasCapabilities(PhoneAccount.CAPABILITY_MULTI_USER)) {
- enforceRegisterMultiUser();
- }
- enforceUserHandleMatchesCaller(account.getAccountHandle());
-
- mPhoneAccountRegistrar.registerPhoneAccount(account);
-
- // Broadcast an intent indicating the phone account which was registered.
- long token = Binder.clearCallingIdentity();
try {
- Intent intent = new Intent(TelecomManager.ACTION_PHONE_ACCOUNT_REGISTERED);
- intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
- account.getAccountHandle());
- Log.i(this, "Sending phone-account registered intent as user");
- mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
- PERMISSION_PROCESS_PHONE_ACCOUNT_REGISTRATION);
- } finally {
- Binder.restoreCallingIdentity(token);
+ enforcePhoneAccountModificationForPackage(
+ account.getAccountHandle().getComponentName().getPackageName());
+ if (account.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) {
+ enforceRegisterSimSubscriptionPermission();
+ }
+ if (account.hasCapabilities(PhoneAccount.CAPABILITY_MULTI_USER)) {
+ enforceRegisterMultiUser();
+ }
+ enforceUserHandleMatchesCaller(account.getAccountHandle());
+ mPhoneAccountRegistrar.registerPhoneAccount(account);
+ // Broadcast an intent indicating the phone account which was registered.
+ long token = Binder.clearCallingIdentity();
+ try {
+ Intent intent = new Intent(
+ TelecomManager.ACTION_PHONE_ACCOUNT_REGISTERED);
+ intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
+ account.getAccountHandle());
+ Log.i(this, "Sending phone-account registered intent as user");
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
+ PERMISSION_PROCESS_PHONE_ACCOUNT_REGISTRATION);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ } catch (Exception e) {
+ Log.e(this, e, "registerPhoneAccount %s", account);
+ throw e;
}
- } catch (Exception e) {
- Log.e(this, e, "registerPhoneAccount %s", account);
- throw e;
}
+ } finally {
+ Log.endSession();
}
}
@@ -337,6 +369,7 @@
public void unregisterPhoneAccount(PhoneAccountHandle accountHandle) {
synchronized (mLock) {
try {
+ Log.startSession("TSI.uPA");
enforcePhoneAccountModificationForPackage(
accountHandle.getComponentName().getPackageName());
enforceUserHandleMatchesCaller(accountHandle);
@@ -347,7 +380,8 @@
try {
Intent intent =
new Intent(TelecomManager.ACTION_PHONE_ACCOUNT_UNREGISTERED);
- intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, accountHandle);
+ intent.putExtra(
+ TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, accountHandle);
Log.i(this, "Sending phone-account unregistered intent as user");
mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
PERMISSION_PROCESS_PHONE_ACCOUNT_REGISTRATION);
@@ -357,6 +391,8 @@
} catch (Exception e) {
Log.e(this, e, "unregisterPhoneAccount %s", accountHandle);
throw e;
+ } finally {
+ Log.endSession();
}
}
}
@@ -365,12 +401,15 @@
public void clearAccounts(String packageName) {
synchronized (mLock) {
try {
+ Log.startSession("TSI.cA");
enforcePhoneAccountModificationForPackage(packageName);
mPhoneAccountRegistrar
.clearAccounts(packageName, Binder.getCallingUserHandle());
} catch (Exception e) {
Log.e(this, e, "clearAccounts %s", packageName);
throw e;
+ } finally {
+ Log.endSession();
}
}
}
@@ -381,25 +420,30 @@
@Override
public boolean isVoiceMailNumber(PhoneAccountHandle accountHandle, String number,
String callingPackage) {
- synchronized (mLock) {
- if (!canReadPhoneState(callingPackage, "isVoiceMailNumber")) {
- return false;
+ try {
+ Log.startSession("TSI.iVMN");
+ synchronized (mLock) {
+ if (!canReadPhoneState(callingPackage, "isVoiceMailNumber")) {
+ return false;
+ }
+ final UserHandle callingUserHandle = Binder.getCallingUserHandle();
+ if (!isPhoneAccountHandleVisibleToCallingUser(accountHandle,
+ callingUserHandle)) {
+ Log.d(this, "%s is not visible for the calling user [iVMN]", accountHandle);
+ return false;
+ }
+ long token = Binder.clearCallingIdentity();
+ try {
+ return mPhoneAccountRegistrar.isVoiceMailNumber(accountHandle, number);
+ } catch (Exception e) {
+ Log.e(this, e, "getSubscriptionIdForPhoneAccount");
+ throw e;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
-
- if (!isVisibleToCaller(accountHandle)) {
- Log.d(this, "%s is not visible for the calling user [iVMN]", accountHandle);
- return false;
- }
-
- long token = Binder.clearCallingIdentity();
- try {
- return mPhoneAccountRegistrar.isVoiceMailNumber(accountHandle, number);
- } catch (Exception e) {
- Log.e(this, e, "getSubscriptionIdForPhoneAccount");
- throw e;
- } finally {
- Binder.restoreCallingIdentity(token);
- }
+ } finally {
+ Log.endSession();
}
}
@@ -408,27 +452,33 @@
*/
@Override
public String getVoiceMailNumber(PhoneAccountHandle accountHandle, String callingPackage) {
- synchronized (mLock) {
- if (!canReadPhoneState(callingPackage, "getVoiceMailNumber")) {
- return null;
- }
-
- try {
- if (!isVisibleToCaller(accountHandle)) {
- Log.d(this, "%s is not visible for the calling user [gVMN]", accountHandle);
+ try {
+ Log.startSession("TSI.gVMN");
+ synchronized (mLock) {
+ if (!canReadPhoneState(callingPackage, "getVoiceMailNumber")) {
return null;
}
-
- int subId = SubscriptionManager.getDefaultVoiceSubId();
- if (accountHandle != null) {
- subId = mPhoneAccountRegistrar
- .getSubscriptionIdForPhoneAccount(accountHandle);
+ try {
+ final UserHandle callingUserHandle = Binder.getCallingUserHandle();
+ if (!isPhoneAccountHandleVisibleToCallingUser(accountHandle,
+ callingUserHandle)) {
+ Log.d(this, "%s is not visible for the calling user [gVMN]",
+ accountHandle);
+ return null;
+ }
+ int subId = SubscriptionManager.getDefaultVoiceSubId();
+ if (accountHandle != null) {
+ subId = mPhoneAccountRegistrar
+ .getSubscriptionIdForPhoneAccount(accountHandle);
+ }
+ return getTelephonyManager().getVoiceMailNumber(subId);
+ } catch (Exception e) {
+ Log.e(this, e, "getSubscriptionIdForPhoneAccount");
+ throw e;
}
- return getTelephonyManager().getVoiceMailNumber(subId);
- } catch (Exception e) {
- Log.e(this, e, "getSubscriptionIdForPhoneAccount");
- throw e;
}
+ } finally {
+ Log.endSession();
}
}
@@ -437,27 +487,34 @@
*/
@Override
public String getLine1Number(PhoneAccountHandle accountHandle, String callingPackage) {
- if (!canReadPhoneState(callingPackage, "getLine1Number")) {
- return null;
- }
-
- synchronized (mLock) {
- if (!isVisibleToCaller(accountHandle)) {
- Log.d(this, "%s is not visible for the calling user [gL1N]", accountHandle);
+ try {
+ Log.startSession("getL1N");
+ if (!canReadPhoneState(callingPackage, "getLine1Number")) {
return null;
}
- long token = Binder.clearCallingIdentity();
- try {
- int subId =
- mPhoneAccountRegistrar.getSubscriptionIdForPhoneAccount(accountHandle);
- return getTelephonyManager().getLine1NumberForSubscriber(subId);
- } catch (Exception e) {
- Log.e(this, e, "getSubscriptionIdForPhoneAccount");
- throw e;
- } finally {
- Binder.restoreCallingIdentity(token);
+ synchronized (mLock) {
+ final UserHandle callingUserHandle = Binder.getCallingUserHandle();
+ if (!isPhoneAccountHandleVisibleToCallingUser(accountHandle,
+ callingUserHandle)) {
+ Log.d(this, "%s is not visible for the calling user [gL1N]", accountHandle);
+ return null;
+ }
+
+ long token = Binder.clearCallingIdentity();
+ try {
+ int subId = mPhoneAccountRegistrar.getSubscriptionIdForPhoneAccount(
+ accountHandle);
+ return getTelephonyManager().getLine1NumberForSubscriber(subId);
+ } catch (Exception e) {
+ Log.e(this, e, "getSubscriptionIdForPhoneAccount");
+ throw e;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
+ } finally {
+ Log.endSession();
}
}
@@ -466,16 +523,21 @@
*/
@Override
public void silenceRinger(String callingPackage) {
- synchronized (mLock) {
- enforcePermissionOrPrivilegedDialer(MODIFY_PHONE_STATE, callingPackage);
+ try {
+ Log.startSession("TSI.sR");
+ synchronized (mLock) {
+ enforcePermissionOrPrivilegedDialer(MODIFY_PHONE_STATE, callingPackage);
- long token = Binder.clearCallingIdentity();
- try {
- Log.i(this, "Silence Ringer requested by %s", callingPackage);
- mCallsManager.getRinger().silence();
- } finally {
- Binder.restoreCallingIdentity(token);
+ long token = Binder.clearCallingIdentity();
+ try {
+ Log.i(this, "Silence Ringer requested by %s", callingPackage);
+ mCallsManager.getRinger().silence();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
+ } finally {
+ Log.endSession();
}
}
@@ -486,11 +548,16 @@
*/
@Override
public ComponentName getDefaultPhoneApp() {
- // No need to synchronize
- Resources resources = mContext.getResources();
- return new ComponentName(
- resources.getString(R.string.ui_default_package),
- resources.getString(R.string.dialer_default_class));
+ try {
+ Log.startSession("TSI.gDPA");
+ // No need to synchronize
+ Resources resources = mContext.getResources();
+ return new ComponentName(
+ resources.getString(R.string.ui_default_package),
+ resources.getString(R.string.dialer_default_class));
+ } finally {
+ Log.endSession();
+ }
}
/**
@@ -501,11 +568,16 @@
*/
@Override
public String getDefaultDialerPackage() {
- final long token = Binder.clearCallingIdentity();
try {
- return DefaultDialerManager.getDefaultDialerApplication(mContext);
+ Log.startSession("TSI.gDDP");
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return DefaultDialerManager.getDefaultDialerApplication(mContext);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
} finally {
- Binder.restoreCallingIdentity(token);
+ Log.endSession();
}
}
@@ -514,7 +586,12 @@
*/
@Override
public String getSystemDialerPackage() {
- return mContext.getResources().getString(R.string.ui_default_package);
+ try {
+ Log.startSession("TSI.gSDP");
+ return mContext.getResources().getString(R.string.ui_default_package);
+ } finally {
+ Log.endSession();
+ }
}
/**
@@ -522,14 +599,19 @@
*/
@Override
public boolean isInCall(String callingPackage) {
- if (!canReadPhoneState(callingPackage, "isInCall")) {
- return false;
- }
+ try {
+ Log.startSession("TSI.iIC");
+ if (!canReadPhoneState(callingPackage, "isInCall")) {
+ return false;
+ }
- synchronized (mLock) {
- final int callState = mCallsManager.getCallState();
- return callState == TelephonyManager.CALL_STATE_OFFHOOK
- || callState == TelephonyManager.CALL_STATE_RINGING;
+ synchronized (mLock) {
+ final int callState = mCallsManager.getCallState();
+ return callState == TelephonyManager.CALL_STATE_OFFHOOK
+ || callState == TelephonyManager.CALL_STATE_RINGING;
+ }
+ } finally {
+ Log.endSession();
}
}
@@ -538,12 +620,22 @@
*/
@Override
public boolean isRinging(String callingPackage) {
- if (!canReadPhoneState(callingPackage, "isRinging")) {
- return false;
- }
+ try {
+ Log.startSession("TSI.iR");
+ if (!canReadPhoneState(callingPackage, "isRinging")) {
+ return false;
+ }
- synchronized (mLock) {
- return mCallsManager.getCallState() == TelephonyManager.CALL_STATE_RINGING;
+ synchronized (mLock) {
+ // Note: We are explicitly checking the calls telecom is tracking rather than
+ // relying on mCallsManager#getCallState(). Since getCallState() relies on the
+ // current state as tracked by PhoneStateBroadcaster, any failure to properly
+ // track the current call state there could result in the wrong ringing state
+ // being reported by this API.
+ return mCallsManager.hasRingingCall();
+ }
+ } finally {
+ Log.endSession();
}
}
@@ -552,8 +644,13 @@
*/
@Override
public int getCallState() {
- synchronized (mLock) {
- return mCallsManager.getCallState();
+ try {
+ Log.startSession("TSI.getCallState");
+ synchronized (mLock) {
+ return mCallsManager.getCallState();
+ }
+ } finally {
+ Log.endSession();
}
}
@@ -562,15 +659,20 @@
*/
@Override
public boolean endCall() {
- synchronized (mLock) {
- enforceModifyPermission();
+ try {
+ Log.startSession("TSI.eC");
+ synchronized (mLock) {
+ enforceModifyPermission();
- long token = Binder.clearCallingIdentity();
- try {
- return endCallInternal();
- } finally {
- Binder.restoreCallingIdentity(token);
+ long token = Binder.clearCallingIdentity();
+ try {
+ return endCallInternal();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
+ } finally {
+ Log.endSession();
}
}
@@ -579,15 +681,43 @@
*/
@Override
public void acceptRingingCall() {
- synchronized (mLock) {
- enforceModifyPermission();
+ try {
+ Log.startSession("TSI.aRC");
+ synchronized (mLock) {
+ enforceModifyPermission();
- long token = Binder.clearCallingIdentity();
- try {
- acceptRingingCallInternal();
- } finally {
- Binder.restoreCallingIdentity(token);
+ long token = Binder.clearCallingIdentity();
+ try {
+ acceptRingingCallInternal(DEFAULT_VIDEO_STATE);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
+ } finally {
+ Log.endSession();
+ }
+ }
+
+ /**
+ * @see android.telecom.TelecomManager#acceptRingingCall(int)
+ *
+ */
+ @Override
+ public void acceptRingingCallWithVideoState(int videoState) {
+ try {
+ Log.startSession("TSI.aRCWVS");
+ synchronized (mLock) {
+ enforceModifyPermission();
+
+ long token = Binder.clearCallingIdentity();
+ try {
+ acceptRingingCallInternal(videoState);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ } finally {
+ Log.endSession();
}
}
@@ -596,18 +726,23 @@
*/
@Override
public void showInCallScreen(boolean showDialpad, String callingPackage) {
- if (!canReadPhoneState(callingPackage, "showInCallScreen")) {
- return;
- }
-
- synchronized (mLock) {
-
- long token = Binder.clearCallingIdentity();
- try {
- mCallsManager.getInCallController().bringToForeground(showDialpad);
- } finally {
- Binder.restoreCallingIdentity(token);
+ try {
+ Log.startSession("TSI.sICS");
+ if (!canReadPhoneState(callingPackage, "showInCallScreen")) {
+ return;
}
+
+ synchronized (mLock) {
+
+ long token = Binder.clearCallingIdentity();
+ try {
+ mCallsManager.getInCallController().bringToForeground(showDialpad);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ } finally {
+ Log.endSession();
}
}
@@ -616,35 +751,45 @@
*/
@Override
public void cancelMissedCallsNotification(String callingPackage) {
- synchronized (mLock) {
- enforcePermissionOrPrivilegedDialer(MODIFY_PHONE_STATE, callingPackage);
- long token = Binder.clearCallingIdentity();
- try {
- mCallsManager.getMissedCallNotifier().clearMissedCalls();
- } finally {
- Binder.restoreCallingIdentity(token);
+ try {
+ Log.startSession("TSI.cMCN");
+ synchronized (mLock) {
+ enforcePermissionOrPrivilegedDialer(MODIFY_PHONE_STATE, callingPackage);
+ long token = Binder.clearCallingIdentity();
+ try {
+ mCallsManager.getMissedCallNotifier().clearMissedCalls();
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
+ } finally {
+ Log.endSession();
}
}
-
/**
* @see android.telecom.TelecomManager#handleMmi
*/
@Override
public boolean handlePinMmi(String dialString, String callingPackage) {
- synchronized (mLock) {
- enforcePermissionOrPrivilegedDialer(MODIFY_PHONE_STATE, callingPackage);
+ try {
+ Log.startSession("TSI.hPM");
+ synchronized (mLock) {
+ enforcePermissionOrPrivilegedDialer(MODIFY_PHONE_STATE, callingPackage);
- // Switch identity so that TelephonyManager checks Telecom's permissions instead.
- long token = Binder.clearCallingIdentity();
- boolean retval = false;
- try {
- retval = getTelephonyManager().handlePinMmi(dialString);
- } finally {
- Binder.restoreCallingIdentity(token);
+ // Switch identity so that TelephonyManager checks Telecom's permissions
+ // instead.
+ long token = Binder.clearCallingIdentity();
+ boolean retval = false;
+ try {
+ retval = getTelephonyManager().handlePinMmi(dialString);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+
+ return retval;
}
-
- return retval;
+ }finally {
+ Log.endSession();
}
}
@@ -652,30 +797,35 @@
* @see android.telecom.TelecomManager#handleMmi
*/
@Override
- public boolean handlePinMmiForPhoneAccount(
- PhoneAccountHandle accountHandle,
- String dialString,
- String callingPackage) {
- synchronized (mLock) {
- enforcePermissionOrPrivilegedDialer(MODIFY_PHONE_STATE, callingPackage);
+ public boolean handlePinMmiForPhoneAccount(PhoneAccountHandle accountHandle,
+ String dialString, String callingPackage) {
+ try {
+ Log.startSession("TSI.hPMFPA");
+ synchronized (mLock) {
+ enforcePermissionOrPrivilegedDialer(MODIFY_PHONE_STATE, callingPackage);
- if (!isVisibleToCaller(accountHandle)) {
- Log.d(this, "%s is not visible for the calling user [hMMI]", accountHandle);
- return false;
+ UserHandle callingUserHandle = Binder.getCallingUserHandle();
+ if (!isPhoneAccountHandleVisibleToCallingUser(accountHandle,
+ callingUserHandle)) {
+ Log.d(this, "%s is not visible for the calling user [hMMI]", accountHandle);
+ return false;
+ }
+
+ // Switch identity so that TelephonyManager checks Telecom's permissions
+ // instead.
+ long token = Binder.clearCallingIdentity();
+ boolean retval = false;
+ try {
+ int subId = mPhoneAccountRegistrar.getSubscriptionIdForPhoneAccount(
+ accountHandle);
+ retval = getTelephonyManager().handlePinMmiForSubscriber(subId, dialString);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ return retval;
}
-
- // Switch identity so that TelephonyManager checks Telecom's permissions instead.
- long token = Binder.clearCallingIdentity();
- boolean retval = false;
- try {
- int subId = mPhoneAccountRegistrar
- .getSubscriptionIdForPhoneAccount(accountHandle);
- retval = getTelephonyManager().handlePinMmiForSubscriber(subId, dialString);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
-
- return retval;
+ }finally {
+ Log.endSession();
}
}
@@ -685,26 +835,32 @@
@Override
public Uri getAdnUriForPhoneAccount(PhoneAccountHandle accountHandle,
String callingPackage) {
- synchronized (mLock) {
- enforcePermissionOrPrivilegedDialer(MODIFY_PHONE_STATE, callingPackage);
+ try {
+ Log.startSession("TSI.aAUFPA");
+ synchronized (mLock) {
+ enforcePermissionOrPrivilegedDialer(MODIFY_PHONE_STATE, callingPackage);
+ if (!isPhoneAccountHandleVisibleToCallingUser(accountHandle,
+ Binder.getCallingUserHandle())) {
+ Log.d(this, "%s is not visible for the calling user [gA4PA]",
+ accountHandle);
+ return null;
+ }
+ // Switch identity so that TelephonyManager checks Telecom's permissions
+ // instead.
+ long token = Binder.clearCallingIdentity();
+ String retval = "content://icc/adn/";
+ try {
+ long subId = mPhoneAccountRegistrar
+ .getSubscriptionIdForPhoneAccount(accountHandle);
+ retval = retval + "subId/" + subId;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
- if (!isVisibleToCaller(accountHandle)) {
- Log.d(this, "%s is not visible for the calling user [gA4PA]", accountHandle);
- return null;
+ return Uri.parse(retval);
}
-
- // Switch identity so that TelephonyManager checks Telecom's permissions instead.
- long token = Binder.clearCallingIdentity();
- String retval = "content://icc/adn/";
- try {
- long subId = mPhoneAccountRegistrar
- .getSubscriptionIdForPhoneAccount(accountHandle);
- retval = retval + "subId/" + subId;
- } finally {
- Binder.restoreCallingIdentity(token);
- }
-
- return Uri.parse(retval);
+ } finally {
+ Log.endSession();
}
}
@@ -713,12 +869,17 @@
*/
@Override
public boolean isTtySupported(String callingPackage) {
- if (!canReadPhoneState(callingPackage, "hasVoiceMailNumber")) {
- return false;
- }
+ try {
+ Log.startSession("TSI.iTS");
+ if (!canReadPhoneState(callingPackage, "hasVoiceMailNumber")) {
+ return false;
+ }
- synchronized (mLock) {
- return mCallsManager.isTtySupported();
+ synchronized (mLock) {
+ return mCallsManager.isTtySupported();
+ }
+ } finally {
+ Log.endSession();
}
}
@@ -727,12 +888,17 @@
*/
@Override
public int getCurrentTtyMode(String callingPackage) {
- if (!canReadPhoneState(callingPackage, "getCurrentTtyMode")) {
- return TelecomManager.TTY_MODE_OFF;
- }
+ try {
+ Log.startSession("TSI.gCTM");
+ if (!canReadPhoneState(callingPackage, "getCurrentTtyMode")) {
+ return TelecomManager.TTY_MODE_OFF;
+ }
- synchronized (mLock) {
- return mCallsManager.getCurrentTtyMode();
+ synchronized (mLock) {
+ return mCallsManager.getCurrentTtyMode();
+ }
+ } finally {
+ Log.endSession();
}
}
@@ -741,39 +907,47 @@
*/
@Override
public void addNewIncomingCall(PhoneAccountHandle phoneAccountHandle, Bundle extras) {
- synchronized (mLock) {
- Log.i(this, "Adding new incoming call with phoneAccountHandle %s",
- phoneAccountHandle);
- if (phoneAccountHandle != null && phoneAccountHandle.getComponentName() != null) {
- // TODO(sail): Add unit tests for adding incoming calls from a SIM call manager.
- if (isCallerSimCallManager() && TelephonyUtil.isPstnComponentName(
- phoneAccountHandle.getComponentName())) {
- Log.v(this, "Allowing call manager to add incoming call with PSTN handle");
- } else {
- mAppOpsManager.checkPackage(
- Binder.getCallingUid(),
- phoneAccountHandle.getComponentName().getPackageName());
- // Make sure it doesn't cross the UserHandle boundary
- enforceUserHandleMatchesCaller(phoneAccountHandle);
- }
-
- long token = Binder.clearCallingIdentity();
- try {
- Intent intent = new Intent(TelecomManager.ACTION_INCOMING_CALL);
- intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
+ try {
+ Log.startSession("TSI.aNIC");
+ synchronized (mLock) {
+ Log.i(this, "Adding new incoming call with phoneAccountHandle %s",
phoneAccountHandle);
- intent.putExtra(CallIntentProcessor.KEY_IS_INCOMING_CALL, true);
- if (extras != null) {
- intent.putExtra(TelecomManager.EXTRA_INCOMING_CALL_EXTRAS, extras);
+ if (phoneAccountHandle != null &&
+ phoneAccountHandle.getComponentName() != null) {
+ // TODO(sail): Add unit tests for adding incoming calls from a SIM call
+ // manager.
+ if (isCallerSimCallManager() && TelephonyUtil.isPstnComponentName(
+ phoneAccountHandle.getComponentName())) {
+ Log.v(this, "Allowing call manager to add incoming call with PSTN" +
+ " handle");
+ } else {
+ mAppOpsManager.checkPackage(
+ Binder.getCallingUid(),
+ phoneAccountHandle.getComponentName().getPackageName());
+ // Make sure it doesn't cross the UserHandle boundary
+ enforceUserHandleMatchesCaller(phoneAccountHandle);
}
- CallIntentProcessor.processIncomingCallIntent(mCallsManager, intent);
- } finally {
- Binder.restoreCallingIdentity(token);
+
+ long token = Binder.clearCallingIdentity();
+ try {
+ Intent intent = new Intent(TelecomManager.ACTION_INCOMING_CALL);
+ intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
+ phoneAccountHandle);
+ intent.putExtra(CallIntentProcessor.KEY_IS_INCOMING_CALL, true);
+ if (extras != null) {
+ intent.putExtra(TelecomManager.EXTRA_INCOMING_CALL_EXTRAS, extras);
+ }
+ CallIntentProcessor.processIncomingCallIntent(mCallsManager, intent);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ } else {
+ Log.w(this, "Null phoneAccountHandle. Ignoring request to add new" +
+ " incoming call");
}
- } else {
- Log.w(this,
- "Null phoneAccountHandle. Ignoring request to add new incoming call");
}
+ } finally {
+ Log.endSession();
}
}
@@ -782,31 +956,37 @@
*/
@Override
public void addNewUnknownCall(PhoneAccountHandle phoneAccountHandle, Bundle extras) {
- synchronized (mLock) {
- if (phoneAccountHandle != null && phoneAccountHandle.getComponentName() != null) {
- mAppOpsManager.checkPackage(
- Binder.getCallingUid(),
- phoneAccountHandle.getComponentName().getPackageName());
+ try {
+ Log.startSession("TSI.aNUC");
+ synchronized (mLock) {
+ if (phoneAccountHandle != null &&
+ phoneAccountHandle.getComponentName() != null) {
+ mAppOpsManager.checkPackage(
+ Binder.getCallingUid(),
+ phoneAccountHandle.getComponentName().getPackageName());
- // Make sure it doesn't cross the UserHandle boundary
- enforceUserHandleMatchesCaller(phoneAccountHandle);
- long token = Binder.clearCallingIdentity();
+ // Make sure it doesn't cross the UserHandle boundary
+ enforceUserHandleMatchesCaller(phoneAccountHandle);
+ long token = Binder.clearCallingIdentity();
- try {
- Intent intent = new Intent(TelecomManager.ACTION_NEW_UNKNOWN_CALL);
- intent.putExtras(extras);
- intent.putExtra(CallIntentProcessor.KEY_IS_UNKNOWN_CALL, true);
- intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
- phoneAccountHandle);
- CallIntentProcessor.processUnknownCallIntent(mCallsManager, intent);
- } finally {
- Binder.restoreCallingIdentity(token);
+ try {
+ Intent intent = new Intent(TelecomManager.ACTION_NEW_UNKNOWN_CALL);
+ intent.putExtras(extras);
+ intent.putExtra(CallIntentProcessor.KEY_IS_UNKNOWN_CALL, true);
+ intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
+ phoneAccountHandle);
+ CallIntentProcessor.processUnknownCallIntent(mCallsManager, intent);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ } else {
+ Log.i(this,
+ "Null phoneAccountHandle or not initiated by Telephony. " +
+ "Ignoring request to add new unknown call.");
}
- } else {
- Log.i(this,
- "Null phoneAccountHandle or not initiated by Telephony. " +
- "Ignoring request to add new unknown call.");
}
+ } finally {
+ Log.endSession();
}
}
@@ -815,36 +995,41 @@
*/
@Override
public void placeCall(Uri handle, Bundle extras, String callingPackage) {
- enforceCallingPackage(callingPackage);
- if (!canCallPhone(callingPackage, "placeCall")) {
- throw new SecurityException("Package " + callingPackage
- + " is not allowed to place phone calls");
- }
-
- // Note: we can still get here for the default/system dialer, even if the Phone
- // permission is turned off. This is because the default/system dialer is always
- // allowed to attempt to place a call (regardless of permission state), in case
- // it turns out to be an emergency call. If the permission is denied and the
- // call is being made to a non-emergency number, the call will be denied later on
- // by {@link UserCallIntentProcessor}.
-
- final boolean hasCallAppOp = mAppOpsManager.noteOp(AppOpsManager.OP_CALL_PHONE,
- Binder.getCallingUid(), callingPackage) == AppOpsManager.MODE_ALLOWED;
-
- final boolean hasCallPermission = mContext.checkCallingPermission(CALL_PHONE) ==
- PackageManager.PERMISSION_GRANTED;
-
- synchronized (mLock) {
- final UserHandle userHandle = Binder.getCallingUserHandle();
- long token = Binder.clearCallingIdentity();
- try {
- final Intent intent = new Intent(Intent.ACTION_CALL, handle);
- intent.putExtras(extras);
- new UserCallIntentProcessor(mContext, userHandle).processIntent(intent,
- callingPackage, hasCallAppOp && hasCallPermission);
- } finally {
- Binder.restoreCallingIdentity(token);
+ try {
+ Log.startSession("TSI.pC");
+ enforceCallingPackage(callingPackage);
+ if (!canCallPhone(callingPackage, "placeCall")) {
+ throw new SecurityException("Package " + callingPackage
+ + " is not allowed to place phone calls");
}
+
+ // Note: we can still get here for the default/system dialer, even if the Phone
+ // permission is turned off. This is because the default/system dialer is always
+ // allowed to attempt to place a call (regardless of permission state), in case
+ // it turns out to be an emergency call. If the permission is denied and the
+ // call is being made to a non-emergency number, the call will be denied later on
+ // by {@link UserCallIntentProcessor}.
+
+ final boolean hasCallAppOp = mAppOpsManager.noteOp(AppOpsManager.OP_CALL_PHONE,
+ Binder.getCallingUid(), callingPackage) == AppOpsManager.MODE_ALLOWED;
+
+ final boolean hasCallPermission = mContext.checkCallingPermission(CALL_PHONE) ==
+ PackageManager.PERMISSION_GRANTED;
+
+ synchronized (mLock) {
+ final UserHandle userHandle = Binder.getCallingUserHandle();
+ long token = Binder.clearCallingIdentity();
+ try {
+ final Intent intent = new Intent(Intent.ACTION_CALL, handle);
+ intent.putExtras(extras);
+ new UserCallIntentProcessor(mContext, userHandle).processIntent(intent,
+ callingPackage, hasCallAppOp && hasCallPermission);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ } finally {
+ Log.endSession();
}
}
@@ -853,39 +1038,50 @@
*/
@Override
public boolean enablePhoneAccount(PhoneAccountHandle accountHandle, boolean isEnabled) {
- enforceModifyPermission();
- synchronized (mLock) {
- long token = Binder.clearCallingIdentity();
- try {
- // enable/disable phone account
- return mPhoneAccountRegistrar.enablePhoneAccount(accountHandle, isEnabled);
- } finally {
- Binder.restoreCallingIdentity(token);
+ try {
+ Log.startSession("TSI.ePA");
+ enforceModifyPermission();
+ synchronized (mLock) {
+ long token = Binder.clearCallingIdentity();
+ try {
+ // enable/disable phone account
+ return mPhoneAccountRegistrar.enablePhoneAccount(accountHandle, isEnabled);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
+ } finally {
+ Log.endSession();
}
}
@Override
public boolean setDefaultDialer(String packageName) {
- enforcePermission(MODIFY_PHONE_STATE);
- enforcePermission(WRITE_SECURE_SETTINGS);
- synchronized (mLock) {
- long token = Binder.clearCallingIdentity();
- try {
- final boolean result =
- DefaultDialerManager.setDefaultDialerApplication(mContext, packageName);
- if (result) {
- final Intent intent =
- new Intent(TelecomManager.ACTION_DEFAULT_DIALER_CHANGED);
- intent.putExtra(TelecomManager.EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME,
- packageName);
- mContext.sendBroadcastAsUser(intent,
- new UserHandle(ActivityManager.getCurrentUser()));
+ try {
+ Log.startSession("TSI.sDD");
+ enforcePermission(MODIFY_PHONE_STATE);
+ enforcePermission(WRITE_SECURE_SETTINGS);
+ synchronized (mLock) {
+ long token = Binder.clearCallingIdentity();
+ try {
+ final boolean result =
+ DefaultDialerManager.setDefaultDialerApplication(mContext,
+ packageName);
+ if (result) {
+ final Intent intent =
+ new Intent(TelecomManager.ACTION_DEFAULT_DIALER_CHANGED);
+ intent.putExtra(TelecomManager.EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME,
+ packageName);
+ mContext.sendBroadcastAsUser(intent,
+ new UserHandle(ActivityManager.getCurrentUser()));
+ }
+ return result;
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
- return result;
- } finally {
- Binder.restoreCallingIdentity(token);
}
+ } finally {
+ Log.endSession();
}
}
@@ -918,6 +1114,11 @@
pw.increaseIndent();
mPhoneAccountRegistrar.dump(pw);
pw.decreaseIndent();
+
+ pw.println("Analytics:");
+ pw.increaseIndent();
+ Analytics.dump(pw);
+ pw.decreaseIndent();
}
Log.dumpCallEvents(pw);
@@ -956,63 +1157,9 @@
// Supporting methods for the ITelecomService interface implementation.
//
- private boolean isVisibleToCaller(PhoneAccountHandle accountHandle) {
- if (accountHandle == null) {
- return false;
- }
- return isVisibleToCaller(mPhoneAccountRegistrar.getPhoneAccount(accountHandle));
- }
-
- private boolean isVisibleToCaller(PhoneAccount account) {
- if (account == null) {
- return false;
- }
-
- // If this PhoneAccount has CAPABILITY_MULTI_USER, it should be visible to all users and
- // all profiles. Only Telephony and SIP accounts should have this capability.
- if (account.hasCapabilities(PhoneAccount.CAPABILITY_MULTI_USER)) {
- return true;
- }
-
- UserHandle phoneAccountUserHandle = account.getAccountHandle().getUserHandle();
- if (phoneAccountUserHandle == null) {
- return false;
- }
-
- if (phoneAccountUserHandle.equals(Binder.getCallingUserHandle())) {
- return true;
- }
-
- List<UserHandle> profileUserHandles;
- if (UserHandle.getCallingUserId() == UserHandle.USER_OWNER) {
- profileUserHandles = mUserManager.getUserProfiles();
- } else {
- // Otherwise, it has to be owned by the current caller's profile.
- profileUserHandles = new ArrayList<>(1);
- profileUserHandles.add(Binder.getCallingUserHandle());
- }
-
- return profileUserHandles.contains(phoneAccountUserHandle);
- }
-
- /**
- * Given a list of {@link PhoneAccountHandle}s, filter them to the ones that the calling
- * user can see.
- *
- * @param phoneAccountHandles Unfiltered list of account handles.
- *
- * @return {@link PhoneAccountHandle}s visible to the calling user and its profiles.
- */
- private List<PhoneAccountHandle> filterForAccountsVisibleToCaller(
- List<PhoneAccountHandle> phoneAccountHandles) {
- List<PhoneAccountHandle> profilePhoneAccountHandles =
- new ArrayList<>(phoneAccountHandles.size());
- for (PhoneAccountHandle phoneAccountHandle : phoneAccountHandles) {
- if (isVisibleToCaller(phoneAccountHandle)) {
- profilePhoneAccountHandles.add(phoneAccountHandle);
- }
- }
- return profilePhoneAccountHandles;
+ private boolean isPhoneAccountHandleVisibleToCallingUser(
+ PhoneAccountHandle phoneAccountUserHandle, UserHandle callingUser) {
+ return mPhoneAccountRegistrar.getPhoneAccount(phoneAccountUserHandle, callingUser) != null;
}
private boolean isCallerSystemApp() {
@@ -1038,10 +1185,13 @@
return false;
}
- private void acceptRingingCallInternal() {
+ private void acceptRingingCallInternal(int videoState) {
Call call = mCallsManager.getFirstCallWithState(CallState.RINGING);
if (call != null) {
- call.answer(call.getVideoState());
+ if (videoState == DEFAULT_VIDEO_STATE || !isValidAcceptVideoState(videoState)) {
+ videoState = call.getVideoState();
+ }
+ call.answer(videoState);
}
}
@@ -1131,6 +1281,14 @@
}
}
+ private void enforceCrossUserPermission(int callingUid) {
+ if (callingUid != Process.SYSTEM_UID && callingUid != 0) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, "Must be system or have"
+ + " INTERACT_ACROSS_USERS_FULL permission");
+ }
+ }
+
private void enforceFeature(String feature) {
PackageManager pm = mContext.getPackageManager();
if (!pm.hasSystemFeature(feature)) {
@@ -1177,10 +1335,10 @@
}
private boolean isCallerSimCallManager() {
- PhoneAccountHandle accountHandle = null;
long token = Binder.clearCallingIdentity();
+ PhoneAccountHandle accountHandle = null;
try {
- accountHandle = mPhoneAccountRegistrar.getSimCallManager();
+ accountHandle = mPhoneAccountRegistrar.getSimCallManagerOfCurrentUser();
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -1204,4 +1362,23 @@
private TelephonyManager getTelephonyManager() {
return (TelephonyManager)mContext.getSystemService(Context.TELEPHONY_SERVICE);
}
+
+ /**
+ * Determines if a video state is valid for accepting an incoming call.
+ * For the purpose of accepting a call, states {@link VideoProfile#STATE_AUDIO_ONLY}, and
+ * any combination of {@link VideoProfile#STATE_RX_ENABLED} and
+ * {@link VideoProfile#STATE_TX_ENABLED} are considered valid.
+ *
+ * @param videoState The video state.
+ * @return {@code true} if the video state is valid, {@code false} otherwise.
+ */
+ private boolean isValidAcceptVideoState(int videoState) {
+ // Given a video state input, turn off TX and RX so that we can determine if those were the
+ // only bits set.
+ int remainingState = videoState & ~VideoProfile.STATE_TX_ENABLED;
+ remainingState = remainingState & ~VideoProfile.STATE_RX_ENABLED;
+
+ // If only TX or RX were set (or neither), the video state is valid.
+ return remainingState == 0;
+ }
}
diff --git a/src/com/android/server/telecom/TelecomSystem.java b/src/com/android/server/telecom/TelecomSystem.java
index c3ab0dc..e0c3602 100644
--- a/src/com/android/server/telecom/TelecomSystem.java
+++ b/src/com/android/server/telecom/TelecomSystem.java
@@ -18,6 +18,7 @@
import com.android.internal.annotations.VisibleForTesting;
+import android.Manifest;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -54,6 +55,25 @@
private static final IntentFilter USER_SWITCHED_FILTER =
new IntentFilter(Intent.ACTION_USER_SWITCHED);
+ /** Intent filter for dialer secret codes. */
+ private static final IntentFilter DIALER_SECRET_CODE_FILTER;
+
+ /**
+ * Initializes the dialer secret code intent filter. Setup to handle the various secret codes
+ * which can be dialed (e.g. in format *#*#code#*#*) to trigger various behavior in Telecom.
+ */
+ static {
+ DIALER_SECRET_CODE_FILTER = new IntentFilter(
+ "android.provider.Telephony.SECRET_CODE");
+ DIALER_SECRET_CODE_FILTER.addDataScheme("android_secret_code");
+ DIALER_SECRET_CODE_FILTER
+ .addDataAuthority(DialerCodeReceiver.TELECOM_SECRET_CODE_DEBUG_ON, null);
+ DIALER_SECRET_CODE_FILTER
+ .addDataAuthority(DialerCodeReceiver.TELECOM_SECRET_CODE_DEBUG_OFF, null);
+ DIALER_SECRET_CODE_FILTER
+ .addDataAuthority(DialerCodeReceiver.TELECOM_SECRET_CODE_MARK, null);
+ }
+
private static TelecomSystem INSTANCE = null;
private final SyncRoot mLock = new SyncRoot() { };
@@ -67,6 +87,7 @@
private final TelecomBroadcastIntentProcessor mTelecomBroadcastIntentProcessor;
private final TelecomServiceImpl mTelecomServiceImpl;
private final ContactsAsyncHelper mContactsAsyncHelper;
+ private final DialerCodeReceiver mDialerCodeReceiver;
private final BroadcastReceiver mUserSwitchedReceiver = new BroadcastReceiver() {
@Override
@@ -95,12 +116,18 @@
CallerInfoAsyncQueryFactory callerInfoAsyncQueryFactory,
HeadsetMediaButtonFactory headsetMediaButtonFactory,
ProximitySensorManagerFactory proximitySensorManagerFactory,
- InCallWakeLockControllerFactory inCallWakeLockControllerFactory) {
+ InCallWakeLockControllerFactory inCallWakeLockControllerFactory,
+ CallAudioManager.AudioServiceFactory audioServiceFactory,
+ BluetoothPhoneServiceImpl.BluetoothPhoneServiceImplFactory
+ bluetoothPhoneServiceImplFactory) {
mContext = context.getApplicationContext();
+ Log.setContext(mContext);
mMissedCallNotifier = missedCallNotifier;
mPhoneAccountRegistrar = new PhoneAccountRegistrar(mContext);
mContactsAsyncHelper = new ContactsAsyncHelper(mLock);
+ BluetoothManager bluetoothManager = new BluetoothManager(mContext);
+ WiredHeadsetManager wiredHeadsetManager = new WiredHeadsetManager(mContext);
mCallsManager = new CallsManager(
mContext,
@@ -111,17 +138,26 @@
mPhoneAccountRegistrar,
headsetMediaButtonFactory,
proximitySensorManagerFactory,
- inCallWakeLockControllerFactory);
+ inCallWakeLockControllerFactory,
+ audioServiceFactory,
+ bluetoothManager,
+ wiredHeadsetManager);
mRespondViaSmsManager = new RespondViaSmsManager(mCallsManager, mLock);
mCallsManager.setRespondViaSmsManager(mRespondViaSmsManager);
mContext.registerReceiver(mUserSwitchedReceiver, USER_SWITCHED_FILTER);
- mBluetoothPhoneServiceImpl = new BluetoothPhoneServiceImpl(
+ mBluetoothPhoneServiceImpl = bluetoothPhoneServiceImplFactory.makeBluetoothPhoneServiceImpl(
mContext, mLock, mCallsManager, mPhoneAccountRegistrar);
mCallIntentProcessor = new CallIntentProcessor(mContext, mCallsManager);
mTelecomBroadcastIntentProcessor = new TelecomBroadcastIntentProcessor(
mContext, mCallsManager);
+
+ // Register the receiver for the dialer secret codes, used to enable extended logging.
+ mDialerCodeReceiver = new DialerCodeReceiver(mCallsManager);
+ mContext.registerReceiver(mDialerCodeReceiver, DIALER_SECRET_CODE_FILTER,
+ Manifest.permission.CONTROL_INCALL_EXPERIENCE, null);
+
mTelecomServiceImpl = new TelecomServiceImpl(
mContext, mCallsManager, mPhoneAccountRegistrar, mLock);
}
diff --git a/src/com/android/server/telecom/TelephonyUtil.java b/src/com/android/server/telecom/TelephonyUtil.java
index 5f43ee9..69adaf9 100644
--- a/src/com/android/server/telecom/TelephonyUtil.java
+++ b/src/com/android/server/telecom/TelephonyUtil.java
@@ -23,6 +23,8 @@
import android.telecom.PhoneAccountHandle;
import android.telephony.PhoneNumberUtils;
+import com.android.internal.annotations.VisibleForTesting;
+
/**
* Utilities to deal with the system telephony services. The system telephony services are treated
* differently from 3rd party services in some situations (emergency calls, audio focus, etc...).
@@ -45,7 +47,8 @@
* account are not expected to be displayed in the UI, so the description, etc are not
* populated.
*/
- static PhoneAccount getDefaultEmergencyPhoneAccount() {
+ @VisibleForTesting
+ public static PhoneAccount getDefaultEmergencyPhoneAccount() {
return PhoneAccount.builder(DEFAULT_EMERGENCY_PHONE_ACCOUNT_HANDLE, "E")
.setCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION |
PhoneAccount.CAPABILITY_CALL_PROVIDER |
diff --git a/src/com/android/server/telecom/Timeouts.java b/src/com/android/server/telecom/Timeouts.java
index f047971..ba16990 100644
--- a/src/com/android/server/telecom/Timeouts.java
+++ b/src/com/android/server/telecom/Timeouts.java
@@ -98,4 +98,21 @@
return get(contentResolver, "call_remove_unbind_in_call_services_delay",
2000L /* 2 seconds */);
}
+
+ /**
+ * Returns the amount of time for which bluetooth is considered connected after requesting
+ * connection. This compensates for the amount of time it takes for the audio route to
+ * actually change to bluetooth.
+ */
+ public static long getBluetoothPendingTimeoutMillis(ContentResolver contentResolver) {
+ return get(contentResolver, "bluetooth_pending_timeout_millis", 5000L);
+ }
+
+ /**
+ * Returns the amount of time after a Logging session has been started that Telecom is set to
+ * perform a sweep to check and make sure that the session is still not incomplete (stale).
+ */
+ public static long getStaleSessionCleanupTimeoutMillis(ContentResolver contentResolver) {
+ return get(contentResolver, "stale_session_cleanup_timeout_millis", 30000L);
+ }
}
diff --git a/src/com/android/server/telecom/UserUtil.java b/src/com/android/server/telecom/UserUtil.java
new file mode 100644
index 0000000..a35a61e
--- /dev/null
+++ b/src/com/android/server/telecom/UserUtil.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2014 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.server.telecom;
+
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+public final class UserUtil {
+
+ private UserUtil() {
+ }
+
+ private static UserInfo getUserInfoFromUserHandle(Context context, UserHandle userHandle) {
+ UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
+ return userManager.getUserInfo(userHandle.getIdentifier());
+ }
+
+ public static boolean isManagedProfile(Context context, UserHandle userHandle) {
+ UserInfo userInfo = getUserInfoFromUserHandle(context, userHandle);
+ return userInfo != null && userInfo.isManagedProfile();
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/server/telecom/VideoProviderProxy.java b/src/com/android/server/telecom/VideoProviderProxy.java
index 7dcfdfb..b531242 100644
--- a/src/com/android/server/telecom/VideoProviderProxy.java
+++ b/src/com/android/server/telecom/VideoProviderProxy.java
@@ -206,6 +206,9 @@
* Proxies a request from the {@link #mConectionServiceVideoProvider} to the
* {@link InCallService} when the call data usage changes.
*
+ * Also tracks the current call data usage on the {@link Call} for use when writing to the
+ * call log.
+ *
* @param dataUsage The data usage.
*/
@Override
@@ -213,6 +216,7 @@
synchronized (mLock) {
logFromVideoProvider("changeCallDataUsage: " + dataUsage);
VideoProviderProxy.this.setCallDataUsage(dataUsage);
+ mCall.setCallDataUsage(dataUsage);
}
}
diff --git a/src/com/android/server/telecom/WiredHeadsetManager.java b/src/com/android/server/telecom/WiredHeadsetManager.java
index ef5f38c..f25e928 100644
--- a/src/com/android/server/telecom/WiredHeadsetManager.java
+++ b/src/com/android/server/telecom/WiredHeadsetManager.java
@@ -22,6 +22,7 @@
import android.content.IntentFilter;
import android.media.AudioManager;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
import java.util.Collections;
@@ -29,8 +30,10 @@
import java.util.concurrent.ConcurrentHashMap;
/** Listens for and caches headset state. */
-class WiredHeadsetManager {
- interface Listener {
+@VisibleForTesting
+public class WiredHeadsetManager {
+ @VisibleForTesting
+ public interface Listener {
void onWiredHeadsetPluggedInChanged(boolean oldIsPluggedIn, boolean newIsPluggedIn);
}
@@ -58,7 +61,7 @@
private final Set<Listener> mListeners = Collections.newSetFromMap(
new ConcurrentHashMap<Listener, Boolean>(8, 0.9f, 1));
- WiredHeadsetManager(Context context) {
+ public WiredHeadsetManager(Context context) {
mReceiver = new WiredHeadsetBroadcastReceiver();
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
@@ -69,7 +72,8 @@
context.registerReceiver(mReceiver, intentFilter);
}
- void addListener(Listener listener) {
+ @VisibleForTesting
+ public void addListener(Listener listener) {
mListeners.add(listener);
}
@@ -79,7 +83,8 @@
}
}
- boolean isPluggedIn() {
+ @VisibleForTesting
+ public boolean isPluggedIn() {
return mIsPluggedIn;
}
diff --git a/src/com/android/server/telecom/components/PrimaryCallReceiver.java b/src/com/android/server/telecom/components/PrimaryCallReceiver.java
index 5198862..a05f04e 100644
--- a/src/com/android/server/telecom/components/PrimaryCallReceiver.java
+++ b/src/com/android/server/telecom/components/PrimaryCallReceiver.java
@@ -1,5 +1,6 @@
package com.android.server.telecom.components;
+import com.android.server.telecom.Log;
import com.android.server.telecom.TelecomSystem;
import android.content.BroadcastReceiver;
@@ -16,9 +17,11 @@
@Override
public void onReceive(Context context, Intent intent) {
+ Log.startSession("PCR.oR");
synchronized (getTelecomSystem().getLock()) {
getTelecomSystem().getCallIntentProcessor().processIntent(intent);
}
+ Log.endSession();
}
@Override
diff --git a/src/com/android/server/telecom/components/TelecomService.java b/src/com/android/server/telecom/components/TelecomService.java
index 7856094..3d24aa2 100644
--- a/src/com/android/server/telecom/components/TelecomService.java
+++ b/src/com/android/server/telecom/components/TelecomService.java
@@ -20,14 +20,19 @@
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.content.Intent;
+import android.media.IAudioService;
import android.os.IBinder;
+import android.os.ServiceManager;
import com.android.internal.telephony.CallerInfoAsyncQuery;
+import com.android.server.telecom.BluetoothPhoneServiceImpl;
+import com.android.server.telecom.CallAudioManager;
import com.android.server.telecom.CallerInfoAsyncQueryFactory;
import com.android.server.telecom.CallsManager;
import com.android.server.telecom.HeadsetMediaButton;
import com.android.server.telecom.HeadsetMediaButtonFactory;
import com.android.server.telecom.InCallWakeLockControllerFactory;
+import com.android.server.telecom.PhoneAccountRegistrar;
import com.android.server.telecom.ProximitySensorManagerFactory;
import com.android.server.telecom.InCallWakeLockController;
import com.android.server.telecom.Log;
@@ -101,7 +106,25 @@
CallsManager callsManager) {
return new InCallWakeLockController(context, callsManager);
}
- }));
+ },
+ new CallAudioManager.AudioServiceFactory() {
+ @Override
+ public IAudioService getAudioService() {
+ return IAudioService.Stub.asInterface(
+ ServiceManager.getService(Context.AUDIO_SERVICE));
+ }
+ },
+ new BluetoothPhoneServiceImpl.BluetoothPhoneServiceImplFactory() {
+ @Override
+ public BluetoothPhoneServiceImpl makeBluetoothPhoneServiceImpl(
+ Context context, TelecomSystem.SyncRoot lock,
+ CallsManager callsManager,
+ PhoneAccountRegistrar phoneAccountRegistrar) {
+ return new BluetoothPhoneServiceImpl(context, lock,
+ callsManager, phoneAccountRegistrar);
+ }
+ }
+ ));
}
if (BluetoothAdapter.getDefaultAdapter() != null) {
context.startService(new Intent(context, BluetoothPhoneService.class));
diff --git a/src/com/android/server/telecom/components/UserCallActivity.java b/src/com/android/server/telecom/components/UserCallActivity.java
index d88e09e..39cb4f9 100644
--- a/src/com/android/server/telecom/components/UserCallActivity.java
+++ b/src/com/android/server/telecom/components/UserCallActivity.java
@@ -52,18 +52,23 @@
@Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
- // TODO: Figure out if there is something to restore from bundle.
- // See OutgoingCallBroadcaster in services/Telephony for more.
- Intent intent = getIntent();
- verifyCallAction(intent);
- final UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
- final UserHandle userHandle = new UserHandle(userManager.getUserHandle());
- // Once control flow has passed to this activity, it is no longer guaranteed that we can
- // accurately determine whether the calling package has the CALL_PHONE runtime permission.
- // At this point in time we trust that the ActivityManager has already performed this
- // validation before starting this activity.
- new UserCallIntentProcessor(this, userHandle).processIntent(getIntent(),
- getCallingPackage(), true /* hasCallAppOp*/);
+ Log.startSession("UCA.oC");
+ try {
+ // TODO: Figure out if there is something to restore from bundle.
+ // See OutgoingCallBroadcaster in services/Telephony for more.
+ Intent intent = getIntent();
+ verifyCallAction(intent);
+ final UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
+ final UserHandle userHandle = new UserHandle(userManager.getUserHandle());
+ // Once control flow has passed to this activity, it is no longer guaranteed that we can
+ // accurately determine whether the calling package has the CALL_PHONE runtime permission.
+ // At this point in time we trust that the ActivityManager has already performed this
+ // validation before starting this activity.
+ new UserCallIntentProcessor(this, userHandle).processIntent(getIntent(),
+ getCallingPackage(), true /* hasCallAppOp*/);
+ } finally {
+ Log.endSession();
+ }
finish();
}
diff --git a/src/com/android/server/telecom/components/UserCallIntentProcessor.java b/src/com/android/server/telecom/components/UserCallIntentProcessor.java
index 8f451b5..a73a33e 100644
--- a/src/com/android/server/telecom/components/UserCallIntentProcessor.java
+++ b/src/com/android/server/telecom/components/UserCallIntentProcessor.java
@@ -123,15 +123,9 @@
VideoProfile.STATE_AUDIO_ONLY);
Log.d(this, "processOutgoingCallIntent videoState = " + videoState);
- if (VideoProfile.isVideo(videoState)
- && TelephonyUtil.shouldProcessAsEmergency(mContext, handle)) {
- Log.d(this, "Emergency call...Converting video call to voice...");
- videoState = VideoProfile.STATE_AUDIO_ONLY;
- intent.putExtra(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
- videoState);
- }
+ if (VideoProfile.isVideo(videoState) && isTtyModeEnabled() &&
+ !TelephonyUtil.shouldProcessAsEmergency(mContext, handle)) {
- if (VideoProfile.isVideo(videoState) && isTtyModeEnabled()) {
Toast.makeText(mContext, mContext.getResources().getString(R.string.
video_call_not_allowed_if_tty_enabled), Toast.LENGTH_SHORT).show();
Log.d(this, "Rejecting video calls as tty is enabled");
@@ -140,6 +134,10 @@
intent.putExtra(CallIntentProcessor.KEY_IS_PRIVILEGED_DIALER,
isDefaultOrSystemDialer(callingPackageName));
+
+ // Save the user handle of current user before forwarding the intent to primary user.
+ intent.putExtra(CallIntentProcessor.KEY_INITIATING_USER, mUserHandle);
+
sendBroadcastToReceiver(intent);
}
@@ -184,7 +182,7 @@
intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
intent.setClass(mContext, PrimaryCallReceiver.class);
Log.d(this, "Sending broadcast as user to CallReceiver");
- mContext.sendBroadcastAsUser(intent, UserHandle.OWNER);
+ mContext.sendBroadcastAsUser(intent, UserHandle.SYSTEM);
return true;
}
diff --git a/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java b/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java
index a864a83..b650400 100644
--- a/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java
+++ b/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java
@@ -18,6 +18,7 @@
import android.content.ComponentName;
import android.telecom.TelecomManager;
+
import com.android.server.telecom.Call;
import com.android.server.telecom.CallState;
import com.android.server.telecom.CallerInfoAsyncQueryFactory;
@@ -75,6 +76,19 @@
*/
public class MissedCallNotifierImpl extends CallsManagerListenerBase implements MissedCallNotifier {
+ public interface NotificationBuilderFactory {
+ Notification.Builder getBuilder(Context context);
+ }
+
+ private static class DefaultNotificationBuilderFactory implements NotificationBuilderFactory {
+ public DefaultNotificationBuilderFactory() {}
+
+ @Override
+ public Notification.Builder getBuilder(Context context) {
+ return new Notification.Builder(context);
+ }
+ }
+
private static final String[] CALL_LOG_PROJECTION = new String[] {
Calls._ID,
Calls.NUMBER,
@@ -95,6 +109,7 @@
private final Context mContext;
private final NotificationManager mNotificationManager;
+ private final NotificationBuilderFactory mNotificationBuilderFactory;
private final ComponentName mNotificationComponent;
@@ -102,11 +117,17 @@
private int mMissedCallCount = 0;
public MissedCallNotifierImpl(Context context) {
+ this(context, new DefaultNotificationBuilderFactory());
+ }
+
+ public MissedCallNotifierImpl(Context context,
+ NotificationBuilderFactory notificationBuilderFactory) {
mContext = context;
mNotificationManager =
(NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
final String notificationComponent = context.getString(R.string.notification_component);
+ mNotificationBuilderFactory = notificationBuilderFactory;
mNotificationComponent = notificationComponent != null
? ComponentName.unflattenFromString(notificationComponent) : null;
}
@@ -149,8 +170,6 @@
/**
* Broadcasts missed call notification to custom component if set.
- * @param number The phone number associated with the notification. null if
- * no call.
* @param count The number of calls associated with the notification.
* @return {@code true} if the broadcast was sent. {@code false} otherwise.
*/
@@ -215,7 +234,7 @@
// Create a public viewable version of the notification, suitable for display when sensitive
// notification content is hidden.
- Notification.Builder publicBuilder = new Notification.Builder(mContext);
+ Notification.Builder publicBuilder = mNotificationBuilderFactory.getBuilder(mContext);
publicBuilder.setSmallIcon(android.R.drawable.stat_notify_missed_call)
.setColor(mContext.getResources().getColor(R.color.theme_color))
.setWhen(call.getCreationTimeMillis())
@@ -229,7 +248,7 @@
.setDeleteIntent(createClearMissedCallsPendingIntent());
// Create the notification suitable for display when sensitive information is showing.
- Notification.Builder builder = new Notification.Builder(mContext);
+ Notification.Builder builder = mNotificationBuilderFactory.getBuilder(mContext);
builder.setSmallIcon(android.R.drawable.stat_notify_missed_call)
.setColor(mContext.getResources().getColor(R.color.theme_color))
.setWhen(call.getCreationTimeMillis())
@@ -470,9 +489,10 @@
synchronized (lock) {
// Convert the data to a call object
- Call call = new Call(mContext, callsManager, lock,
- null, contactsAsyncHelper, callerInfoAsyncQueryFactory,
- null, null, null, null, true, false);
+ Call call = new Call(Call.CALL_ID_UNKNOWN, mContext, callsManager,
+ lock, null, contactsAsyncHelper,
+ callerInfoAsyncQueryFactory, null, null, null, null,
+ Call.CALL_DIRECTION_INCOMING, false, false);
call.setDisconnectCause(
new DisconnectCause(DisconnectCause.MISSED));
call.setState(CallState.DISCONNECTED, "throw away call");
diff --git a/testapps/AndroidManifest.xml b/testapps/AndroidManifest.xml
index df333a5..0672b6d 100644
--- a/testapps/AndroidManifest.xml
+++ b/testapps/AndroidManifest.xml
@@ -18,6 +18,10 @@
coreApp="true"
package="com.android.server.telecom.testapps">
+ <uses-sdk
+ android:minSdkVersion="23"
+ android:targetSdkVersion="23" />
+
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.CALL_PHONE" />
<uses-permission android:name="android.permission.CONTROL_INCALL_EXPERIENCE" />
diff --git a/testapps/src/com/android/server/telecom/testapps/CallServiceNotifier.java b/testapps/src/com/android/server/telecom/testapps/CallServiceNotifier.java
index c1ced80..28c43c8 100644
--- a/testapps/src/com/android/server/telecom/testapps/CallServiceNotifier.java
+++ b/testapps/src/com/android/server/telecom/testapps/CallServiceNotifier.java
@@ -29,6 +29,7 @@
import android.graphics.Color;
import android.graphics.drawable.Icon;
import android.net.Uri;
+import android.os.Bundle;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
@@ -103,6 +104,14 @@
telecomManager.clearAccounts();
+ Bundle testBundle = new Bundle();
+ testBundle.putInt("EXTRA_INT_1", 1);
+ testBundle.putInt("EXTRA_INT_100", 100);
+ testBundle.putBoolean("EXTRA_BOOL_TRUE", true);
+ testBundle.putBoolean("EXTRA_BOOL_FALSE", false);
+ testBundle.putString("EXTRA_STR1", "Hello");
+ testBundle.putString("EXTRA_STR2", "There");
+
telecomManager.registerPhoneAccount(PhoneAccount.builder(
new PhoneAccountHandle(
new ComponentName(context, TestConnectionService.class),
@@ -115,10 +124,11 @@
PhoneAccount.CAPABILITY_CALL_SUBJECT)
.setIcon(Icon.createWithResource(
context.getResources(), R.drawable.stat_sys_phone_call))
- // TODO: Add icon tint (Color.RED)
.setHighlightColor(Color.RED)
+ // TODO: Add icon tint (Color.RED)
.setShortDescription("a short description for the call provider")
.setSupportedUriSchemes(Arrays.asList("tel"))
+ .setExtras(testBundle)
.build());
telecomManager.registerPhoneAccount(PhoneAccount.builder(
@@ -134,8 +144,8 @@
PhoneAccount.CAPABILITY_CALL_SUBJECT)
.setIcon(Icon.createWithResource(
context.getResources(), R.drawable.stat_sys_phone_call))
- // TODO: Add icon tint (Color.GREEN)
.setHighlightColor(Color.GREEN)
+ // TODO: Add icon tint (Color.GREEN)
.setShortDescription("a short description for the sim subscription")
.build());
diff --git a/tests/Android.mk b/tests/Android.mk
index a802768..317bfe3 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -39,6 +39,9 @@
--auto-add-overlay \
--extra-packages com.android.server.telecom
+LOCAL_JACK_ENABLED := disabled
+LOCAL_PROGUARD_ENABLED := disabled
+
LOCAL_PACKAGE_NAME := TelecomUnitTests
LOCAL_CERTIFICATE := platform
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index 6a08c63..4576690 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -19,6 +19,10 @@
package="com.android.server.telecom.tests"
android:debuggable="true">
+ <uses-sdk
+ android:minSdkVersion="23"
+ android:targetSdkVersion="23" />
+
<!-- TODO: Needed because we call BluetoothAdapter.getDefaultAdapter() statically, and
BluetoothAdapter is a final class. -->
<uses-permission android:name="android.permission.BLUETOOTH" />
@@ -27,6 +31,9 @@
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
<uses-permission android:name="android.permission.MANAGE_USERS" />
+ <!-- Used to access TelephonyManager APIs -->
+ <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
+
<application android:label="@string/app_name"
android:debuggable="true">
<uses-library android:name="android.test.runner" />
diff --git a/tests/src/com/android/server/telecom/tests/BluetoothPhoneServiceTest.java b/tests/src/com/android/server/telecom/tests/BluetoothPhoneServiceTest.java
new file mode 100644
index 0000000..74a2d64
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/BluetoothPhoneServiceTest.java
@@ -0,0 +1,886 @@
+/*
+ * 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.server.telecom.tests;
+
+import android.bluetooth.BluetoothAdapter;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Debug;
+import android.telecom.Connection;
+import android.telecom.GatewayInfo;
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
+import android.telecom.TelecomManager;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.TelephonyManager;
+
+import com.android.server.telecom.BluetoothHeadsetProxy;
+import com.android.server.telecom.BluetoothPhoneServiceImpl;
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallState;
+import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.PhoneAccountRegistrar;
+import com.android.server.telecom.TelecomSystem;
+
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.anyChar;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isNull;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.verify;
+
+public class BluetoothPhoneServiceTest extends TelecomTestCase {
+
+ private static final int TEST_DTMF_TONE = 0;
+ private static final String TEST_ACCOUNT_ADDRESS = "//foo.com/";
+ private static final int TEST_ACCOUNT_INDEX = 0;
+
+ // match up with BluetoothPhoneServiceImpl
+ private static final int CALL_STATE_ACTIVE = 0;
+ private static final int CALL_STATE_HELD = 1;
+ private static final int CALL_STATE_DIALING = 2;
+ private static final int CALL_STATE_ALERTING = 3;
+ private static final int CALL_STATE_INCOMING = 4;
+ private static final int CALL_STATE_WAITING = 5;
+ private static final int CALL_STATE_IDLE = 6;
+ // Terminate all held or set UDUB("busy") to a waiting call
+ private static final int CHLD_TYPE_RELEASEHELD = 0;
+ // Terminate all active calls and accepts a waiting/held call
+ private static final int CHLD_TYPE_RELEASEACTIVE_ACCEPTHELD = 1;
+ // Hold all active calls and accepts a waiting/held call
+ private static final int CHLD_TYPE_HOLDACTIVE_ACCEPTHELD = 2;
+ // Add all held calls to a conference
+ private static final int CHLD_TYPE_ADDHELDTOCONF = 3;
+
+ private BluetoothPhoneServiceImpl mBluetoothPhoneService;
+ private final TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() {
+ };
+
+ @Mock CallsManager mMockCallsManager;
+ @Mock PhoneAccountRegistrar mMockPhoneAccountRegistrar;
+ @Mock BluetoothHeadsetProxy mMockBluetoothHeadset;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ MockitoAnnotations.initMocks(this);
+ mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
+
+ // Ensure initialization does not actually try to access any of the CallsManager fields.
+ // This also works to return null if it is not overwritten later in the test.
+ doNothing().when(mMockCallsManager).addListener(any(
+ CallsManager.CallsManagerListener.class));
+ doReturn(null).when(mMockCallsManager).getActiveCall();
+ doReturn(null).when(mMockCallsManager).getRingingCall();
+ doReturn(null).when(mMockCallsManager).getHeldCall();
+ doReturn(null).when(mMockCallsManager).getOutgoingCall();
+ doReturn(0).when(mMockCallsManager).getNumHeldCalls();
+ mBluetoothPhoneService = new BluetoothPhoneServiceImpl(mContext, mLock, mMockCallsManager,
+ mMockPhoneAccountRegistrar);
+
+ // Bring in test Bluetooth Headset
+ mBluetoothPhoneService.setBluetoothHeadset(mMockBluetoothHeadset);
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+
+ mBluetoothPhoneService = null;
+ super.tearDown();
+ }
+
+ public void testHeadsetAnswerCall() throws Exception {
+ Call mockCall = createRingingCall();
+
+ boolean callAnswered = mBluetoothPhoneService.mBinder.answerCall();
+
+ verify(mMockCallsManager).answerCall(eq(mockCall), any(int.class));
+ assertEquals(callAnswered, true);
+ }
+
+ public void testHeadsetAnswerCallNull() throws Exception {
+ when(mMockCallsManager.getRingingCall()).thenReturn(null);
+
+ boolean callAnswered = mBluetoothPhoneService.mBinder.answerCall();
+
+ verify(mMockCallsManager,never()).answerCall(any(Call.class), any(int.class));
+ assertEquals(callAnswered, false);
+ }
+
+ public void testHeadsetHangupCall() throws Exception {
+ Call mockCall = createForegroundCall();
+
+ boolean callHungup = mBluetoothPhoneService.mBinder.hangupCall();
+
+ verify(mMockCallsManager).disconnectCall(eq(mockCall));
+ assertEquals(callHungup, true);
+ }
+
+ public void testHeadsetHangupCallNull() throws Exception {
+ when(mMockCallsManager.getForegroundCall()).thenReturn(null);
+
+ boolean callHungup = mBluetoothPhoneService.mBinder.hangupCall();
+
+ verify(mMockCallsManager,never()).disconnectCall(any(Call.class));
+ assertEquals(callHungup, false);
+ }
+
+ public void testHeadsetSendDTMF() throws Exception {
+ Call mockCall = createForegroundCall();
+
+ boolean sentDtmf = mBluetoothPhoneService.mBinder.sendDtmf(TEST_DTMF_TONE);
+
+ verify(mMockCallsManager).playDtmfTone(eq(mockCall), eq((char) TEST_DTMF_TONE));
+ verify(mMockCallsManager).stopDtmfTone(eq(mockCall));
+ assertEquals(sentDtmf, true);
+ }
+
+ public void testHeadsetSendDTMFNull() throws Exception {
+ when(mMockCallsManager.getForegroundCall()).thenReturn(null);
+
+ boolean sentDtmf = mBluetoothPhoneService.mBinder.sendDtmf(TEST_DTMF_TONE);
+
+ verify(mMockCallsManager,never()).playDtmfTone(any(Call.class), anyChar());
+ verify(mMockCallsManager,never()).stopDtmfTone(any(Call.class));
+ assertEquals(sentDtmf, false);
+ }
+
+ public void testGetNetworkOperator() throws Exception {
+ Call mockCall = createForegroundCall();
+ PhoneAccount fakePhoneAccount = makeQuickAccount("id0", TEST_ACCOUNT_INDEX);
+ when(mMockPhoneAccountRegistrar.getPhoneAccountOfCurrentUser(
+ any(PhoneAccountHandle.class))).thenReturn(fakePhoneAccount);
+
+ String networkOperator = mBluetoothPhoneService.mBinder.getNetworkOperator();
+
+ assertEquals(networkOperator, "label0");
+ }
+
+ public void testGetNetworkOperatorNoPhoneAccount() throws Exception {
+ when(mMockCallsManager.getForegroundCall()).thenReturn(null);
+
+ String networkOperator = mBluetoothPhoneService.mBinder.getNetworkOperator();
+
+ assertEquals(networkOperator, "label1");
+ }
+
+ public void testGetSubscriberNumber() throws Exception {
+ Call mockCall = createForegroundCall();
+ PhoneAccount fakePhoneAccount = makeQuickAccount("id0", TEST_ACCOUNT_INDEX);
+ when(mMockPhoneAccountRegistrar.getPhoneAccountOfCurrentUser(
+ any(PhoneAccountHandle.class))).thenReturn(fakePhoneAccount);
+
+ String subscriberNumber = mBluetoothPhoneService.mBinder.getSubscriberNumber();
+
+ assertEquals(subscriberNumber, TEST_ACCOUNT_ADDRESS + TEST_ACCOUNT_INDEX);
+ }
+
+ public void testGetSubscriberNumberFallbackToTelephony() throws Exception {
+ Call mockCall = createForegroundCall();
+ String fakeNumber = "8675309";
+ when(mMockPhoneAccountRegistrar.getPhoneAccountOfCurrentUser(
+ any(PhoneAccountHandle.class))).thenReturn(null);
+ when(mMockPhoneAccountRegistrar.getPhoneAccountUnchecked(
+ any(PhoneAccountHandle.class))).thenReturn(null);
+ when(TelephonyManager.from(mContext).getLine1Number()).thenReturn(fakeNumber);
+
+ String subscriberNumber = mBluetoothPhoneService.mBinder.getSubscriberNumber();
+
+ assertEquals(subscriberNumber, fakeNumber);
+ }
+
+ public void testListCurrentCallsOneCall() throws Exception {
+ ArrayList<Call> calls = new ArrayList<>();
+ Call activeCall = createActiveCall();
+ when(activeCall.getState()).thenReturn(CallState.ACTIVE);
+ calls.add(activeCall);
+ when(activeCall.isConference()).thenReturn(false);
+ when(activeCall.getHandle()).thenReturn(Uri.parse("tel:555-000"));
+ when(mMockCallsManager.getCalls()).thenReturn(calls);
+
+ mBluetoothPhoneService.mBinder.listCurrentCalls();
+
+ verify(mMockBluetoothHeadset).clccResponse(eq(1), eq(0), eq(0), eq(0), eq(false),
+ eq("555-000"), eq(PhoneNumberUtils.TOA_Unknown));
+ verify(mMockBluetoothHeadset).clccResponse(0, 0, 0, 0, false, null, 0);
+ }
+
+ public void testConferenceInProgressCDMA() throws Exception {
+ // If two calls are being conferenced and updateHeadsetWithCallState runs while this is
+ // still occuring, it will look like there is an active and held call still while we are
+ // transitioning into a conference.
+ // Call has been put into a CDMA "conference" with one call on hold.
+ ArrayList<Call> calls = new ArrayList<>();
+ Call parentCall = createActiveCall();
+ final Call confCall1 = mock(Call.class);
+ final Call confCall2 = createHeldCall();
+ calls.add(parentCall);
+ calls.add(confCall1);
+ calls.add(confCall2);
+ when(mMockCallsManager.getCalls()).thenReturn(calls);
+ when(confCall1.getState()).thenReturn(CallState.ACTIVE);
+ when(confCall2.getState()).thenReturn(CallState.ACTIVE);
+ when(confCall1.isIncoming()).thenReturn(false);
+ when(confCall2.isIncoming()).thenReturn(true);
+ when(confCall1.getGatewayInfo()).thenReturn(new GatewayInfo(null, null,
+ Uri.parse("tel:555-0000")));
+ when(confCall2.getGatewayInfo()).thenReturn(new GatewayInfo(null, null,
+ Uri.parse("tel:555-0001")));
+ addCallCapability(parentCall, Connection.CAPABILITY_MERGE_CONFERENCE);
+ addCallCapability(parentCall, Connection.CAPABILITY_SWAP_CONFERENCE);
+ removeCallCapability(parentCall, Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN);
+ when(parentCall.getConferenceLevelActiveCall()).thenReturn(confCall1);
+ when(parentCall.isConference()).thenReturn(true);
+ when(parentCall.getChildCalls()).thenReturn(new LinkedList<Call>() {{
+ add(confCall1);
+ add(confCall2);
+ }});
+ //Add links from child calls to parent
+ when(confCall1.getParentCall()).thenReturn(parentCall);
+ when(confCall2.getParentCall()).thenReturn(parentCall);
+
+ mBluetoothPhoneService.mBinder.queryPhoneState();
+ verify(mMockBluetoothHeadset).phoneStateChanged(eq(1), eq(1), eq(CALL_STATE_IDLE),
+ eq(""), eq(128));
+ when(parentCall.wasConferencePreviouslyMerged()).thenReturn(true);
+ mBluetoothPhoneService.mCallsManagerListener.onIsConferencedChanged(parentCall);
+ verify(mMockBluetoothHeadset).phoneStateChanged(eq(1), eq(0), eq(CALL_STATE_IDLE),
+ eq(""), eq(128));
+ when(mMockCallsManager.getHeldCall()).thenReturn(null);
+ // Spurious call to onIsConferencedChanged.
+ mBluetoothPhoneService.mCallsManagerListener.onIsConferencedChanged(parentCall);
+ // Make sure the call has only occurred collectively 2 times (not on the third)
+ verify(mMockBluetoothHeadset, times(2)).phoneStateChanged(any(int.class),
+ any(int.class), any(int.class), any(String.class), any(int.class));
+ }
+
+ public void testListCurrentCallsCdmaHold() throws Exception {
+ // Call has been put into a CDMA "conference" with one call on hold.
+ ArrayList<Call> calls = new ArrayList<>();
+ Call parentCall = createActiveCall();
+ final Call foregroundCall = mock(Call.class);
+ final Call heldCall = createHeldCall();
+ calls.add(parentCall);
+ calls.add(foregroundCall);
+ calls.add(heldCall);
+ when(mMockCallsManager.getCalls()).thenReturn(calls);
+ when(foregroundCall.getState()).thenReturn(CallState.ACTIVE);
+ when(heldCall.getState()).thenReturn(CallState.ACTIVE);
+ when(foregroundCall.isIncoming()).thenReturn(false);
+ when(heldCall.isIncoming()).thenReturn(true);
+ when(foregroundCall.getGatewayInfo()).thenReturn(new GatewayInfo(null, null,
+ Uri.parse("tel:555-0000")));
+ when(heldCall.getGatewayInfo()).thenReturn(new GatewayInfo(null, null,
+ Uri.parse("tel:555-0001")));
+ addCallCapability(parentCall, Connection.CAPABILITY_MERGE_CONFERENCE);
+ addCallCapability(parentCall, Connection.CAPABILITY_SWAP_CONFERENCE);
+ removeCallCapability(parentCall, Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN);
+ when(parentCall.getConferenceLevelActiveCall()).thenReturn(foregroundCall);
+ when(parentCall.isConference()).thenReturn(true);
+ when(parentCall.getChildCalls()).thenReturn(new LinkedList<Call>() {{
+ add(foregroundCall);
+ add(heldCall);
+ }});
+ //Add links from child calls to parent
+ when(foregroundCall.getParentCall()).thenReturn(parentCall);
+ when(heldCall.getParentCall()).thenReturn(parentCall);
+
+ mBluetoothPhoneService.mBinder.listCurrentCalls();
+
+ verify(mMockBluetoothHeadset).clccResponse(eq(1), eq(0), eq(CALL_STATE_ACTIVE), eq(0),
+ eq(false), eq("555-0000"), eq(PhoneNumberUtils.TOA_Unknown));
+ verify(mMockBluetoothHeadset).clccResponse(eq(2), eq(1), eq(CALL_STATE_HELD), eq(0),
+ eq(false), eq("555-0001"), eq(PhoneNumberUtils.TOA_Unknown));
+ verify(mMockBluetoothHeadset).clccResponse(0, 0, 0, 0, false, null, 0);
+ }
+
+ public void testListCurrentCallsCdmaConference() throws Exception {
+ // Call is in a true CDMA conference
+ ArrayList<Call> calls = new ArrayList<>();
+ Call parentCall = createActiveCall();
+ final Call confCall1 = mock(Call.class);
+ final Call confCall2 = createHeldCall();
+ calls.add(parentCall);
+ calls.add(confCall1);
+ calls.add(confCall2);
+ when(mMockCallsManager.getCalls()).thenReturn(calls);
+ when(confCall1.getState()).thenReturn(CallState.ACTIVE);
+ when(confCall2.getState()).thenReturn(CallState.ACTIVE);
+ when(confCall1.isIncoming()).thenReturn(false);
+ when(confCall2.isIncoming()).thenReturn(true);
+ when(confCall1.getGatewayInfo()).thenReturn(new GatewayInfo(null, null,
+ Uri.parse("tel:555-0000")));
+ when(confCall2.getGatewayInfo()).thenReturn(new GatewayInfo(null, null,
+ Uri.parse("tel:555-0001")));
+ removeCallCapability(parentCall, Connection.CAPABILITY_MERGE_CONFERENCE);
+ removeCallCapability(parentCall, Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN);
+ when(parentCall.wasConferencePreviouslyMerged()).thenReturn(true);
+ when(parentCall.getConferenceLevelActiveCall()).thenReturn(confCall1);
+ when(parentCall.isConference()).thenReturn(true);
+ when(parentCall.getChildCalls()).thenReturn(new LinkedList<Call>() {{
+ add(confCall1);
+ add(confCall2);
+ }});
+ //Add links from child calls to parent
+ when(confCall1.getParentCall()).thenReturn(parentCall);
+ when(confCall2.getParentCall()).thenReturn(parentCall);
+
+ mBluetoothPhoneService.mBinder.listCurrentCalls();
+
+ verify(mMockBluetoothHeadset).clccResponse(eq(1), eq(0), eq(CALL_STATE_ACTIVE), eq(0),
+ eq(true), eq("555-0000"), eq(PhoneNumberUtils.TOA_Unknown));
+ verify(mMockBluetoothHeadset).clccResponse(eq(2), eq(1), eq(CALL_STATE_ACTIVE), eq(0),
+ eq(true), eq("555-0001"), eq(PhoneNumberUtils.TOA_Unknown));
+ verify(mMockBluetoothHeadset).clccResponse(0, 0, 0, 0, false, null, 0);
+ }
+
+ public void testWaitingCallClccResponse() throws Exception {
+ ArrayList<Call> calls = new ArrayList<>();
+ when(mMockCallsManager.getCalls()).thenReturn(calls);
+ // This test does not define a value for getForegroundCall(), so this ringing call will
+ // be treated as if it is a waiting call when listCurrentCalls() is invoked.
+ Call waitingCall = createRingingCall();
+ calls.add(waitingCall);
+ when(waitingCall.isIncoming()).thenReturn(true);
+ when(waitingCall.getGatewayInfo()).thenReturn(new GatewayInfo(null, null,
+ Uri.parse("tel:555-0000")));
+ when(waitingCall.getState()).thenReturn(CallState.RINGING);
+ when(waitingCall.isConference()).thenReturn(false);
+
+ mBluetoothPhoneService.mBinder.listCurrentCalls();
+ verify(mMockBluetoothHeadset).clccResponse(1, 1, CALL_STATE_WAITING, 0, false,
+ "555-0000", PhoneNumberUtils.TOA_Unknown);
+ verify(mMockBluetoothHeadset).clccResponse(0, 0, 0, 0, false, null, 0);
+ verify(mMockBluetoothHeadset, times(2)).clccResponse(anyInt(),
+ anyInt(), anyInt(), anyInt(), anyBoolean(), anyString(), anyInt());
+ }
+
+ public void testNewCallClccResponse() throws Exception {
+ ArrayList<Call> calls = new ArrayList<>();
+ when(mMockCallsManager.getCalls()).thenReturn(calls);
+ Call newCall = createForegroundCall();
+ calls.add(newCall);
+ when(newCall.getState()).thenReturn(CallState.NEW);
+ when(newCall.isConference()).thenReturn(false);
+
+ mBluetoothPhoneService.mBinder.listCurrentCalls();
+ verify(mMockBluetoothHeadset).clccResponse(0, 0, 0, 0, false, null, 0);
+ verify(mMockBluetoothHeadset, times(1)).clccResponse(anyInt(),
+ anyInt(), anyInt(), anyInt(), anyBoolean(), anyString(), anyInt());
+ }
+
+ public void testRingingCallClccResponse() throws Exception {
+ ArrayList<Call> calls = new ArrayList<>();
+ when(mMockCallsManager.getCalls()).thenReturn(calls);
+ Call ringingCall = createForegroundCall();
+ calls.add(ringingCall);
+ when(ringingCall.getState()).thenReturn(CallState.RINGING);
+ when(ringingCall.isIncoming()).thenReturn(true);
+ when(ringingCall.isConference()).thenReturn(false);
+ when(ringingCall.getGatewayInfo()).thenReturn(new GatewayInfo(null, null,
+ Uri.parse("tel:555-0000")));
+
+ mBluetoothPhoneService.mBinder.listCurrentCalls();
+ verify(mMockBluetoothHeadset).clccResponse(1, 1, CALL_STATE_INCOMING, 0, false,
+ "555-0000", PhoneNumberUtils.TOA_Unknown);
+ verify(mMockBluetoothHeadset).clccResponse(0, 0, 0, 0, false, null, 0);
+ verify(mMockBluetoothHeadset, times(2)).clccResponse(anyInt(),
+ anyInt(), anyInt(), anyInt(), anyBoolean(), anyString(), anyInt());
+ }
+
+ public void testCallClccCache() throws Exception {
+ ArrayList<Call> calls = new ArrayList<>();
+ when(mMockCallsManager.getCalls()).thenReturn(calls);
+ Call ringingCall = createForegroundCall();
+ calls.add(ringingCall);
+ when(ringingCall.getState()).thenReturn(CallState.RINGING);
+ when(ringingCall.isIncoming()).thenReturn(true);
+ when(ringingCall.isConference()).thenReturn(false);
+ when(ringingCall.getGatewayInfo()).thenReturn(new GatewayInfo(null, null,
+ Uri.parse("tel:555-0000")));
+
+ mBluetoothPhoneService.mBinder.listCurrentCalls();
+ verify(mMockBluetoothHeadset).clccResponse(1, 1, CALL_STATE_INCOMING, 0, false,
+ "555-0000", PhoneNumberUtils.TOA_Unknown);
+
+ // Test Caching of old call indicies in clcc
+ when(ringingCall.getState()).thenReturn(CallState.ACTIVE);
+ Call newHoldingCall = createHeldCall();
+ calls.add(0, newHoldingCall);
+ when(newHoldingCall.getState()).thenReturn(CallState.ON_HOLD);
+ when(newHoldingCall.isIncoming()).thenReturn(true);
+ when(newHoldingCall.isConference()).thenReturn(false);
+ when(newHoldingCall.getGatewayInfo()).thenReturn(new GatewayInfo(null, null,
+ Uri.parse("tel:555-0001")));
+
+ mBluetoothPhoneService.mBinder.listCurrentCalls();
+ verify(mMockBluetoothHeadset).clccResponse(1, 1, CALL_STATE_ACTIVE, 0, false,
+ "555-0000", PhoneNumberUtils.TOA_Unknown);
+ verify(mMockBluetoothHeadset).clccResponse(2, 1, CALL_STATE_HELD, 0, false,
+ "555-0001", PhoneNumberUtils.TOA_Unknown);
+ verify(mMockBluetoothHeadset, times(2)).clccResponse(0, 0, 0, 0, false, null, 0);
+ }
+
+ public void testAlertingCallClccResponse() throws Exception {
+ ArrayList<Call> calls = new ArrayList<>();
+ when(mMockCallsManager.getCalls()).thenReturn(calls);
+ Call dialingCall = createForegroundCall();
+ calls.add(dialingCall);
+ when(dialingCall.getState()).thenReturn(CallState.DIALING);
+ when(dialingCall.isIncoming()).thenReturn(false);
+ when(dialingCall.isConference()).thenReturn(false);
+ when(dialingCall.getGatewayInfo()).thenReturn(new GatewayInfo(null, null,
+ Uri.parse("tel:555-0000")));
+
+ mBluetoothPhoneService.mBinder.listCurrentCalls();
+ verify(mMockBluetoothHeadset).clccResponse(1, 0, CALL_STATE_ALERTING, 0, false,
+ "555-0000", PhoneNumberUtils.TOA_Unknown);
+ verify(mMockBluetoothHeadset).clccResponse(0, 0, 0, 0, false, null, 0);
+ verify(mMockBluetoothHeadset, times(2)).clccResponse(anyInt(),
+ anyInt(), anyInt(), anyInt(), anyBoolean(), anyString(), anyInt());
+ }
+
+ public void testHoldingCallClccResponse() throws Exception {
+ ArrayList<Call> calls = new ArrayList<>();
+ when(mMockCallsManager.getCalls()).thenReturn(calls);
+ Call dialingCall = createForegroundCall();
+ calls.add(dialingCall);
+ when(dialingCall.getState()).thenReturn(CallState.DIALING);
+ when(dialingCall.isIncoming()).thenReturn(false);
+ when(dialingCall.isConference()).thenReturn(false);
+ when(dialingCall.getGatewayInfo()).thenReturn(new GatewayInfo(null, null,
+ Uri.parse("tel:555-0000")));
+ Call holdingCall = createHeldCall();
+ calls.add(holdingCall);
+ when(holdingCall.getState()).thenReturn(CallState.ON_HOLD);
+ when(holdingCall.isIncoming()).thenReturn(true);
+ when(holdingCall.isConference()).thenReturn(false);
+ when(holdingCall.getGatewayInfo()).thenReturn(new GatewayInfo(null, null,
+ Uri.parse("tel:555-0001")));
+
+ mBluetoothPhoneService.mBinder.listCurrentCalls();
+ verify(mMockBluetoothHeadset).clccResponse(1, 0, CALL_STATE_ALERTING, 0, false,
+ "555-0000", PhoneNumberUtils.TOA_Unknown);
+ verify(mMockBluetoothHeadset).clccResponse(2, 1, CALL_STATE_HELD, 0, false,
+ "555-0001", PhoneNumberUtils.TOA_Unknown);
+ verify(mMockBluetoothHeadset).clccResponse(0, 0, 0, 0, false, null, 0);
+ verify(mMockBluetoothHeadset, times(3)).clccResponse(anyInt(),
+ anyInt(), anyInt(), anyInt(), anyBoolean(), anyString(), anyInt());
+ }
+
+ public void testListCurrentCallsImsConference() throws Exception {
+ ArrayList<Call> calls = new ArrayList<>();
+ Call parentCall = createActiveCall();
+ calls.add(parentCall);
+ addCallCapability(parentCall, Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN);
+ when(parentCall.isConference()).thenReturn(true);
+ when(parentCall.getState()).thenReturn(CallState.ACTIVE);
+ when(parentCall.isIncoming()).thenReturn(true);
+ when(mMockCallsManager.getCalls()).thenReturn(calls);
+
+ mBluetoothPhoneService.mBinder.listCurrentCalls();
+
+ verify(mMockBluetoothHeadset).clccResponse(eq(1), eq(1), eq(CALL_STATE_ACTIVE), eq(0),
+ eq(true), (String) isNull(), eq(-1));
+ verify(mMockBluetoothHeadset).clccResponse(0, 0, 0, 0, false, null, 0);
+ }
+
+ public void testQueryPhoneState() throws Exception {
+ Call ringingCall = createRingingCall();
+ when(ringingCall.getHandle()).thenReturn(Uri.parse("tel:555-0000"));
+
+ mBluetoothPhoneService.mBinder.queryPhoneState();
+
+ verify(mMockBluetoothHeadset).phoneStateChanged(eq(0), eq(0), eq(CALL_STATE_INCOMING),
+ eq("555-0000"), eq(PhoneNumberUtils.TOA_Unknown));
+ }
+
+ public void testCDMAConferenceQueryState() throws Exception {
+ Call parentConfCall = createActiveCall();
+ final Call confCall1 = mock(Call.class);
+ final Call confCall2 = mock(Call.class);
+ when(parentConfCall.getHandle()).thenReturn(Uri.parse("tel:555-0000"));
+ addCallCapability(parentConfCall, Connection.CAPABILITY_SWAP_CONFERENCE);
+ removeCallCapability(parentConfCall, Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN);
+ when(parentConfCall.wasConferencePreviouslyMerged()).thenReturn(true);
+ when(parentConfCall.isConference()).thenReturn(true);
+ when(parentConfCall.getChildCalls()).thenReturn(new LinkedList<Call>() {{
+ add(confCall1);
+ add(confCall2);
+ }});
+
+ mBluetoothPhoneService.mBinder.queryPhoneState();
+ verify(mMockBluetoothHeadset).phoneStateChanged(eq(1), eq(0), eq(CALL_STATE_IDLE),
+ eq(""), eq(128));
+ }
+
+ public void testProcessChldTypeReleaseHeldRinging() throws Exception {
+ Call ringingCall = createRingingCall();
+
+ boolean didProcess = mBluetoothPhoneService.mBinder.processChld(CHLD_TYPE_RELEASEHELD);
+
+ verify(mMockCallsManager).rejectCall(eq(ringingCall), eq(false), any(String.class));
+ assertEquals(didProcess, true);
+ }
+
+ public void testProcessChldTypeReleaseHeldHold() throws Exception {
+ Call onHoldCall = createHeldCall();
+
+ boolean didProcess = mBluetoothPhoneService.mBinder.processChld(CHLD_TYPE_RELEASEHELD);
+
+ verify(mMockCallsManager).disconnectCall(eq(onHoldCall));
+ assertEquals(didProcess, true);
+ }
+
+ public void testProcessChldReleaseActiveRinging() throws Exception {
+ Call activeCall = createActiveCall();
+ Call ringingCall = createRingingCall();
+
+ boolean didProcess = mBluetoothPhoneService.mBinder.processChld(
+ CHLD_TYPE_RELEASEACTIVE_ACCEPTHELD);
+
+ verify(mMockCallsManager).disconnectCall(eq(activeCall));
+ verify(mMockCallsManager).answerCall(eq(ringingCall), any(int.class));
+ assertEquals(didProcess, true);
+ }
+
+ public void testProcessChldReleaseActiveHold() throws Exception {
+ Call activeCall = createActiveCall();
+ Call heldCall = createHeldCall();
+
+ boolean didProcess = mBluetoothPhoneService.mBinder.processChld(
+ CHLD_TYPE_RELEASEACTIVE_ACCEPTHELD);
+
+ verify(mMockCallsManager).disconnectCall(eq(activeCall));
+ verify(mMockCallsManager).unholdCall(eq(heldCall));
+ assertEquals(didProcess, true);
+ }
+
+ public void testProcessChldHoldActiveRinging() throws Exception {
+ Call ringingCall = createRingingCall();
+
+ boolean didProcess = mBluetoothPhoneService.mBinder.processChld(
+ CHLD_TYPE_HOLDACTIVE_ACCEPTHELD);
+
+ verify(mMockCallsManager).answerCall(eq(ringingCall), any(int.class));
+ assertEquals(didProcess, true);
+ }
+
+ public void testProcessChldHoldActiveUnhold() throws Exception {
+ Call heldCall = createHeldCall();
+
+ boolean didProcess = mBluetoothPhoneService.mBinder.processChld(
+ CHLD_TYPE_HOLDACTIVE_ACCEPTHELD);
+
+ verify(mMockCallsManager).unholdCall(eq(heldCall));
+ assertEquals(didProcess, true);
+ }
+
+ public void testProcessChldHoldActiveHold() throws Exception {
+ Call activeCall = createActiveCall();
+ addCallCapability(activeCall, Connection.CAPABILITY_HOLD);
+
+ boolean didProcess = mBluetoothPhoneService.mBinder.processChld(
+ CHLD_TYPE_HOLDACTIVE_ACCEPTHELD);
+
+ verify(mMockCallsManager).holdCall(eq(activeCall));
+ assertEquals(didProcess, true);
+ }
+
+ public void testProcessChldAddHeldToConfHolding() throws Exception {
+ Call activeCall = createActiveCall();
+ addCallCapability(activeCall, Connection.CAPABILITY_MERGE_CONFERENCE);
+
+ boolean didProcess = mBluetoothPhoneService.mBinder.processChld(CHLD_TYPE_ADDHELDTOCONF);
+
+ verify(activeCall).mergeConference();
+ assertEquals(didProcess, true);
+ }
+
+ public void testProcessChldAddHeldToConf() throws Exception {
+ Call activeCall = createActiveCall();
+ removeCallCapability(activeCall, Connection.CAPABILITY_MERGE_CONFERENCE);
+ Call conferenceableCall = mock(Call.class);
+ ArrayList<Call> conferenceableCalls = new ArrayList<>();
+ conferenceableCalls.add(conferenceableCall);
+ when(activeCall.getConferenceableCalls()).thenReturn(conferenceableCalls);
+
+ boolean didProcess = mBluetoothPhoneService.mBinder.processChld(CHLD_TYPE_ADDHELDTOCONF);
+
+ verify(mMockCallsManager).conference(activeCall, conferenceableCall);
+ assertEquals(didProcess, true);
+ }
+
+ public void testProcessChldHoldActiveSwapConference() throws Exception {
+ // Create an active CDMA Call with a call on hold and simulate a swapConference().
+ Call parentCall = createActiveCall();
+ final Call foregroundCall = mock(Call.class);
+ final Call heldCall = createHeldCall();
+ addCallCapability(parentCall, Connection.CAPABILITY_SWAP_CONFERENCE);
+ removeCallCapability(parentCall, Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN);
+ when(parentCall.isConference()).thenReturn(true);
+ when(parentCall.wasConferencePreviouslyMerged()).thenReturn(false);
+ when(parentCall.getChildCalls()).thenReturn(new LinkedList<Call>() {{
+ add(foregroundCall);
+ add(heldCall);
+ }});
+
+ boolean didProcess = mBluetoothPhoneService.mBinder.processChld(
+ CHLD_TYPE_HOLDACTIVE_ACCEPTHELD);
+
+ verify(parentCall).swapConference();
+ verify(mMockBluetoothHeadset).phoneStateChanged(eq(1), eq(1), eq(CALL_STATE_IDLE), eq(""),
+ eq(128));
+ assertEquals(didProcess, true);
+ }
+
+ // Testing the CallsManager Listener Functionality on Bluetooth
+ public void testOnCallAddedRinging() throws Exception {
+ Call ringingCall = createRingingCall();
+ when(ringingCall.getHandle()).thenReturn(Uri.parse("tel:555-000"));
+
+ mBluetoothPhoneService.mCallsManagerListener.onCallAdded(ringingCall);
+
+ verify(mMockBluetoothHeadset).phoneStateChanged(eq(0), eq(0), eq(CALL_STATE_INCOMING),
+ eq("555-000"), eq(PhoneNumberUtils.TOA_Unknown));
+
+ }
+
+ public void testOnCallAddedCdmaActiveHold() throws Exception {
+ // Call has been put into a CDMA "conference" with one call on hold.
+ Call parentCall = createActiveCall();
+ final Call foregroundCall = mock(Call.class);
+ final Call heldCall = createHeldCall();
+ addCallCapability(parentCall, Connection.CAPABILITY_MERGE_CONFERENCE);
+ removeCallCapability(parentCall, Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN);
+ when(parentCall.isConference()).thenReturn(true);
+ when(parentCall.getChildCalls()).thenReturn(new LinkedList<Call>() {{
+ add(foregroundCall);
+ add(heldCall);
+ }});
+
+ mBluetoothPhoneService.mCallsManagerListener.onCallAdded(parentCall);
+
+ verify(mMockBluetoothHeadset).phoneStateChanged(eq(1), eq(1), eq(CALL_STATE_IDLE),
+ eq(""), eq(128));
+
+ }
+
+ public void testOnCallRemoved() throws Exception {
+ Call activeCall = createActiveCall();
+ mBluetoothPhoneService.mCallsManagerListener.onCallAdded(activeCall);
+ doReturn(null).when(mMockCallsManager).getActiveCall();
+ mBluetoothPhoneService.mCallsManagerListener.onCallRemoved(activeCall);
+
+ verify(mMockBluetoothHeadset).phoneStateChanged(eq(0), eq(0), eq(CALL_STATE_IDLE),
+ eq(""), eq(128));
+ }
+
+ public void testOnCallStateChangedConnectingCall() throws Exception {
+ Call activeCall = mock(Call.class);
+ Call connectingCall = mock(Call.class);
+ when(connectingCall.getState()).thenReturn(CallState.CONNECTING);
+ ArrayList<Call> calls = new ArrayList<>();
+ calls.add(connectingCall);
+ calls.add(activeCall);
+ when(mMockCallsManager.getCalls()).thenReturn(calls);
+
+ mBluetoothPhoneService.mCallsManagerListener.onCallStateChanged(activeCall,
+ CallState.ACTIVE, CallState.ON_HOLD);
+
+ verify(mMockBluetoothHeadset, never()).phoneStateChanged(anyInt(), anyInt(), anyInt(),
+ anyString(), anyInt());
+ }
+
+ public void testOnCallStateChangedDialing() throws Exception {
+ Call activeCall = createActiveCall();
+
+ mBluetoothPhoneService.mCallsManagerListener.onCallStateChanged(activeCall,
+ CallState.CONNECTING, CallState.DIALING);
+
+ verify(mMockBluetoothHeadset, never()).phoneStateChanged(anyInt(), anyInt(), anyInt(),
+ anyString(), anyInt());
+ }
+
+ public void testOnCallStateChangedAlerting() throws Exception {
+ Call outgoingCall = createOutgoingCall();
+
+ mBluetoothPhoneService.mCallsManagerListener.onCallStateChanged(outgoingCall,
+ CallState.NEW, CallState.DIALING);
+
+ verify(mMockBluetoothHeadset).phoneStateChanged(0, 0, CALL_STATE_DIALING, "", 128);
+ verify(mMockBluetoothHeadset).phoneStateChanged(0, 0, CALL_STATE_ALERTING, "", 128);
+ }
+
+ public void testOnCallStateChanged() throws Exception {
+ Call ringingCall = createRingingCall();
+ when(ringingCall.getHandle()).thenReturn(Uri.parse("tel:555-0000"));
+ mBluetoothPhoneService.mCallsManagerListener.onCallAdded(ringingCall);
+
+ verify(mMockBluetoothHeadset).phoneStateChanged(eq(0), eq(0), eq(CALL_STATE_INCOMING),
+ eq("555-0000"), eq(PhoneNumberUtils.TOA_Unknown));
+
+ //Switch to active
+ doReturn(null).when(mMockCallsManager).getRingingCall();
+ when(mMockCallsManager.getActiveCall()).thenReturn(ringingCall);
+
+ mBluetoothPhoneService.mCallsManagerListener.onCallStateChanged(ringingCall,
+ CallState.RINGING, CallState.ACTIVE);
+
+ verify(mMockBluetoothHeadset).phoneStateChanged(eq(1), eq(0), eq(CALL_STATE_IDLE),
+ eq(""), eq(128));
+ }
+
+ public void testOnCallStateChangedGSMSwap() throws Exception {
+ Call heldCall = createHeldCall();
+ when(heldCall.getHandle()).thenReturn(Uri.parse("tel:555-0000"));
+ doReturn(2).when(mMockCallsManager).getNumHeldCalls();
+ mBluetoothPhoneService.mCallsManagerListener.onCallStateChanged(heldCall,
+ CallState.ACTIVE, CallState.ON_HOLD);
+
+ verify(mMockBluetoothHeadset, never()).phoneStateChanged(eq(0), eq(2), eq(CALL_STATE_HELD),
+ eq("555-0000"), eq(PhoneNumberUtils.TOA_Unknown));
+ }
+
+ public void testOnIsConferencedChanged() throws Exception {
+ // Start with two calls that are being merged into a CDMA conference call. The
+ // onIsConferencedChanged method will be called multiple times during the call. Make sure
+ // that the bluetooth phone state is updated properly.
+ Call parentCall = createActiveCall();
+ Call activeCall = mock(Call.class);
+ Call heldCall = createHeldCall();
+ when(activeCall.getParentCall()).thenReturn(parentCall);
+ when(heldCall.getParentCall()).thenReturn(parentCall);
+ ArrayList<Call> calls = new ArrayList<>();
+ calls.add(activeCall);
+ when(parentCall.getChildCalls()).thenReturn(calls);
+ when(parentCall.isConference()).thenReturn(true);
+ removeCallCapability(parentCall, Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN);
+ addCallCapability(parentCall, Connection.CAPABILITY_SWAP_CONFERENCE);
+ when(parentCall.wasConferencePreviouslyMerged()).thenReturn(false);
+
+ // Be sure that onIsConferencedChanged rejects spurious changes during set up of
+ // CDMA "conference"
+ mBluetoothPhoneService.mCallsManagerListener.onIsConferencedChanged(activeCall);
+ verify(mMockBluetoothHeadset, never()).phoneStateChanged(anyInt(), anyInt(), anyInt(),
+ anyString(), anyInt());
+ mBluetoothPhoneService.mCallsManagerListener.onIsConferencedChanged(heldCall);
+ verify(mMockBluetoothHeadset, never()).phoneStateChanged(anyInt(), anyInt(), anyInt(),
+ anyString(), anyInt());
+ mBluetoothPhoneService.mCallsManagerListener.onIsConferencedChanged(parentCall);
+ verify(mMockBluetoothHeadset, never()).phoneStateChanged(anyInt(), anyInt(), anyInt(),
+ anyString(), anyInt());
+
+ calls.add(heldCall);
+ mBluetoothPhoneService.mCallsManagerListener.onIsConferencedChanged(parentCall);
+ verify(mMockBluetoothHeadset).phoneStateChanged(eq(1), eq(1), eq(CALL_STATE_IDLE),
+ eq(""), eq(128));
+ }
+
+ public void testBluetoothAdapterReceiver() throws Exception {
+ Call ringingCall = createRingingCall();
+ when(ringingCall.getHandle()).thenReturn(Uri.parse("tel:555-0000"));
+
+ Intent intent = new Intent();
+ intent.putExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_ON);
+ mBluetoothPhoneService.mBluetoothAdapterReceiver.onReceive(mContext, intent);
+
+ verify(mMockBluetoothHeadset).phoneStateChanged(eq(0), eq(0), eq(CALL_STATE_INCOMING),
+ eq("555-0000"), eq(PhoneNumberUtils.TOA_Unknown));
+ }
+
+ private void addCallCapability(Call call, int capability) {
+ when(call.can(capability)).thenReturn(true);
+ }
+
+ private void removeCallCapability(Call call, int capability) {
+ when(call.can(capability)).thenReturn(false);
+ }
+
+ private Call createActiveCall() {
+ Call call = mock(Call.class);
+ when(mMockCallsManager.getActiveCall()).thenReturn(call);
+ return call;
+ }
+
+ private Call createRingingCall() {
+ Call call = mock(Call.class);
+ when(mMockCallsManager.getRingingCall()).thenReturn(call);
+ return call;
+ }
+
+ private Call createHeldCall() {
+ Call call = mock(Call.class);
+ when(mMockCallsManager.getHeldCall()).thenReturn(call);
+ return call;
+ }
+
+ private Call createOutgoingCall() {
+ Call call = mock(Call.class);
+ when(mMockCallsManager.getOutgoingCall()).thenReturn(call);
+ return call;
+ }
+
+ private Call createForegroundCall() {
+ Call call = mock(Call.class);
+ when(mMockCallsManager.getForegroundCall()).thenReturn(call);
+ return call;
+ }
+
+ private static ComponentName makeQuickConnectionServiceComponentName() {
+ return new ComponentName("com.android.server.telecom.tests",
+ "com.android.server.telecom.tests.MockConnectionService");
+ }
+
+ private static PhoneAccountHandle makeQuickAccountHandle(String id) {
+ return new PhoneAccountHandle(makeQuickConnectionServiceComponentName(), id,
+ Binder.getCallingUserHandle());
+ }
+
+ private PhoneAccount.Builder makeQuickAccountBuilder(String id, int idx) {
+ return new PhoneAccount.Builder(makeQuickAccountHandle(id), "label" + idx);
+ }
+
+ private PhoneAccount makeQuickAccount(String id, int idx) {
+ return makeQuickAccountBuilder(id, idx)
+ .setAddress(Uri.parse(TEST_ACCOUNT_ADDRESS + idx))
+ .setSubscriptionAddress(Uri.parse("tel:555-000" + idx))
+ .setCapabilities(idx)
+ .setIcon(Icon.createWithResource(
+ "com.android.server.telecom.tests", R.drawable.stat_sys_phone_call))
+ .setShortDescription("desc" + idx)
+ .setIsEnabled(true)
+ .build();
+ }
+}
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
new file mode 100644
index 0000000..b12ef8d
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
@@ -0,0 +1,652 @@
+/*
+ * 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.server.telecom.tests;
+
+import android.content.Context;
+import android.media.AudioManager;
+import android.media.IAudioService;
+import android.telecom.CallAudioState;
+
+import com.android.server.telecom.BluetoothManager;
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallAudioManager;
+import com.android.server.telecom.CallAudioRouteStateMachine;
+import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.ConnectionServiceWrapper;
+import com.android.server.telecom.StatusBarNotifier;
+import com.android.server.telecom.WiredHeadsetManager;
+
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.same;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+
+public class CallAudioRouteStateMachineTest extends TelecomTestCase {
+ private static final int NONE = 0;
+ private static final int ON = 1;
+ private static final int OFF = 2;
+
+ private class TestParameters {
+ public String name;
+ public int initialRoute;
+ public int availableRoutes; // may excl. speakerphone, because that's always available
+ public int speakerInteraction; // one of NONE, ON, or OFF
+ public int bluetoothInteraction; // one of NONE, ON, or OFF
+ public int action;
+ public int expectedRoute;
+ public int expectedAvailableRoutes; // also may exclude the speakerphone.
+
+ public TestParameters(String name, int initialRoute, int availableRoutes, int
+ speakerInteraction, int bluetoothInteraction, int action, int expectedRoute, int
+ expectedAvailableRoutes) {
+ this.name = name;
+ this.initialRoute = initialRoute;
+ this.availableRoutes = availableRoutes;
+ this.speakerInteraction = speakerInteraction;
+ this.bluetoothInteraction = bluetoothInteraction;
+ this.action = action;
+ this.expectedRoute = expectedRoute;
+ this.expectedAvailableRoutes = expectedAvailableRoutes;
+ }
+
+ @Override
+ public String toString() {
+ return "TestParameters{" +
+ "name='" + name + '\'' +
+ ", initialRoute=" + initialRoute +
+ ", availableRoutes=" + availableRoutes +
+ ", speakerInteraction=" + speakerInteraction +
+ ", bluetoothInteraction=" + bluetoothInteraction +
+ ", action=" + action +
+ ", expectedRoute=" + expectedRoute +
+ ", expectedAvailableRoutes=" + expectedAvailableRoutes +
+ '}';
+ }
+ }
+
+ @Mock CallsManager mockCallsManager;
+ @Mock BluetoothManager mockBluetoothManager;
+ @Mock IAudioService mockAudioService;
+ @Mock ConnectionServiceWrapper mockConnectionServiceWrapper;
+ @Mock WiredHeadsetManager mockWiredHeadsetManager;
+ @Mock StatusBarNotifier mockStatusBarNotifier;
+ @Mock Call fakeCall;
+
+ private CallAudioManager.AudioServiceFactory mAudioServiceFactory;
+ private static final int TEST_TIMEOUT = 500;
+ private AudioManager mockAudioManager;
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ MockitoAnnotations.initMocks(this);
+ mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
+ mockAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+
+ mAudioServiceFactory = new CallAudioManager.AudioServiceFactory() {
+ @Override
+ public IAudioService getAudioService() {
+ return mockAudioService;
+ }
+ };
+
+ when(mockCallsManager.getForegroundCall()).thenReturn(fakeCall);
+ when(fakeCall.getConnectionService()).thenReturn(mockConnectionServiceWrapper);
+ when(fakeCall.isAlive()).thenReturn(true);
+ doNothing().when(mockConnectionServiceWrapper).onCallAudioStateChanged(any(Call.class),
+ any(CallAudioState.class));
+ }
+
+ public void testStateMachineTransitionsWithFocus() throws Throwable {
+ List<TestParameters> paramList = generateTransitionTests();
+ for (TestParameters params : paramList) {
+ try {
+ runParametrizedTestCaseWithFocus(params);
+ } catch (Throwable e) {
+ String newMessage = "Failed at parameters: \n" + params.toString() + '\n'
+ + e.getMessage();
+ throw(new Throwable(newMessage, e));
+ }
+ }
+ }
+
+ public void testStateMachineTransitionsWithoutFocus() throws Throwable {
+ List<TestParameters> paramList = generateTransitionTests();
+ for (TestParameters params : paramList) {
+ try {
+ runParametrizedTestCaseWithoutFocus(params);
+ } catch (Throwable e) {
+ String newMessage = "Failed at parameters: \n" + params.toString() + '\n'
+ + e.getMessage();
+ throw(new Throwable(newMessage, e));
+ }
+ }
+ }
+
+ public void testSpeakerPersistence() {
+ CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
+ mContext,
+ mockCallsManager,
+ mockBluetoothManager,
+ mockWiredHeadsetManager,
+ mockStatusBarNotifier,
+ mAudioServiceFactory);
+
+ when(mockBluetoothManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
+ when(mockBluetoothManager.isBluetoothAvailable()).thenReturn(true);
+ when(mockAudioManager.isSpeakerphoneOn()).thenReturn(true);
+ doAnswer(new Answer() {
+ @Override
+ public Object answer(InvocationOnMock invocation) throws Throwable {
+ Object[] args = invocation.getArguments();
+ when(mockAudioManager.isSpeakerphoneOn()).thenReturn((Boolean) args[0]);
+ return null;
+ }
+ }).when(mockAudioManager).setSpeakerphoneOn(any(Boolean.class));
+ CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_SPEAKER,
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER);
+ stateMachine.initialize(initState);
+
+ stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
+ CallAudioRouteStateMachine.HAS_FOCUS);
+ stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.CONNECT_WIRED_HEADSET);
+ CallAudioState expectedMiddleState = new CallAudioState(false,
+ CallAudioState.ROUTE_WIRED_HEADSET,
+ CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_SPEAKER);
+ verifyNewSystemCallAudioState(initState, expectedMiddleState);
+ resetMocks();
+
+ stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.DISCONNECT_WIRED_HEADSET);
+ verifyNewSystemCallAudioState(expectedMiddleState, initState);
+ }
+
+ public void testInitializationWithNoHeadsetNoBluetooth() {
+ when(mockWiredHeadsetManager.isPluggedIn()).thenReturn(false);
+ when(mockBluetoothManager.isBluetoothAvailable()).thenReturn(false);
+
+ CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
+ mContext,
+ mockCallsManager,
+ mockBluetoothManager,
+ mockWiredHeadsetManager,
+ mockStatusBarNotifier,
+ mAudioServiceFactory);
+ stateMachine.initialize();
+ CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_EARPIECE,
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER);
+ assertEquals(expectedState, stateMachine.getCurrentCallAudioState());
+ }
+
+ public void testInitializationWithHeadsetNoBluetooth() {
+ when(mockWiredHeadsetManager.isPluggedIn()).thenReturn(true);
+ when(mockBluetoothManager.isBluetoothAvailable()).thenReturn(false);
+
+ CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
+ mContext,
+ mockCallsManager,
+ mockBluetoothManager,
+ mockWiredHeadsetManager,
+ mockStatusBarNotifier,
+ mAudioServiceFactory);
+ stateMachine.initialize();
+ CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_WIRED_HEADSET,
+ CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_SPEAKER);
+ assertEquals(expectedState, stateMachine.getCurrentCallAudioState());
+ }
+
+ public void testInitializationWithHeadsetAndBluetooth() {
+ when(mockWiredHeadsetManager.isPluggedIn()).thenReturn(true);
+ when(mockBluetoothManager.isBluetoothAvailable()).thenReturn(true);
+
+ CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
+ mContext,
+ mockCallsManager,
+ mockBluetoothManager,
+ mockWiredHeadsetManager,
+ mockStatusBarNotifier,
+ mAudioServiceFactory);
+ stateMachine.initialize();
+ CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH,
+ CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_SPEAKER
+ | CallAudioState.ROUTE_BLUETOOTH);
+ assertEquals(expectedState, stateMachine.getCurrentCallAudioState());
+ }
+
+ public void testInitializationWithBluetoothNoHeadset() {
+ when(mockWiredHeadsetManager.isPluggedIn()).thenReturn(false);
+ when(mockBluetoothManager.isBluetoothAvailable()).thenReturn(true);
+
+ CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
+ mContext,
+ mockCallsManager,
+ mockBluetoothManager,
+ mockWiredHeadsetManager,
+ mockStatusBarNotifier,
+ mAudioServiceFactory);
+ stateMachine.initialize();
+ CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH,
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER
+ | CallAudioState.ROUTE_BLUETOOTH);
+ assertEquals(expectedState, stateMachine.getCurrentCallAudioState());
+ }
+
+ private List<TestParameters> generateTransitionTests() {
+ List<TestParameters> params = new ArrayList<>();
+ params.add(new TestParameters(
+ "Connect headset during earpiece", // name
+ CallAudioState.ROUTE_EARPIECE, // initialRoute
+ CallAudioState.ROUTE_EARPIECE, // availableRoutes
+ NONE, // speakerInteraction
+ NONE, // bluetoothInteraction
+ CallAudioRouteStateMachine.CONNECT_WIRED_HEADSET, // action
+ CallAudioState.ROUTE_WIRED_HEADSET, // expectedRoute
+ CallAudioState.ROUTE_WIRED_HEADSET // expectedAvailableRoutes
+ ));
+
+ params.add(new TestParameters(
+ "Connect headset during bluetooth", // name
+ CallAudioState.ROUTE_BLUETOOTH, // initialRoute
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // availableRoutes
+ NONE, // speakerInteraction
+ OFF, // bluetoothInteraction
+ CallAudioRouteStateMachine.CONNECT_WIRED_HEADSET, // action
+ CallAudioState.ROUTE_WIRED_HEADSET, // expectedRoute
+ CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH // expectedAvail
+ ));
+
+ params.add(new TestParameters(
+ "Connect headset during speakerphone", // name
+ CallAudioState.ROUTE_SPEAKER, // initialRoute
+ CallAudioState.ROUTE_EARPIECE, // availableRoutes
+ OFF, // speakerInteraction
+ NONE, // bluetoothInteraction
+ CallAudioRouteStateMachine.CONNECT_WIRED_HEADSET, // action
+ CallAudioState.ROUTE_WIRED_HEADSET, // expectedRoute
+ CallAudioState.ROUTE_WIRED_HEADSET // expectedAvailableRoutes
+ ));
+
+ params.add(new TestParameters(
+ "Disconnect headset during headset", // name
+ CallAudioState.ROUTE_WIRED_HEADSET, // initialRoute
+ CallAudioState.ROUTE_WIRED_HEADSET, // availableRoutes
+ NONE, // speakerInteraction
+ NONE, // bluetoothInteraction
+ CallAudioRouteStateMachine.DISCONNECT_WIRED_HEADSET, // action
+ CallAudioState.ROUTE_EARPIECE, // expectedRoute
+ CallAudioState.ROUTE_EARPIECE // expectedAvailableRoutes
+ ));
+
+ params.add(new TestParameters(
+ "Disconnect headset during headset with bluetooth available", // name
+ CallAudioState.ROUTE_WIRED_HEADSET, // initialRoute
+ CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // availableRou
+ NONE, // speakerInteraction
+ NONE, // bluetoothInteraction
+ CallAudioRouteStateMachine.DISCONNECT_WIRED_HEADSET, // action
+ CallAudioState.ROUTE_EARPIECE, // expectedRoute
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH // expectedAvailableR
+ ));
+
+ params.add(new TestParameters(
+ "Disconnect headset during bluetooth", // name
+ CallAudioState.ROUTE_BLUETOOTH, // initialRoute
+ CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // availableRou
+ NONE, // speakerInteraction
+ NONE, // bluetoothInteraction
+ CallAudioRouteStateMachine.DISCONNECT_WIRED_HEADSET, // action
+ CallAudioState.ROUTE_BLUETOOTH, // expectedRoute
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH // expectedAvailableR
+ ));
+
+ params.add(new TestParameters(
+ "Disconnect headset during speakerphone", // name
+ CallAudioState.ROUTE_SPEAKER, // initialRoute
+ CallAudioState.ROUTE_WIRED_HEADSET, // availableRoutes
+ NONE, // speakerInteraction
+ NONE, // bluetoothInteraction
+ CallAudioRouteStateMachine.DISCONNECT_WIRED_HEADSET, // action
+ CallAudioState.ROUTE_SPEAKER, // expectedRoute
+ CallAudioState.ROUTE_EARPIECE // expectedAvailableRoutes
+ ));
+
+ params.add(new TestParameters(
+ "Disconnect headset during speakerphone with bluetooth available", // name
+ CallAudioState.ROUTE_SPEAKER, // initialRoute
+ CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // availableRou
+ NONE, // speakerInteraction
+ NONE, // bluetoothInteraction
+ CallAudioRouteStateMachine.DISCONNECT_WIRED_HEADSET, // action
+ CallAudioState.ROUTE_SPEAKER, // expectedRoute
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH // expectedAvailableR
+ ));
+
+ params.add(new TestParameters(
+ "Connect bluetooth during earpiece", // name
+ CallAudioState.ROUTE_EARPIECE, // initialRoute
+ CallAudioState.ROUTE_EARPIECE, // availableRoutes
+ NONE, // speakerInteraction
+ ON, // bluetoothInteraction
+ CallAudioRouteStateMachine.CONNECT_BLUETOOTH, // action
+ CallAudioState.ROUTE_BLUETOOTH, // expectedRoute
+ CallAudioState.ROUTE_BLUETOOTH | CallAudioState.ROUTE_EARPIECE // expectedAvailableR
+ ));
+
+ params.add(new TestParameters(
+ "Connect bluetooth during wired headset", // name
+ CallAudioState.ROUTE_WIRED_HEADSET, // initialRoute
+ CallAudioState.ROUTE_WIRED_HEADSET, // availableRoutes
+ NONE, // speakerInteraction
+ ON, // bluetoothInteraction
+ CallAudioRouteStateMachine.CONNECT_BLUETOOTH, // action
+ CallAudioState.ROUTE_BLUETOOTH, // expectedRoute
+ CallAudioState.ROUTE_BLUETOOTH | CallAudioState.ROUTE_WIRED_HEADSET // expectedAvail
+ ));
+
+ params.add(new TestParameters(
+ "Connect bluetooth during speakerphone", // name
+ CallAudioState.ROUTE_SPEAKER, // initialRoute
+ CallAudioState.ROUTE_EARPIECE, // availableRoutes
+ OFF, // speakerInteraction
+ ON, // bluetoothInteraction
+ CallAudioRouteStateMachine.CONNECT_BLUETOOTH, // action
+ CallAudioState.ROUTE_BLUETOOTH, // expectedRoute
+ CallAudioState.ROUTE_BLUETOOTH | CallAudioState.ROUTE_EARPIECE // expectedAvailableR
+ ));
+
+ params.add(new TestParameters(
+ "Disconnect bluetooth during bluetooth without headset in", // name
+ CallAudioState.ROUTE_BLUETOOTH, // initialRoute
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // availableRoutes
+ NONE, // speakerInteraction
+ OFF, // bluetoothInteraction
+ CallAudioRouteStateMachine.DISCONNECT_BLUETOOTH, // action
+ CallAudioState.ROUTE_EARPIECE, // expectedRoute
+ CallAudioState.ROUTE_EARPIECE // expectedAvailableRoutes
+ ));
+
+ params.add(new TestParameters(
+ "Disconnect bluetooth during bluetooth with headset in", // name
+ CallAudioState.ROUTE_BLUETOOTH, // initialRoute
+ CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // availableRou
+ NONE, // speakerInteraction
+ OFF, // bluetoothInteraction
+ CallAudioRouteStateMachine.DISCONNECT_BLUETOOTH, // action
+ CallAudioState.ROUTE_WIRED_HEADSET, // expectedRoute
+ CallAudioState.ROUTE_WIRED_HEADSET // expectedAvailableRoutes
+ ));
+
+ params.add(new TestParameters(
+ "Disconnect bluetooth during speakerphone", // name
+ CallAudioState.ROUTE_SPEAKER, // initialRoute
+ CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // availableRou
+ NONE, // speakerInteraction
+ NONE, // bluetoothInteraction
+ CallAudioRouteStateMachine.DISCONNECT_BLUETOOTH, // action
+ CallAudioState.ROUTE_SPEAKER, // expectedRoute
+ CallAudioState.ROUTE_WIRED_HEADSET // expectedAvailableRoutes
+ ));
+
+ params.add(new TestParameters(
+ "Disconnect bluetooth during earpiece", // name
+ CallAudioState.ROUTE_EARPIECE, // initialRoute
+ CallAudioState.ROUTE_EARPIECE| CallAudioState.ROUTE_BLUETOOTH, // availableRoutes
+ NONE, // speakerInteraction
+ NONE, // bluetoothInteraction
+ CallAudioRouteStateMachine.DISCONNECT_BLUETOOTH, // action
+ CallAudioState.ROUTE_EARPIECE, // expectedRoute
+ CallAudioState.ROUTE_EARPIECE // expectedAvailableRoutes
+ ));
+
+ params.add(new TestParameters(
+ "Switch to speakerphone from earpiece", // name
+ CallAudioState.ROUTE_EARPIECE, // initialRoute
+ CallAudioState.ROUTE_EARPIECE, // availableRoutes
+ ON, // speakerInteraction
+ NONE, // bluetoothInteraction
+ CallAudioRouteStateMachine.SWITCH_SPEAKER, // action
+ CallAudioState.ROUTE_SPEAKER, // expectedRoute
+ CallAudioState.ROUTE_EARPIECE // expectedAvailableRoutes
+ ));
+
+ params.add(new TestParameters(
+ "Switch to speakerphone from headset", // name
+ CallAudioState.ROUTE_WIRED_HEADSET, // initialRoute
+ CallAudioState.ROUTE_WIRED_HEADSET, // availableRoutes
+ ON, // speakerInteraction
+ NONE, // bluetoothInteraction
+ CallAudioRouteStateMachine.SWITCH_SPEAKER, // action
+ CallAudioState.ROUTE_SPEAKER, // expectedRoute
+ CallAudioState.ROUTE_WIRED_HEADSET // expectedAvailableRoutes
+ ));
+
+ params.add(new TestParameters(
+ "Switch to speakerphone from bluetooth", // name
+ CallAudioState.ROUTE_BLUETOOTH, // initialRoute
+ CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // availableRou
+ ON, // speakerInteraction
+ OFF, // bluetoothInteraction
+ CallAudioRouteStateMachine.SWITCH_SPEAKER, // action
+ CallAudioState.ROUTE_SPEAKER, // expectedRoute
+ CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH // expectedAvail
+ ));
+
+ params.add(new TestParameters(
+ "Switch to earpiece from bluetooth", // name
+ CallAudioState.ROUTE_BLUETOOTH, // initialRoute
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // availableRoutes
+ NONE, // speakerInteraction
+ OFF, // bluetoothInteraction
+ CallAudioRouteStateMachine.SWITCH_EARPIECE, // action
+ CallAudioState.ROUTE_EARPIECE, // expectedRoute
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH // expectedAvailableR
+ ));
+
+ params.add(new TestParameters(
+ "Switch to earpiece from speakerphone", // name
+ CallAudioState.ROUTE_SPEAKER, // initialRoute
+ CallAudioState.ROUTE_EARPIECE, // availableRoutes
+ OFF, // speakerInteraction
+ NONE, // bluetoothInteraction
+ CallAudioRouteStateMachine.SWITCH_EARPIECE, // action
+ CallAudioState.ROUTE_EARPIECE, // expectedRoute
+ CallAudioState.ROUTE_EARPIECE // expectedAvailableRoutes
+ ));
+
+ params.add(new TestParameters(
+ "Switch to bluetooth from speakerphone", // name
+ CallAudioState.ROUTE_SPEAKER, // initialRoute
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // availableRoutes
+ OFF, // speakerInteraction
+ ON, // bluetoothInteraction
+ CallAudioRouteStateMachine.SWITCH_BLUETOOTH, // action
+ CallAudioState.ROUTE_BLUETOOTH, // expectedRoute
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH // expectedAvailableR
+ ));
+
+ params.add(new TestParameters(
+ "Switch to bluetooth from earpiece", // name
+ CallAudioState.ROUTE_EARPIECE, // initialRoute
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // availableRoutes
+ NONE, // speakerInteraction
+ ON, // bluetoothInteraction
+ CallAudioRouteStateMachine.SWITCH_BLUETOOTH, // action
+ CallAudioState.ROUTE_BLUETOOTH, // expectedRoute
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH // expectedAvailableR
+ ));
+
+ params.add(new TestParameters(
+ "Switch to bluetooth from wired headset", // name
+ CallAudioState.ROUTE_WIRED_HEADSET, // initialRoute
+ CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // availableRou
+ NONE, // speakerInteraction
+ ON, // bluetoothInteraction
+ CallAudioRouteStateMachine.SWITCH_BLUETOOTH, // action
+ CallAudioState.ROUTE_BLUETOOTH, // expectedRoute
+ CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH // expectedAvail
+ ));
+
+ return params;
+ }
+
+ private void runParametrizedTestCaseWithFocus(TestParameters params) {
+ resetMocks();
+
+ // Construct a fresh state machine on every case
+ CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
+ mContext,
+ mockCallsManager,
+ mockBluetoothManager,
+ mockWiredHeadsetManager,
+ mockStatusBarNotifier,
+ mAudioServiceFactory);
+
+ // Set up bluetooth and speakerphone state
+ when(mockBluetoothManager.isBluetoothAudioConnectedOrPending()).thenReturn(
+ params.initialRoute == CallAudioState.ROUTE_BLUETOOTH);
+ when(mockBluetoothManager.isBluetoothAvailable()).thenReturn(
+ (params.availableRoutes & CallAudioState.ROUTE_BLUETOOTH) != 0
+ || (params.expectedAvailableRoutes & CallAudioState.ROUTE_BLUETOOTH) != 0);
+ when(mockAudioManager.isSpeakerphoneOn()).thenReturn(
+ params.initialRoute == CallAudioState.ROUTE_SPEAKER);
+
+ // Set the initial CallAudioState object
+ CallAudioState initState = new CallAudioState(false,
+ params.initialRoute, (params.availableRoutes | CallAudioState.ROUTE_SPEAKER));
+ stateMachine.initialize(initState);
+ // Make the state machine have focus so that we actually do something
+ stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
+ CallAudioRouteStateMachine.HAS_FOCUS);
+ stateMachine.sendMessageWithSessionInfo(params.action);
+
+ // Verify interactions with the speakerphone and bluetooth systems
+ switch(params.bluetoothInteraction) {
+ case NONE:
+ verify(mockBluetoothManager, never()).disconnectBluetoothAudio();
+ verify(mockBluetoothManager, never()).connectBluetoothAudio();
+ break;
+ case ON:
+ verify(mockBluetoothManager, timeout(TEST_TIMEOUT)).connectBluetoothAudio();
+ verify(mockBluetoothManager, never()).disconnectBluetoothAudio();
+ break;
+ case OFF:
+ verify(mockBluetoothManager, never()).connectBluetoothAudio();
+ verify(mockBluetoothManager, timeout(TEST_TIMEOUT)).disconnectBluetoothAudio();
+ }
+
+ switch (params.speakerInteraction) {
+ case NONE:
+ verify(mockAudioManager, never()).setSpeakerphoneOn(any(Boolean.class));
+ break;
+ case ON: // fall through
+ case OFF:
+ verify(mockAudioManager, timeout(TEST_TIMEOUT)).setSpeakerphoneOn(
+ params.speakerInteraction == ON);
+ }
+
+ // Verify the end state
+ CallAudioState expectedState = new CallAudioState(false, params.expectedRoute,
+ params.expectedAvailableRoutes | CallAudioState.ROUTE_SPEAKER);
+ verifyNewSystemCallAudioState(initState, expectedState);
+ }
+
+ private void runParametrizedTestCaseWithoutFocus(TestParameters params) {
+ resetMocks();
+
+ // Construct a fresh state machine on every case
+ CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
+ mContext,
+ mockCallsManager,
+ mockBluetoothManager,
+ mockWiredHeadsetManager,
+ mockStatusBarNotifier,
+ mAudioServiceFactory);
+
+ // Set up bluetooth and speakerphone state
+ when(mockBluetoothManager.isBluetoothAvailable()).thenReturn(
+ (params.availableRoutes & CallAudioState.ROUTE_BLUETOOTH) != 0
+ || (params.expectedAvailableRoutes & CallAudioState.ROUTE_BLUETOOTH) != 0);
+ when(mockAudioManager.isSpeakerphoneOn()).thenReturn(
+ params.initialRoute == CallAudioState.ROUTE_SPEAKER);
+
+ // Set the initial CallAudioState object
+ CallAudioState initState = new CallAudioState(false,
+ params.initialRoute, (params.availableRoutes | CallAudioState.ROUTE_SPEAKER));
+ stateMachine.initialize(initState);
+ // Omit the focus-getting statement
+ stateMachine.sendMessageWithSessionInfo(params.action);
+ try {
+ Thread.sleep(100L);
+ } catch (InterruptedException e) {
+ // Just a pause to make sure the state machine handler thread has a chance to update
+ // its state. Do nothing.
+ }
+
+ // Verify that no substantive interactions have taken place with the rest of the system
+ verify(mockBluetoothManager, never()).disconnectBluetoothAudio();
+ verify(mockBluetoothManager, never()).connectBluetoothAudio();
+ verify(mockAudioManager, never()).setSpeakerphoneOn(any(Boolean.class));
+ verify(mockCallsManager, never()).onCallAudioStateChanged(any(CallAudioState.class),
+ any(CallAudioState.class));
+ verify(mockConnectionServiceWrapper, never()).onCallAudioStateChanged(
+ any(Call.class), any(CallAudioState.class));
+
+ // Verify the end state
+ CallAudioState expectedState = new CallAudioState(false, params.expectedRoute,
+ params.expectedAvailableRoutes | CallAudioState.ROUTE_SPEAKER);
+ assertEquals(expectedState, stateMachine.getCurrentCallAudioState());
+ }
+
+ private void verifyNewSystemCallAudioState(CallAudioState expectedOldState,
+ CallAudioState expectedNewState) {
+ ArgumentCaptor<CallAudioState> oldStateCaptor = ArgumentCaptor.forClass(
+ CallAudioState.class);
+ ArgumentCaptor<CallAudioState> newStateCaptor1 = ArgumentCaptor.forClass(
+ CallAudioState.class);
+ ArgumentCaptor<CallAudioState> newStateCaptor2 = ArgumentCaptor.forClass(
+ CallAudioState.class);
+ verify(mockCallsManager, timeout(TEST_TIMEOUT).atLeastOnce()).onCallAudioStateChanged(
+ oldStateCaptor.capture(), newStateCaptor1.capture());
+ verify(mockConnectionServiceWrapper, timeout(TEST_TIMEOUT).atLeastOnce())
+ .onCallAudioStateChanged(same(fakeCall), newStateCaptor2.capture());
+
+ assertTrue(oldStateCaptor.getValue().equals(expectedOldState));
+ assertTrue(newStateCaptor1.getValue().equals(expectedNewState));
+ assertTrue(newStateCaptor2.getValue().equals(expectedNewState));
+ }
+
+ private void resetMocks() {
+ reset(mockAudioManager, mockBluetoothManager, mockCallsManager,
+ mockConnectionServiceWrapper);
+ when(mockCallsManager.getForegroundCall()).thenReturn(fakeCall);
+ doNothing().when(mockConnectionServiceWrapper).onCallAudioStateChanged(any(Call.class),
+ any(CallAudioState.class));
+ }
+}
\ No newline at end of file
diff --git a/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java b/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java
new file mode 100644
index 0000000..f5ead6c
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java
@@ -0,0 +1,596 @@
+/*
+ * 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.server.telecom.tests;
+
+
+import android.content.ComponentName;
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.IContentProvider;
+import android.content.pm.UserInfo;
+import android.net.Uri;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.CallLog;
+import android.provider.CallLog.Calls;
+import android.telecom.DisconnectCause;
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
+import android.telecom.VideoProfile;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallLogManager;
+import com.android.server.telecom.CallState;
+import com.android.server.telecom.PhoneAccountRegistrar;
+import com.android.server.telecom.TelephonyUtil;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+
+import java.util.Arrays;
+
+public class CallLogManagerTest extends TelecomTestCase {
+
+ private CallLogManager mCallLogManager;
+ private IContentProvider mContentProvider;
+ private PhoneAccountHandle mDefaultAccountHandle;
+ private PhoneAccountHandle mOtherUserAccountHandle;
+ private PhoneAccountHandle mManagedProfileAccountHandle;
+
+ private static final Uri TEL_PHONEHANDLE = Uri.parse("tel:5555551234");
+
+ private static final PhoneAccountHandle EMERGENCY_ACCT_HANDLE = TelephonyUtil
+ .getDefaultEmergencyPhoneAccount()
+ .getAccountHandle();
+
+ private static final int NO_VIDEO_STATE = VideoProfile.STATE_AUDIO_ONLY;
+ private static final int BIDIRECTIONAL_VIDEO_STATE = VideoProfile.STATE_BIDIRECTIONAL;
+ private static final String POST_DIAL_STRING = ";12345";
+ private static final String TEST_PHONE_ACCOUNT_ID= "testPhoneAccountId";
+
+ private static final int TEST_TIMEOUT_MILLIS = 100;
+ private static final int CURRENT_USER_ID = 0;
+ private static final int OTHER_USER_ID = 10;
+ private static final int MANAGED_USER_ID = 11;
+
+ @Mock
+ PhoneAccountRegistrar mMockPhoneAccountRegistrar;
+
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
+ mCallLogManager = new CallLogManager(mContext, mMockPhoneAccountRegistrar);
+ mContentProvider = mContext.getContentResolver().acquireProvider("test");
+ mDefaultAccountHandle = new PhoneAccountHandle(
+ new ComponentName("com.android.server.telecom.tests", "CallLogManagerTest"),
+ TEST_PHONE_ACCOUNT_ID,
+ UserHandle.of(CURRENT_USER_ID)
+ );
+
+ mOtherUserAccountHandle = new PhoneAccountHandle(
+ new ComponentName("com.android.server.telecom.tests", "CallLogManagerTest"),
+ TEST_PHONE_ACCOUNT_ID,
+ UserHandle.of(OTHER_USER_ID)
+ );
+
+ mManagedProfileAccountHandle = new PhoneAccountHandle(
+ new ComponentName("com.android.server.telecom.tests", "CallLogManagerTest"),
+ TEST_PHONE_ACCOUNT_ID,
+ UserHandle.of(MANAGED_USER_ID)
+ );
+
+ UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ UserInfo userInfo = new UserInfo(CURRENT_USER_ID, "test", 0);
+ UserInfo otherUserInfo = new UserInfo(OTHER_USER_ID, "test2", 0);
+ UserInfo managedProfileUserInfo = new UserInfo(OTHER_USER_ID, "test3",
+ UserInfo.FLAG_MANAGED_PROFILE);
+
+ when(userManager.isUserRunning(any(UserHandle.class))).thenReturn(true);
+ when(userManager.hasUserRestriction(any(String.class), any(UserHandle.class)))
+ .thenReturn(false);
+ when(userManager.getUsers(any(Boolean.class)))
+ .thenReturn(Arrays.asList(userInfo, otherUserInfo, managedProfileUserInfo));
+ when(userManager.getUserInfo(eq(CURRENT_USER_ID))).thenReturn(userInfo);
+ when(userManager.getUserInfo(eq(OTHER_USER_ID))).thenReturn(otherUserInfo);
+ when(userManager.getUserInfo(eq(MANAGED_USER_ID))).thenReturn(managedProfileUserInfo);
+ }
+
+ public void testDontLogCancelledCall() {
+ Call fakeCall = makeFakeCall(
+ DisconnectCause.CANCELED,
+ false, // isConference
+ false, // isIncoming
+ 1L, // creationTimeMillis
+ 1000L, // ageMillis
+ TEL_PHONEHANDLE, // callHandle
+ mDefaultAccountHandle, // phoneAccountHandle
+ NO_VIDEO_STATE, // callVideoState
+ POST_DIAL_STRING, // postDialDigits,
+ UserHandle.of(CURRENT_USER_ID)
+ );
+ mCallLogManager.onCallStateChanged(fakeCall, CallState.DIALING, CallState.DISCONNECTED);
+ verifyNoInsertion();
+ mCallLogManager.onCallStateChanged(fakeCall, CallState.DIALING, CallState.ABORTED);
+ verifyNoInsertion();
+ }
+
+ public void testDontLogChoosingAccountCall() {
+ when(mMockPhoneAccountRegistrar.getPhoneAccountUnchecked(any(PhoneAccountHandle.class)))
+ .thenReturn(makeFakePhoneAccount(mDefaultAccountHandle, CURRENT_USER_ID));
+ Call fakeCall = makeFakeCall(
+ DisconnectCause.OTHER, // disconnectCauseCode
+ false, // isConference
+ false, // isIncoming
+ 1L, // creationTimeMillis
+ 1000L, // ageMillis
+ TEL_PHONEHANDLE, // callHandle
+ mDefaultAccountHandle, // phoneAccountHandle
+ NO_VIDEO_STATE, // callVideoState
+ POST_DIAL_STRING, // postDialDigits
+ UserHandle.of(CURRENT_USER_ID)
+ );
+ mCallLogManager.onCallStateChanged(fakeCall, CallState.SELECT_PHONE_ACCOUNT,
+ CallState.DISCONNECTED);
+ verifyNoInsertion();
+ }
+
+ public void testDontLogCallsFromEmergencyAccount() {
+ when(mMockPhoneAccountRegistrar.getPhoneAccountUnchecked(any(PhoneAccountHandle.class)))
+ .thenReturn(makeFakePhoneAccount(EMERGENCY_ACCT_HANDLE, 0));
+ mComponentContextFixture.putBooleanResource(R.bool.allow_emergency_numbers_in_call_log,
+ false);
+ Call fakeCall = makeFakeCall(
+ DisconnectCause.OTHER, // disconnectCauseCode
+ false, // isConference
+ false, // isIncoming
+ 1L, // creationTimeMillis
+ 1000L, // ageMillis
+ TEL_PHONEHANDLE, // callHandle
+ EMERGENCY_ACCT_HANDLE, // phoneAccountHandle
+ NO_VIDEO_STATE, // callVideoState
+ POST_DIAL_STRING, // postDialDigits,
+ UserHandle.of(CURRENT_USER_ID)
+ );
+ mCallLogManager.onCallStateChanged(fakeCall, CallState.ACTIVE, CallState.DISCONNECTED);
+ verifyNoInsertion();
+ }
+
+ public void testLogCallDirectionOutgoing() {
+ when(mMockPhoneAccountRegistrar.getPhoneAccountUnchecked(any(PhoneAccountHandle.class)))
+ .thenReturn(makeFakePhoneAccount(mDefaultAccountHandle, CURRENT_USER_ID));
+ Call fakeOutgoingCall = makeFakeCall(
+ DisconnectCause.OTHER, // disconnectCauseCode
+ false, // isConference
+ false, // isIncoming
+ 1L, // creationTimeMillis
+ 1000L, // ageMillis
+ TEL_PHONEHANDLE, // callHandle
+ mDefaultAccountHandle, // phoneAccountHandle
+ NO_VIDEO_STATE, // callVideoState
+ POST_DIAL_STRING, // postDialDigits
+ UserHandle.of(CURRENT_USER_ID)
+ );
+ mCallLogManager.onCallStateChanged(fakeOutgoingCall, CallState.ACTIVE,
+ CallState.DISCONNECTED);
+ ContentValues insertedValues = verifyInsertionWithCapture(CURRENT_USER_ID);
+ assertEquals(insertedValues.getAsInteger(CallLog.Calls.TYPE),
+ Integer.valueOf(CallLog.Calls.OUTGOING_TYPE));
+ }
+
+ public void testLogCallDirectionIncoming() {
+ when(mMockPhoneAccountRegistrar.getPhoneAccountUnchecked(any(PhoneAccountHandle.class)))
+ .thenReturn(makeFakePhoneAccount(mDefaultAccountHandle, CURRENT_USER_ID));
+ Call fakeIncomingCall = makeFakeCall(
+ DisconnectCause.OTHER, // disconnectCauseCode
+ false, // isConference
+ true, // isIncoming
+ 1L, // creationTimeMillis
+ 1000L, // ageMillis
+ TEL_PHONEHANDLE, // callHandle
+ mDefaultAccountHandle, // phoneAccountHandle
+ NO_VIDEO_STATE, // callVideoState
+ POST_DIAL_STRING, // postDialDigits
+ null
+ );
+ mCallLogManager.onCallStateChanged(fakeIncomingCall, CallState.ACTIVE,
+ CallState.DISCONNECTED);
+ ContentValues insertedValues = verifyInsertionWithCapture(CURRENT_USER_ID);
+ assertEquals(insertedValues.getAsInteger(CallLog.Calls.TYPE),
+ Integer.valueOf(CallLog.Calls.INCOMING_TYPE));
+ }
+
+ public void testLogCallDirectionMissed() {
+ when(mMockPhoneAccountRegistrar.getPhoneAccountUnchecked(any(PhoneAccountHandle.class)))
+ .thenReturn(makeFakePhoneAccount(mDefaultAccountHandle, CURRENT_USER_ID));
+ Call fakeMissedCall = makeFakeCall(
+ DisconnectCause.MISSED, // disconnectCauseCode
+ false, // isConference
+ true, // isIncoming
+ 1L, // creationTimeMillis
+ 1000L, // ageMillis
+ TEL_PHONEHANDLE, // callHandle
+ mDefaultAccountHandle, // phoneAccountHandle
+ NO_VIDEO_STATE, // callVideoState
+ POST_DIAL_STRING, // postDialDigits
+ null
+ );
+
+ mCallLogManager.onCallStateChanged(fakeMissedCall, CallState.ACTIVE,
+ CallState.DISCONNECTED);
+ ContentValues insertedValues = verifyInsertionWithCapture(CURRENT_USER_ID);
+ assertEquals(insertedValues.getAsInteger(CallLog.Calls.TYPE),
+ Integer.valueOf(CallLog.Calls.MISSED_TYPE));
+ }
+
+ public void testCreationTimeAndAge() {
+ when(mMockPhoneAccountRegistrar.getPhoneAccountUnchecked(any(PhoneAccountHandle.class)))
+ .thenReturn(makeFakePhoneAccount(mDefaultAccountHandle, CURRENT_USER_ID));
+ long currentTime = System.currentTimeMillis();
+ long duration = 1000L;
+ Call fakeCall = makeFakeCall(
+ DisconnectCause.OTHER, // disconnectCauseCode
+ false, // isConference
+ false, // isIncoming
+ currentTime, // creationTimeMillis
+ duration, // ageMillis
+ TEL_PHONEHANDLE, // callHandle
+ mDefaultAccountHandle, // phoneAccountHandle
+ NO_VIDEO_STATE, // callVideoState
+ POST_DIAL_STRING, // postDialDigits
+ UserHandle.of(CURRENT_USER_ID)
+ );
+ mCallLogManager.onCallStateChanged(fakeCall, CallState.ACTIVE, CallState.DISCONNECTED);
+ ContentValues insertedValues = verifyInsertionWithCapture(CURRENT_USER_ID);
+ assertEquals(insertedValues.getAsLong(CallLog.Calls.DATE),
+ Long.valueOf(currentTime));
+ assertEquals(insertedValues.getAsLong(CallLog.Calls.DURATION),
+ Long.valueOf(duration / 1000));
+ }
+
+ public void testLogPhoneAccountId() {
+ when(mMockPhoneAccountRegistrar.getPhoneAccountUnchecked(any(PhoneAccountHandle.class)))
+ .thenReturn(makeFakePhoneAccount(mDefaultAccountHandle, CURRENT_USER_ID));
+ Call fakeCall = makeFakeCall(
+ DisconnectCause.OTHER, // disconnectCauseCode
+ false, // isConference
+ true, // isIncoming
+ 1L, // creationTimeMillis
+ 1000L, // ageMillis
+ TEL_PHONEHANDLE, // callHandle
+ mDefaultAccountHandle, // phoneAccountHandle
+ NO_VIDEO_STATE, // callVideoState
+ POST_DIAL_STRING, // postDialDigits
+ UserHandle.of(CURRENT_USER_ID)
+ );
+ mCallLogManager.onCallStateChanged(fakeCall, CallState.ACTIVE, CallState.DISCONNECTED);
+ ContentValues insertedValues = verifyInsertionWithCapture(CURRENT_USER_ID);
+ assertEquals(insertedValues.getAsString(CallLog.Calls.PHONE_ACCOUNT_ID),
+ TEST_PHONE_ACCOUNT_ID);
+ }
+
+ public void testLogCorrectPhoneNumber() {
+ when(mMockPhoneAccountRegistrar.getPhoneAccountUnchecked(any(PhoneAccountHandle.class)))
+ .thenReturn(makeFakePhoneAccount(mDefaultAccountHandle, CURRENT_USER_ID));
+ Call fakeCall = makeFakeCall(
+ DisconnectCause.OTHER, // disconnectCauseCode
+ false, // isConference
+ true, // isIncoming
+ 1L, // creationTimeMillis
+ 1000L, // ageMillis
+ TEL_PHONEHANDLE, // callHandle
+ mDefaultAccountHandle, // phoneAccountHandle
+ NO_VIDEO_STATE, // callVideoState
+ POST_DIAL_STRING, // postDialDigits
+ UserHandle.of(CURRENT_USER_ID)
+ );
+ mCallLogManager.onCallStateChanged(fakeCall, CallState.ACTIVE, CallState.DISCONNECTED);
+ ContentValues insertedValues = verifyInsertionWithCapture(CURRENT_USER_ID);
+ assertEquals(insertedValues.getAsString(CallLog.Calls.NUMBER),
+ TEL_PHONEHANDLE.getSchemeSpecificPart());
+ assertEquals(insertedValues.getAsString(CallLog.Calls.POST_DIAL_DIGITS), POST_DIAL_STRING);
+ }
+
+ public void testLogCallVideoFeatures() {
+ when(mMockPhoneAccountRegistrar.getPhoneAccountUnchecked(any(PhoneAccountHandle.class)))
+ .thenReturn(makeFakePhoneAccount(mDefaultAccountHandle, CURRENT_USER_ID));
+ Call fakeVideoCall = makeFakeCall(
+ DisconnectCause.OTHER, // disconnectCauseCode
+ false, // isConference
+ true, // isIncoming
+ 1L, // creationTimeMillis
+ 1000L, // ageMillis
+ TEL_PHONEHANDLE, // callHandle
+ mDefaultAccountHandle, // phoneAccountHandle
+ BIDIRECTIONAL_VIDEO_STATE, // callVideoState
+ POST_DIAL_STRING, // postDialDigits
+ UserHandle.of(CURRENT_USER_ID)
+ );
+ mCallLogManager.onCallStateChanged(fakeVideoCall, CallState.ACTIVE, CallState.DISCONNECTED);
+ ContentValues insertedValues = verifyInsertionWithCapture(CURRENT_USER_ID);
+ assertTrue((insertedValues.getAsInteger(CallLog.Calls.FEATURES)
+ & CallLog.Calls.FEATURES_VIDEO) == CallLog.Calls.FEATURES_VIDEO);
+ }
+
+ public void testLogCallDirectionOutgoingWithMultiUserCapability() {
+ when(mMockPhoneAccountRegistrar.getPhoneAccountUnchecked(any(PhoneAccountHandle.class)))
+ .thenReturn(makeFakePhoneAccount(mOtherUserAccountHandle,
+ PhoneAccount.CAPABILITY_MULTI_USER));
+ Call fakeOutgoingCall = makeFakeCall(
+ DisconnectCause.OTHER, // disconnectCauseCode
+ false, // isConference
+ false, // isIncoming
+ 1L, // creationTimeMillis
+ 1000L, // ageMillis
+ TEL_PHONEHANDLE, // callHandle
+ mDefaultAccountHandle, // phoneAccountHandle
+ NO_VIDEO_STATE, // callVideoState
+ POST_DIAL_STRING, // postDialDigits
+ UserHandle.of(CURRENT_USER_ID)
+ );
+ mCallLogManager.onCallStateChanged(fakeOutgoingCall, CallState.ACTIVE,
+ CallState.DISCONNECTED);
+
+ // Outgoing call placed through a phone account with multi user capability is inserted to
+ // all users except managed profile.
+ ContentValues insertedValues = verifyInsertionWithCapture(CURRENT_USER_ID);
+ assertEquals(insertedValues.getAsInteger(CallLog.Calls.TYPE),
+ Integer.valueOf(CallLog.Calls.OUTGOING_TYPE));
+ insertedValues = verifyInsertionWithCapture(OTHER_USER_ID);
+ assertEquals(insertedValues.getAsInteger(CallLog.Calls.TYPE),
+ Integer.valueOf(CallLog.Calls.OUTGOING_TYPE));
+ verifyNoInsertionInUser(MANAGED_USER_ID);
+ }
+
+ public void testLogCallDirectionIncomingWithMultiUserCapability() {
+ when(mMockPhoneAccountRegistrar.getPhoneAccountUnchecked(any(PhoneAccountHandle.class)))
+ .thenReturn(makeFakePhoneAccount(mOtherUserAccountHandle,
+ PhoneAccount.CAPABILITY_MULTI_USER));
+ Call fakeIncomingCall = makeFakeCall(
+ DisconnectCause.OTHER, // disconnectCauseCode
+ false, // isConference
+ true, // isIncoming
+ 1L, // creationTimeMillis
+ 1000L, // ageMillis
+ TEL_PHONEHANDLE, // callHandle
+ mDefaultAccountHandle, // phoneAccountHandle
+ NO_VIDEO_STATE, // callVideoState
+ POST_DIAL_STRING, // postDialDigits
+ null
+ );
+ mCallLogManager.onCallStateChanged(fakeIncomingCall, CallState.ACTIVE,
+ CallState.DISCONNECTED);
+
+ // Incoming call using a phone account with multi user capability is inserted to all users
+ // except managed profile.
+ ContentValues insertedValues = verifyInsertionWithCapture(CURRENT_USER_ID);
+ assertEquals(insertedValues.getAsInteger(CallLog.Calls.TYPE),
+ Integer.valueOf(CallLog.Calls.INCOMING_TYPE));
+ insertedValues = verifyInsertionWithCapture(OTHER_USER_ID);
+ assertEquals(insertedValues.getAsInteger(CallLog.Calls.TYPE),
+ Integer.valueOf(CallLog.Calls.INCOMING_TYPE));
+ verifyNoInsertionInUser(MANAGED_USER_ID);
+ }
+
+
+ public void testLogCallDirectionOutgoingWithMultiUserCapabilityFromManagedProfile() {
+ when(mMockPhoneAccountRegistrar.getPhoneAccountUnchecked(any(PhoneAccountHandle.class)))
+ .thenReturn(makeFakePhoneAccount(mManagedProfileAccountHandle,
+ PhoneAccount.CAPABILITY_MULTI_USER));
+ Call fakeOutgoingCall = makeFakeCall(
+ DisconnectCause.OTHER, // disconnectCauseCode
+ false, // isConference
+ false, // isIncoming
+ 1L, // creationTimeMillis
+ 1000L, // ageMillis
+ TEL_PHONEHANDLE, // callHandle
+ mManagedProfileAccountHandle, // phoneAccountHandle
+ NO_VIDEO_STATE, // callVideoState
+ POST_DIAL_STRING, // postDialDigits
+ UserHandle.of(MANAGED_USER_ID)
+ );
+ mCallLogManager.onCallStateChanged(fakeOutgoingCall, CallState.ACTIVE,
+ CallState.DISCONNECTED);
+
+ // Outgoing call placed through work dialer should be inserted to managed profile only.
+ verifyNoInsertionInUser(CURRENT_USER_ID);
+ verifyNoInsertionInUser(OTHER_USER_ID);
+ ContentValues insertedValues = verifyInsertionWithCapture(MANAGED_USER_ID);
+ assertEquals(insertedValues.getAsInteger(CallLog.Calls.TYPE),
+ Integer.valueOf(CallLog.Calls.OUTGOING_TYPE));
+ }
+
+ public void testLogCallDirectionOutgoingFromManagedProfile() {
+ when(mMockPhoneAccountRegistrar.getPhoneAccountUnchecked(any(PhoneAccountHandle.class)))
+ .thenReturn(makeFakePhoneAccount(mManagedProfileAccountHandle, 0));
+ Call fakeOutgoingCall = makeFakeCall(
+ DisconnectCause.OTHER, // disconnectCauseCode
+ false, // isConference
+ false, // isIncoming
+ 1L, // creationTimeMillis
+ 1000L, // ageMillis
+ TEL_PHONEHANDLE, // callHandle
+ mManagedProfileAccountHandle, // phoneAccountHandle
+ NO_VIDEO_STATE, // callVideoState
+ POST_DIAL_STRING, // postDialDigits
+ UserHandle.of(MANAGED_USER_ID)
+ );
+ mCallLogManager.onCallStateChanged(fakeOutgoingCall, CallState.ACTIVE,
+ CallState.DISCONNECTED);
+
+ // Outgoing call using phone account in managed profile should be inserted to managed
+ // profile only.
+ verifyNoInsertionInUser(CURRENT_USER_ID);
+ verifyNoInsertionInUser(OTHER_USER_ID);
+ ContentValues insertedValues = verifyInsertionWithCapture(MANAGED_USER_ID);
+ assertEquals(insertedValues.getAsInteger(CallLog.Calls.TYPE),
+ Integer.valueOf(CallLog.Calls.OUTGOING_TYPE));
+ }
+
+ public void testLogCallDirectionIngoingFromManagedProfile() {
+ when(mMockPhoneAccountRegistrar.getPhoneAccountUnchecked(any(PhoneAccountHandle.class)))
+ .thenReturn(makeFakePhoneAccount(mManagedProfileAccountHandle, 0));
+ Call fakeOutgoingCall = makeFakeCall(
+ DisconnectCause.OTHER, // disconnectCauseCode
+ false, // isConference
+ true, // isIncoming
+ 1L, // creationTimeMillis
+ 1000L, // ageMillis
+ TEL_PHONEHANDLE, // callHandle
+ mManagedProfileAccountHandle, // phoneAccountHandle
+ NO_VIDEO_STATE, // callVideoState
+ POST_DIAL_STRING, // postDialDigits
+ null
+ );
+ mCallLogManager.onCallStateChanged(fakeOutgoingCall, CallState.ACTIVE,
+ CallState.DISCONNECTED);
+
+ // Incoming call using phone account in managed profile should be inserted to managed
+ // profile only.
+ verifyNoInsertionInUser(CURRENT_USER_ID);
+ verifyNoInsertionInUser(OTHER_USER_ID);
+ ContentValues insertedValues = verifyInsertionWithCapture(MANAGED_USER_ID);
+ assertEquals(insertedValues.getAsInteger(CallLog.Calls.TYPE),
+ Integer.valueOf(Calls.INCOMING_TYPE));
+ }
+
+ /**
+ * Ensure call data usage is persisted to the call log when present in the call.
+ */
+ public void testLogCallDataUsageSet() {
+ when(mMockPhoneAccountRegistrar.getPhoneAccountUnchecked(any(PhoneAccountHandle.class)))
+ .thenReturn(makeFakePhoneAccount(mDefaultAccountHandle, CURRENT_USER_ID));
+ Call fakeVideoCall = makeFakeCall(
+ DisconnectCause.OTHER, // disconnectCauseCode
+ false, // isConference
+ true, // isIncoming
+ 1L, // creationTimeMillis
+ 1000L, // ageMillis
+ TEL_PHONEHANDLE, // callHandle
+ mDefaultAccountHandle, // phoneAccountHandle
+ BIDIRECTIONAL_VIDEO_STATE, // callVideoState
+ POST_DIAL_STRING, // postDialDigits
+ UserHandle.of(CURRENT_USER_ID), // initiatingUser
+ 1000 // callDataUsage
+ );
+ mCallLogManager.onCallStateChanged(fakeVideoCall, CallState.ACTIVE, CallState.DISCONNECTED);
+ ContentValues insertedValues = verifyInsertionWithCapture(CURRENT_USER_ID);
+ assertEquals(Long.valueOf(1000), insertedValues.getAsLong(CallLog.Calls.DATA_USAGE));
+ }
+
+ /**
+ * Ensures call data usage is null in the call log when not set on the call.
+ */
+ public void testLogCallDataUsageNotSet() {
+ when(mMockPhoneAccountRegistrar.getPhoneAccountUnchecked(any(PhoneAccountHandle.class)))
+ .thenReturn(makeFakePhoneAccount(mDefaultAccountHandle, CURRENT_USER_ID));
+ Call fakeVideoCall = makeFakeCall(
+ DisconnectCause.OTHER, // disconnectCauseCode
+ false, // isConference
+ true, // isIncoming
+ 1L, // creationTimeMillis
+ 1000L, // ageMillis
+ TEL_PHONEHANDLE, // callHandle
+ mDefaultAccountHandle, // phoneAccountHandle
+ BIDIRECTIONAL_VIDEO_STATE, // callVideoState
+ POST_DIAL_STRING, // postDialDigits
+ UserHandle.of(CURRENT_USER_ID), // initiatingUser
+ Call.DATA_USAGE_NOT_SET // callDataUsage
+ );
+ mCallLogManager.onCallStateChanged(fakeVideoCall, CallState.ACTIVE, CallState.DISCONNECTED);
+ ContentValues insertedValues = verifyInsertionWithCapture(CURRENT_USER_ID);
+ assertNull(insertedValues.getAsLong(CallLog.Calls.DATA_USAGE));
+ }
+
+ private void verifyNoInsertion() {
+ try {
+ verify(mContentProvider, timeout(TEST_TIMEOUT_MILLIS).never()).insert(any(String.class),
+ any(Uri.class), any(ContentValues.class));
+ } catch (android.os.RemoteException e) {
+ fail("Remote exception occurred during test execution");
+ }
+ }
+
+
+ private void verifyNoInsertionInUser(int userId) {
+ try {
+ Uri uri = ContentProvider.maybeAddUserId(CallLog.Calls.CONTENT_URI, userId);
+ verify(mContentProvider, timeout(TEST_TIMEOUT_MILLIS).never()).insert(any(String.class),
+ eq(uri), any(ContentValues.class));
+ } catch (android.os.RemoteException e) {
+ fail("Remote exception occurred during test execution");
+ }
+ }
+
+ private ContentValues verifyInsertionWithCapture(int userId) {
+ ArgumentCaptor<ContentValues> captor = ArgumentCaptor.forClass(ContentValues.class);
+ try {
+ Uri uri = ContentProvider.maybeAddUserId(CallLog.Calls.CONTENT_URI, userId);
+ verify(mContentProvider, timeout(TEST_TIMEOUT_MILLIS)).insert(any(String.class),
+ eq(uri), captor.capture());
+ } catch (android.os.RemoteException e) {
+ fail("Remote exception occurred during test execution");
+ }
+
+ return captor.getValue();
+ }
+
+ private Call makeFakeCall(int disconnectCauseCode, boolean isConference, boolean isIncoming,
+ long creationTimeMillis, long ageMillis, Uri callHandle,
+ PhoneAccountHandle phoneAccountHandle, int callVideoState,
+ String postDialDigits, UserHandle initiatingUser) {
+ return makeFakeCall(disconnectCauseCode, isConference, isIncoming, creationTimeMillis,
+ ageMillis,
+ callHandle, phoneAccountHandle, callVideoState, postDialDigits, initiatingUser,
+ Call.DATA_USAGE_NOT_SET);
+ }
+
+ private Call makeFakeCall(int disconnectCauseCode, boolean isConference, boolean isIncoming,
+ long creationTimeMillis, long ageMillis, Uri callHandle,
+ PhoneAccountHandle phoneAccountHandle, int callVideoState,
+ String postDialDigits, UserHandle initiatingUser, long callDataUsage) {
+ Call fakeCall = mock(Call.class);
+ when(fakeCall.getDisconnectCause()).thenReturn(
+ new DisconnectCause(disconnectCauseCode));
+ when(fakeCall.isConference()).thenReturn(isConference);
+ when(fakeCall.isIncoming()).thenReturn(isIncoming);
+ when(fakeCall.getCreationTimeMillis()).thenReturn(creationTimeMillis);
+ when(fakeCall.getAgeMillis()).thenReturn(ageMillis);
+ when(fakeCall.getOriginalHandle()).thenReturn(callHandle);
+ when(fakeCall.getTargetPhoneAccount()).thenReturn(phoneAccountHandle);
+ when(fakeCall.getVideoStateHistory()).thenReturn(callVideoState);
+ when(fakeCall.getPostDialDigits()).thenReturn(postDialDigits);
+ when(fakeCall.getInitiatingUser()).thenReturn(initiatingUser);
+ when(fakeCall.getCallDataUsage()).thenReturn(callDataUsage);
+ return fakeCall;
+ }
+
+ private PhoneAccount makeFakePhoneAccount(PhoneAccountHandle phoneAccountHandle,
+ int capabilities) {
+ return PhoneAccount.builder(phoneAccountHandle, "testing")
+ .setCapabilities(capabilities).build();
+ }
+}
diff --git a/tests/src/com/android/server/telecom/tests/CallerInfoAsyncQueryFactoryFixture.java b/tests/src/com/android/server/telecom/tests/CallerInfoAsyncQueryFactoryFixture.java
index 1ebf170..ea18560 100644
--- a/tests/src/com/android/server/telecom/tests/CallerInfoAsyncQueryFactoryFixture.java
+++ b/tests/src/com/android/server/telecom/tests/CallerInfoAsyncQueryFactoryFixture.java
@@ -24,6 +24,7 @@
import android.content.Context;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
/**
@@ -55,7 +56,7 @@
}
};
- final List<Request> mRequests = new ArrayList<>();
+ final List<Request> mRequests = Collections.synchronizedList(new ArrayList<Request>());
public CallerInfoAsyncQueryFactoryFixture() throws Exception {
Log.i(this, "Creating ...");
diff --git a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
index 9f66f00..52ab512 100644
--- a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
+++ b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
@@ -53,6 +53,8 @@
import android.telecom.ConnectionService;
import android.telecom.InCallService;
import android.telecom.PhoneAccount;
+import android.telecom.TelecomManager;
+import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.test.mock.MockContext;
@@ -60,6 +62,7 @@
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
@@ -69,6 +72,7 @@
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
@@ -90,6 +94,21 @@
}
@Override
+ public String getPackageName() {
+ return "com.android.server.telecom.tests";
+ }
+
+ @Override
+ public String getPackageResourcePath() {
+ return "/tmp/i/dont/know";
+ }
+
+ @Override
+ public Context getApplicationContext() {
+ return mApplicationContextSpy;
+ }
+
+ @Override
public File getFilesDir() {
try {
return File.createTempFile("temp", "temp").getParentFile();
@@ -153,6 +172,10 @@
return mUserManager;
case Context.TELEPHONY_SUBSCRIPTION_SERVICE:
return mSubscriptionManager;
+ case Context.TELECOM_SERVICE:
+ return mTelecomManager;
+ case Context.CARRIER_CONFIG_SERVICE:
+ return mCarrierConfigManager;
default:
return null;
}
@@ -170,7 +193,7 @@
@Override
public String getOpPackageName() {
- return "test";
+ return "com.android.server.telecom.tests";
}
@Override
@@ -179,7 +202,7 @@
@Override
protected IContentProvider acquireProvider(Context c, String name) {
Log.i(this, "acquireProvider %s", name);
- return mock(IContentProvider.class);
+ return mContentProvider;
}
@Override
@@ -190,7 +213,7 @@
@Override
protected IContentProvider acquireUnstableProvider(Context c, String name) {
Log.i(this, "acquireUnstableProvider %s", name);
- return mock(IContentProvider.class);
+ return mContentProvider;
}
@Override
@@ -211,6 +234,12 @@
}
@Override
+ public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
+ String broadcastPermission, Handler scheduler) {
+ return null;
+ }
+
+ @Override
public void sendBroadcast(Intent intent) {
// TODO -- need to ensure this is captured
}
@@ -221,6 +250,10 @@
}
@Override
+ public void sendBroadcastAsUser(Intent intent, UserHandle userHandle) {
+ }
+
+ @Override
public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user,
String receiverPermission, BroadcastReceiver resultReceiver, Handler scheduler,
int initialCode, String initialData, Bundle initialExtras) {
@@ -238,13 +271,20 @@
throws PackageManager.NameNotFoundException {
return this;
}
+
+ @Override
+ public void enforceCallingOrSelfPermission(String permission, String message) {
+ // Don't bother enforcing anything in mock.
+ }
};
public class FakeAudioManager extends AudioManager {
private boolean mMute = false;
private boolean mSpeakerphoneOn = false;
+ private int mAudioStreamValue = 1;
private int mMode = AudioManager.MODE_NORMAL;
+ private int mRingerMode = AudioManager.RINGER_MODE_NORMAL;
public FakeAudioManager(Context context) {
super(context);
@@ -279,6 +319,26 @@
public int getMode() {
return mMode;
}
+
+ @Override
+ public void setRingerModeInternal(int ringerMode) {
+ mRingerMode = ringerMode;
+ }
+
+ @Override
+ public int getRingerModeInternal() {
+ return mRingerMode;
+ }
+
+ @Override
+ public void setStreamVolume(int streamTypeUnused, int index, int flagsUnused){
+ mAudioStreamValue = index;
+ }
+
+ @Override
+ public int getStreamVolume(int streamValueUnused) {
+ return mAudioStreamValue;
+ }
}
private final Multimap<String, ComponentName> mComponentNamesByAction =
@@ -317,8 +377,12 @@
private final UserManager mUserManager = mock(UserManager.class);
private final StatusBarManager mStatusBarManager = mock(StatusBarManager.class);
private final SubscriptionManager mSubscriptionManager = mock(SubscriptionManager.class);
+ private final CarrierConfigManager mCarrierConfigManager = mock(CarrierConfigManager.class);
+ private final IContentProvider mContentProvider = mock(IContentProvider.class);
private final Configuration mResourceConfiguration = new Configuration();
+ private TelecomManager mTelecomManager = null;
+
public ComponentContextFixture() {
MockitoAnnotations.initMocks(this);
when(mResources.getConfiguration()).thenReturn(mResourceConfiguration);
@@ -347,6 +411,8 @@
when(mTelephonyManager.getSubIdForPhoneAccount((PhoneAccount) any())).thenReturn(1);
+ when(mTelephonyManager.getNetworkOperatorName()).thenReturn("label1");
+
doAnswer(new Answer<Void>(){
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
@@ -357,6 +423,9 @@
when(mNotificationManager.matchesCallFilter(any(Bundle.class))).thenReturn(true);
when(mUserManager.getSerialNumberForUser(any(UserHandle.class))).thenReturn(-1L);
+
+ doReturn(null).when(mApplicationContextSpy).registerReceiver(any(BroadcastReceiver.class),
+ any(IntentFilter.class));
}
@Override
@@ -388,8 +457,24 @@
mServiceInfoByComponentName.put(componentName, serviceInfo);
}
- public void putResource(int id, String value) {
+ public void putResource(int id, final String value) {
+ when(mResources.getText(eq(id))).thenReturn(value);
when(mResources.getString(eq(id))).thenReturn(value);
+ when(mResources.getString(eq(id), any())).thenAnswer(new Answer<String>() {
+ @Override
+ public String answer(InvocationOnMock invocation) {
+ Object[] args = invocation.getArguments();
+ return String.format(value, Arrays.copyOfRange(args, 1, args.length));
+ }
+ });
+ }
+
+ public void putBooleanResource(int id, boolean value) {
+ when(mResources.getBoolean(eq(id))).thenReturn(value);
+ }
+
+ public void setTelecomManager(TelecomManager telecomManager) {
+ mTelecomManager = telecomManager;
}
private void addService(String action, ComponentName name, IInterface service) {
diff --git a/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java b/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
index bdcfb5b..0e16986 100644
--- a/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
+++ b/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
@@ -32,8 +32,10 @@
import android.os.IInterface;
import android.os.RemoteException;
import android.telecom.CallAudioState;
+import android.telecom.Conference;
import android.telecom.Connection;
import android.telecom.ConnectionRequest;
+import android.telecom.ConnectionService;
import android.telecom.DisconnectCause;
import android.telecom.ParcelableConference;
import android.telecom.ParcelableConnection;
@@ -48,14 +50,76 @@
import java.util.Map;
import java.util.Set;
-import static org.mockito.Matchers.any;
-
/**
* Controls a test {@link IConnectionService} as would be provided by a source of connectivity
* to the Telecom framework.
*/
public class ConnectionServiceFixture implements TestFixture<IConnectionService> {
+ /**
+ * Implementation of ConnectionService that performs no-ops for tasks normally meant for
+ * Telephony and reports success back to Telecom
+ */
+ public class FakeConnectionServiceDelegate extends ConnectionService {
+ @Override
+ public Connection onCreateUnknownConnection(
+ PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request) {
+ return new FakeConnection();
+ }
+
+ @Override
+ public Connection onCreateIncomingConnection(
+ PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request) {
+ return new FakeConnection();
+ }
+
+ @Override
+ public Connection onCreateOutgoingConnection(
+ PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request) {
+ return new FakeConnection();
+ }
+
+ @Override
+ public void onConference(Connection cxn1, Connection cxn2) {
+ // Usually, this is implemented by something in Telephony, which does a bunch of radio
+ // work to conference the two connections together. Here we just short-cut that and
+ // declare them conferenced.
+ Conference fakeConference = new FakeConference();
+ fakeConference.addConnection(cxn1);
+ fakeConference.addConnection(cxn2);
+ addConference(fakeConference);
+ }
+ }
+
+ public class FakeConnection extends Connection {
+ public FakeConnection() {
+ super();
+ int capabilities = getConnectionCapabilities();
+ capabilities |= CAPABILITY_MUTE;
+ capabilities |= CAPABILITY_SUPPORT_HOLD;
+ capabilities |= CAPABILITY_HOLD;
+ setConnectionCapabilities(capabilities);
+ setActive();
+ }
+ }
+
+ public class FakeConference extends Conference {
+ public FakeConference() {
+ super(null);
+ setConnectionCapabilities(
+ Connection.CAPABILITY_SUPPORT_HOLD
+ | Connection.CAPABILITY_HOLD
+ | Connection.CAPABILITY_MUTE
+ | Connection.CAPABILITY_MANAGE_CONFERENCE);
+ }
+
+ @Override
+ public void onMerge(Connection connection) {
+ // Do nothing besides inform the connection that it was merged into this conference.
+ connection.setConference(this);
+ }
+ }
+
public class FakeConnectionService extends IConnectionService.Stub {
@Override
@@ -64,6 +128,7 @@
if (!mConnectionServiceAdapters.add(adapter)) {
throw new RuntimeException("Adapter already added: " + adapter);
}
+ mConnectionServiceDelegateAdapter.addConnectionServiceAdapter(adapter);
}
@Override
@@ -72,6 +137,7 @@
if (!mConnectionServiceAdapters.remove(adapter)) {
throw new RuntimeException("Adapter never added: " + adapter);
}
+ mConnectionServiceDelegateAdapter.removeConnectionServiceAdapter(adapter);
}
@Override
@@ -93,6 +159,8 @@
c.isUnknown = isUnknown;
c.capabilities |= Connection.CAPABILITY_HOLD | Connection.CAPABILITY_SUPPORT_HOLD;
mConnectionById.put(id, c);
+ mConnectionServiceDelegateAdapter.createConnection(connectionManagerPhoneAccount,
+ id, request, isIncoming, isUnknown);
}
@Override
@@ -133,7 +201,9 @@
public void stopDtmfTone(String callId) throws RemoteException { }
@Override
- public void conference(String conferenceCallId, String callId) throws RemoteException { }
+ public void conference(String conferenceCallId, String callId) throws RemoteException {
+ mConnectionServiceDelegateAdapter.conference(conferenceCallId, callId);
+ }
@Override
public void splitFromConference(String callId) throws RemoteException { }
@@ -156,7 +226,12 @@
public IInterface queryLocalInterface(String descriptor) {
return this;
}
- };
+ }
+
+ private FakeConnectionServiceDelegate mConnectionServiceDelegate =
+ new FakeConnectionServiceDelegate();
+ private IConnectionService mConnectionServiceDelegateAdapter =
+ IConnectionService.Stub.asInterface(mConnectionServiceDelegate.onBind(null));
private IConnectionService.Stub mConnectionService = new FakeConnectionService();
private IConnectionService.Stub mConnectionServiceSpy = Mockito.spy(mConnectionService);
diff --git a/tests/src/com/android/server/telecom/tests/CreateConnectionProcessorTest.java b/tests/src/com/android/server/telecom/tests/CreateConnectionProcessorTest.java
new file mode 100644
index 0000000..e8bd05c
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/CreateConnectionProcessorTest.java
@@ -0,0 +1,345 @@
+/*
+ * 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.server.telecom.tests;
+
+import android.content.ComponentName;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Debug;
+import android.telecom.DisconnectCause;
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallIdMapper;
+import com.android.server.telecom.ConnectionServiceRepository;
+import com.android.server.telecom.ConnectionServiceWrapper;
+import com.android.server.telecom.CreateConnectionProcessor;
+import com.android.server.telecom.CreateConnectionResponse;
+import com.android.server.telecom.PhoneAccountRegistrar;
+
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * Unit testing for CreateConnectionProcessor as well as CreateConnectionTimeout classes.
+ */
+public class CreateConnectionProcessorTest extends TelecomTestCase {
+
+ private static final String TEST_PACKAGE = "com.android.server.telecom.tests";
+ private static final String TEST_CLASS =
+ "com.android.server.telecom.tests.MockConnectionService";
+
+ @Mock
+ ConnectionServiceRepository mMockConnectionServiceRepository;
+ @Mock
+ PhoneAccountRegistrar mMockAccountRegistrar;
+ @Mock
+ CreateConnectionResponse mMockCreateConnectionResponse;
+ @Mock
+ Call mMockCall;
+
+ CreateConnectionProcessor mTestCreateConnectionProcessor;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ MockitoAnnotations.initMocks(this);
+ mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
+
+ mTestCreateConnectionProcessor = new CreateConnectionProcessor(mMockCall,
+ mMockConnectionServiceRepository, mMockCreateConnectionResponse,
+ mMockAccountRegistrar, mContext);
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ mTestCreateConnectionProcessor = null;
+ super.tearDown();
+ }
+
+ public void testSimPhoneAccountSuccess() throws Exception {
+ PhoneAccountHandle pAHandle = getNewTargetPhoneAccountHandle("tel_acct");
+ when(mMockCall.isEmergencyCall()).thenReturn(false);
+ // No Connection Manager in this case
+ when(mMockAccountRegistrar.getSimCallManagerFromCall(any(Call.class))).thenReturn(null);
+ ConnectionServiceWrapper service = makeConnectionServiceWrapper();
+
+ mTestCreateConnectionProcessor.process();
+
+ verify(mMockCall).setConnectionManagerPhoneAccount(eq(pAHandle));
+ verify(mMockCall).setTargetPhoneAccount(eq(pAHandle));
+ verify(mMockCall).setConnectionService(eq(service));
+ verify(service).createConnection(eq(mMockCall), any(CreateConnectionResponse.class));
+ // Notify successful connection to call
+ CallIdMapper mockCallIdMapper = mock(CallIdMapper.class);
+ mTestCreateConnectionProcessor.handleCreateConnectionSuccess(mockCallIdMapper, null);
+ verify(mMockCreateConnectionResponse).handleCreateConnectionSuccess(mockCallIdMapper, null);
+ }
+
+ public void testbadPhoneAccount() throws Exception {
+ PhoneAccountHandle pAHandle = null;
+ when(mMockCall.isEmergencyCall()).thenReturn(false);
+ when(mMockCall.getTargetPhoneAccount()).thenReturn(pAHandle);
+ givePhoneAccountBindPermission(pAHandle);
+ // No Connection Manager in this case
+ when(mMockAccountRegistrar.getSimCallManagerFromCall(any(Call.class))).thenReturn(null);
+ ConnectionServiceWrapper service = makeConnectionServiceWrapper();
+
+ mTestCreateConnectionProcessor.process();
+
+ verify(service, never()).createConnection(eq(mMockCall),
+ any(CreateConnectionResponse.class));
+ verify(mMockCreateConnectionResponse).handleCreateConnectionFailure(
+ eq(new DisconnectCause(DisconnectCause.ERROR)));
+ }
+
+ public void testConnectionManagerSuccess() throws Exception {
+ PhoneAccountHandle pAHandle = getNewTargetPhoneAccountHandle("tel_acct");
+ when(mMockCall.isEmergencyCall()).thenReturn(false);
+ // Include a Connection Manager
+ PhoneAccountHandle callManagerPAHandle = getNewConnectionMangerHandle("cm_acct");
+ ConnectionServiceWrapper service = makeConnectionServiceWrapper();
+ // Make sure the target phone account has the correct permissions
+ PhoneAccount mFakeTargetPhoneAccount = makeQuickAccount("cm_acct",
+ PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION);
+ when(mMockAccountRegistrar.getPhoneAccountUnchecked(pAHandle)).thenReturn(
+ mFakeTargetPhoneAccount);
+
+ mTestCreateConnectionProcessor.process();
+
+ verify(mMockCall).setConnectionManagerPhoneAccount(eq(callManagerPAHandle));
+ verify(mMockCall).setTargetPhoneAccount(eq(pAHandle));
+ verify(mMockCall).setConnectionService(eq(service));
+ verify(service).createConnection(eq(mMockCall), any(CreateConnectionResponse.class));
+ // Notify successful connection to call
+ CallIdMapper mockCallIdMapper = mock(CallIdMapper.class);
+ mTestCreateConnectionProcessor.handleCreateConnectionSuccess(mockCallIdMapper, null);
+ verify(mMockCreateConnectionResponse).handleCreateConnectionSuccess(mockCallIdMapper, null);
+ }
+
+ public void testConnectionManagerFailedFallToSim() throws Exception {
+ PhoneAccountHandle pAHandle = getNewTargetPhoneAccountHandle("tel_acct");
+ when(mMockCall.isEmergencyCall()).thenReturn(false);
+ // Include a Connection Manager
+ PhoneAccountHandle callManagerPAHandle = getNewConnectionMangerHandle("cm_acct");
+ ConnectionServiceWrapper service = makeConnectionServiceWrapper();
+ when(mMockCall.getConnectionManagerPhoneAccount()).thenReturn(callManagerPAHandle);
+ PhoneAccount mFakeTargetPhoneAccount = makeQuickAccount("cm_acct",
+ PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION);
+ when(mMockAccountRegistrar.getPhoneAccountUnchecked(pAHandle)).thenReturn(
+ mFakeTargetPhoneAccount);
+ when(mMockCall.getConnectionService()).thenReturn(service);
+ // Put CreateConnectionProcessor in correct state to fail with ConnectionManager
+ mTestCreateConnectionProcessor.process();
+ reset(mMockCall);
+ reset(service);
+
+ // Notify that the ConnectionManager has denied the call.
+ when(mMockCall.getConnectionManagerPhoneAccount()).thenReturn(callManagerPAHandle);
+ when(mMockCall.getConnectionService()).thenReturn(service);
+ mTestCreateConnectionProcessor.handleCreateConnectionFailure(
+ new DisconnectCause(DisconnectCause.CONNECTION_MANAGER_NOT_SUPPORTED));
+
+ // Verify that the Sim Phone Account is used correctly
+ verify(mMockCall).setConnectionManagerPhoneAccount(eq(pAHandle));
+ verify(mMockCall).setTargetPhoneAccount(eq(pAHandle));
+ verify(mMockCall).setConnectionService(eq(service));
+ verify(service).createConnection(eq(mMockCall), any(CreateConnectionResponse.class));
+ // Notify successful connection to call
+ CallIdMapper mockCallIdMapper = mock(CallIdMapper.class);
+ mTestCreateConnectionProcessor.handleCreateConnectionSuccess(mockCallIdMapper, null);
+ verify(mMockCreateConnectionResponse).handleCreateConnectionSuccess(mockCallIdMapper, null);
+ }
+
+ public void testConnectionManagerFailedDoNotFallToSim() throws Exception {
+ PhoneAccountHandle pAHandle = getNewTargetPhoneAccountHandle("tel_acct");
+ when(mMockCall.isEmergencyCall()).thenReturn(false);
+ // Include a Connection Manager
+ PhoneAccountHandle callManagerPAHandle = getNewConnectionMangerHandle("cm_acct");
+ ConnectionServiceWrapper service = makeConnectionServiceWrapper();
+ when(mMockCall.getConnectionManagerPhoneAccount()).thenReturn(callManagerPAHandle);
+ PhoneAccount mFakeTargetPhoneAccount = makeQuickAccount("cm_acct",
+ PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION);
+ when(mMockAccountRegistrar.getPhoneAccountUnchecked(pAHandle)).thenReturn(
+ mFakeTargetPhoneAccount);
+ when(mMockCall.getConnectionService()).thenReturn(service);
+ // Put CreateConnectionProcessor in correct state to fail with ConnectionManager
+ mTestCreateConnectionProcessor.process();
+ reset(mMockCall);
+ reset(service);
+
+ // Notify that the ConnectionManager has rejected the call.
+ when(mMockCall.getConnectionManagerPhoneAccount()).thenReturn(callManagerPAHandle);
+ when(mMockCall.getConnectionService()).thenReturn(service);
+ when(service.isServiceValid("createConnection")).thenReturn(true);
+ mTestCreateConnectionProcessor.handleCreateConnectionFailure(
+ new DisconnectCause(DisconnectCause.OTHER));
+
+ // Verify call connection rejected
+ verify(mMockCreateConnectionResponse).handleCreateConnectionFailure(
+ new DisconnectCause(DisconnectCause.OTHER));
+ }
+
+ public void testEmergencyCallToSim() throws Exception {
+ when(mMockCall.isEmergencyCall()).thenReturn(true);
+ // Put in a regular phone account to be sure it doesn't call that
+ PhoneAccountHandle pAHandle = getNewTargetPhoneAccountHandle("tel_acct");
+ // Include a Connection Manager to be sure it doesn't call that
+ PhoneAccount callManagerPA = getNewConnectionManagerPhoneAccount("cm_acct", 0);
+ ConnectionServiceWrapper service = makeConnectionServiceWrapper();
+ PhoneAccount emergencyPhoneAccount = makeEmergencyPhoneAccount("tel_emer");
+ PhoneAccountHandle emergencyPhoneAccountHandle = emergencyPhoneAccount.getAccountHandle();
+
+ mTestCreateConnectionProcessor.process();
+
+ verify(mMockCall).setConnectionManagerPhoneAccount(eq(emergencyPhoneAccountHandle));
+ verify(mMockCall).setTargetPhoneAccount(eq(emergencyPhoneAccountHandle));
+ verify(mMockCall).setConnectionService(eq(service));
+ verify(service).createConnection(eq(mMockCall), any(CreateConnectionResponse.class));
+ // Notify successful connection to call
+ CallIdMapper mockCallIdMapper = mock(CallIdMapper.class);
+ mTestCreateConnectionProcessor.handleCreateConnectionSuccess(mockCallIdMapper, null);
+ verify(mMockCreateConnectionResponse).handleCreateConnectionSuccess(mockCallIdMapper, null);
+ }
+
+ public void testEmergencyCallSimFailToConnectionManager() throws Exception {
+ when(mMockCall.isEmergencyCall()).thenReturn(true);
+ when(mMockCall.getHandle()).thenReturn(Uri.parse(""));
+ // Put in a regular phone account to be sure it doesn't call that
+ PhoneAccountHandle pAHandle = getNewTargetPhoneAccountHandle("tel_acct");
+ when(mMockAccountRegistrar.getOutgoingPhoneAccountForSchemeOfCurrentUser(
+ any(String.class))).thenReturn(pAHandle);
+ // Include a normal Connection Manager to be sure it doesn't call that
+ PhoneAccount callManagerPA = getNewConnectionManagerPhoneAccount("cm_acct", 0);
+ // Include a connection Manager for the user with the capability to make calls
+ PhoneAccount emerCallManagerPA = getNewEmergencyConnectionManagerPhoneAccount("cm_acct",
+ PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS);
+ ConnectionServiceWrapper service = makeConnectionServiceWrapper();
+ PhoneAccount emergencyPhoneAccount = makeEmergencyPhoneAccount("tel_emer");
+ mTestCreateConnectionProcessor.process();
+ reset(mMockCall);
+ reset(service);
+ when(mMockCall.isEmergencyCall()).thenReturn(true);
+
+ // When Notify SIM connection fails, fall back to connection manager
+ mTestCreateConnectionProcessor.handleCreateConnectionFailure(new DisconnectCause(
+ DisconnectCause.REJECTED));
+
+ verify(mMockCall).setConnectionManagerPhoneAccount(
+ eq(emerCallManagerPA.getAccountHandle()));
+ verify(mMockCall).setTargetPhoneAccount(eq(pAHandle));
+ verify(mMockCall).setConnectionService(eq(service));
+ verify(service).createConnection(eq(mMockCall), any(CreateConnectionResponse.class));
+ }
+
+ private PhoneAccount makeEmergencyPhoneAccount(String id) {
+ final PhoneAccount emergencyPhoneAccount = makeQuickAccount(id,
+ PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS |
+ PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION);
+ PhoneAccountHandle emergencyPhoneAccountHandle = emergencyPhoneAccount.getAccountHandle();
+ givePhoneAccountBindPermission(emergencyPhoneAccountHandle);
+ ArrayList<PhoneAccount> phoneAccounts = new ArrayList<PhoneAccount>() {{
+ add(emergencyPhoneAccount);
+ }};
+ when(mMockAccountRegistrar.getAllPhoneAccountsOfCurrentUser()).thenReturn(phoneAccounts);
+ return emergencyPhoneAccount;
+ }
+
+ private void givePhoneAccountBindPermission(PhoneAccountHandle handle) {
+ when(mMockAccountRegistrar.phoneAccountRequiresBindPermission(eq(handle))).thenReturn(true);
+ }
+
+ private PhoneAccountHandle getNewConnectionMangerHandle(String id) {
+ PhoneAccountHandle callManagerPAHandle = makeQuickAccountHandle(id);
+ when(mMockAccountRegistrar.getSimCallManagerFromCall(any(Call.class))).thenReturn(
+ callManagerPAHandle);
+ givePhoneAccountBindPermission(callManagerPAHandle);
+ return callManagerPAHandle;
+ }
+
+ private PhoneAccountHandle getNewTargetPhoneAccountHandle(String id) {
+ PhoneAccountHandle pAHandle = makeQuickAccountHandle(id);
+ when(mMockCall.getTargetPhoneAccount()).thenReturn(pAHandle);
+ givePhoneAccountBindPermission(pAHandle);
+ return pAHandle;
+ }
+
+ private PhoneAccount getNewConnectionManagerPhoneAccount(String id, int capability) {
+ PhoneAccount callManagerPA = makeQuickAccount(id, capability);
+ when(mMockAccountRegistrar.getSimCallManagerFromCall(any(Call.class))).thenReturn(
+ callManagerPA.getAccountHandle());
+ givePhoneAccountBindPermission(callManagerPA.getAccountHandle());
+ when(mMockAccountRegistrar.getPhoneAccountUnchecked(
+ callManagerPA.getAccountHandle())).thenReturn(callManagerPA);
+ return callManagerPA;
+ }
+
+ private PhoneAccount getNewEmergencyConnectionManagerPhoneAccount(String id, int capability) {
+ PhoneAccount callManagerPA = makeQuickAccount(id, capability);
+ when(mMockAccountRegistrar.getSimCallManagerOfCurrentUser()).thenReturn(
+ callManagerPA.getAccountHandle());
+ givePhoneAccountBindPermission(callManagerPA.getAccountHandle());
+ when(mMockAccountRegistrar.getPhoneAccountUnchecked(
+ callManagerPA.getAccountHandle())).thenReturn(callManagerPA);
+ return callManagerPA;
+ }
+
+ private static ComponentName makeQuickConnectionServiceComponentName() {
+ return new ComponentName(TEST_PACKAGE, TEST_CLASS);
+ }
+
+ private ConnectionServiceWrapper makeConnectionServiceWrapper() {
+ ConnectionServiceWrapper wrapper = mock(ConnectionServiceWrapper.class);
+ when(mMockConnectionServiceRepository.getService(
+ eq(makeQuickConnectionServiceComponentName()),
+ eq(Binder.getCallingUserHandle()))).thenReturn(wrapper);
+ return wrapper;
+ }
+
+ private static PhoneAccountHandle makeQuickAccountHandle(String id) {
+ return new PhoneAccountHandle(makeQuickConnectionServiceComponentName(), id,
+ Binder.getCallingUserHandle());
+ }
+
+ private PhoneAccount.Builder makeQuickAccountBuilder(String id, int idx) {
+ return new PhoneAccount.Builder(makeQuickAccountHandle(id), "label" + idx);
+ }
+
+ private PhoneAccount makeQuickAccount(String id, int idx) {
+ return makeQuickAccountBuilder(id, idx)
+ .setAddress(Uri.parse("http://foo.com/" + idx))
+ .setSubscriptionAddress(Uri.parse("tel:555-000" + idx))
+ .setCapabilities(idx)
+ .setIcon(Icon.createWithResource(
+ "com.android.server.telecom.tests", R.drawable.stat_sys_phone_call))
+ .setShortDescription("desc" + idx)
+ .setIsEnabled(true)
+ .build();
+ }
+}
\ No newline at end of file
diff --git a/tests/src/com/android/server/telecom/tests/InCallServiceFixture.java b/tests/src/com/android/server/telecom/tests/InCallServiceFixture.java
index 2dd4b97..3790c8b 100644
--- a/tests/src/com/android/server/telecom/tests/InCallServiceFixture.java
+++ b/tests/src/com/android/server/telecom/tests/InCallServiceFixture.java
@@ -128,4 +128,8 @@
public ParcelableCall getCall(String id) {
return mCallById.get(id);
}
+
+ public IInCallAdapter getInCallAdapter() {
+ return mInCallAdapter;
+ }
}
diff --git a/tests/src/com/android/server/telecom/tests/LogTest.java b/tests/src/com/android/server/telecom/tests/LogTest.java
new file mode 100644
index 0000000..673582a
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/LogTest.java
@@ -0,0 +1,512 @@
+/*
+ * 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.server.telecom.tests;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
+
+import com.android.internal.os.SomeArgs;
+import com.android.server.telecom.Session;
+import com.android.server.telecom.SystemLoggingContainer;
+import com.android.server.telecom.Log;
+
+import org.junit.Assert;
+import org.mockito.MockitoAnnotations;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Random;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.verify;
+
+/**
+ * Unit tests for Telecom's Logging system.
+ */
+public class LogTest extends TelecomTestCase{
+
+ /**
+ * This helper class captures the logs that are sent to Log and stores them in an array to be
+ * verified by LogTest.
+ */
+ private class TestLoggingContainer extends SystemLoggingContainer {
+ public ArrayList<String> receivedStrings;
+
+ public TestLoggingContainer() {
+ receivedStrings = new ArrayList<>(100);
+ }
+
+ @Override
+ public synchronized void v(String msgTag, String msg) {
+ if (msgTag.equals(LogTest.TESTING_TAG)) {
+ synchronized (this) {
+ receivedStrings.add(msg);
+ }
+ }
+ }
+
+ @Override
+ public synchronized void i(String msgTag, String msg) {
+ if (msgTag.equals(LogTest.TESTING_TAG)) {
+ synchronized (this) {
+ receivedStrings.add(msg);
+ }
+ }
+ }
+
+ public boolean didReceiveMessage(int timeoutMs, String msg) {
+ String matchedString = null;
+ // Wait for timeout to expire before checking received messages
+ if (timeoutMs > 0) {
+ try {
+ Thread.sleep(timeoutMs);
+ } catch (InterruptedException e) {
+ Log.w(LogTest.TESTING_TAG, "TestLoggingContainer: Thread Interrupted!");
+ }
+ }
+ synchronized (this) {
+ for (String receivedString : receivedStrings) {
+ if (receivedString.contains(msg)) {
+ matchedString = receivedString;
+ break;
+ }
+ }
+ if (matchedString != null) {
+ receivedStrings.remove(matchedString);
+ return true;
+ }
+ }
+ android.util.Log.i(TESTING_TAG, "Did not receive message: " + msg);
+ return false;
+ }
+
+ public boolean isMessagesEmpty() {
+ boolean isEmpty = receivedStrings.isEmpty();
+ if (!isEmpty) {
+ printMessagesThatAreLeft();
+ }
+ return isEmpty;
+ }
+
+ public synchronized void printMessagesThatAreLeft() {
+ android.util.Log.i(TESTING_TAG, "Remaining Messages in Log Queue:");
+ for (String receivedString : receivedStrings) {
+ android.util.Log.i(TESTING_TAG, "\t- " + receivedString);
+ }
+ }
+ }
+
+ public static final int TEST_THREAD_COUNT = 150;
+ public static final int TEST_SLEEP_TIME_MS = 50;
+ // Should be larger than TEST_SLEEP_TIME_MS!
+ public static final int TEST_VERIFY_TIMEOUT_MS = 100;
+ public static final String TEST_ENTER_METHOD1 = "TEM1";
+ public static final String TEST_ENTER_METHOD2 = "TEM2";
+ public static final String TEST_ENTER_METHOD3 = "TEM3";
+ public static final String TEST_ENTER_METHOD4 = "TEM4";
+ public static final String TEST_CLASS_NAME = "LogTest";
+
+ private static final int EVENT_START_TEST_SLEEPY_METHOD = 0;
+ private static final int EVENT_START_TEST_SLEEPY_MULTIPLE_METHOD = 1;
+ private static final int EVENT_LAST_MESSAGE = 2;
+
+ private static final long RANDOM_NUMBER_SEED = 6191991;
+
+ Random rng = new Random(RANDOM_NUMBER_SEED);
+
+ private Handler mSleepyHandler = new Handler(
+ new HandlerThread("sleepyThread"){{start();}}.getLooper()) {
+ @Override
+ public void handleMessage(Message msg) {
+ SomeArgs args = (SomeArgs) msg.obj;
+ Session subsession = (Session) args.arg1;
+ Log.continueSession(subsession, "lTSH.hM");
+ switch (msg.what) {
+ case EVENT_START_TEST_SLEEPY_METHOD:
+ sleepyMethod(TEST_SLEEP_TIME_MS);
+ break;
+ }
+ Log.endSession();
+ }
+ };
+
+ private boolean isHandlerCompleteWithEvents;
+ private Handler mSleepyMultipleHandler = new Handler(
+ new HandlerThread("sleepyMultipleThread"){{start();}}.getLooper()){
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case EVENT_START_TEST_SLEEPY_MULTIPLE_METHOD:
+ SomeArgs args = (SomeArgs) msg.obj;
+ Session subsession = (Session) args.arg1;
+ Log.continueSession(subsession, "lTSCH.hM");
+ sleepyMultipleMethod();
+ Log.endSession();
+ break;
+ case EVENT_LAST_MESSAGE:
+ isHandlerCompleteWithEvents = true;
+ break;
+ }
+ }
+ };
+
+ private AtomicInteger mCompleteCount;
+ class LogTestRunnable implements Runnable {
+ private String mshortMethodName;
+ public LogTestRunnable(String shortMethodName) {
+ mshortMethodName = shortMethodName;
+ }
+
+ public void run() {
+ Log.startSession(mshortMethodName);
+ sleepyCallerMethod(TEST_SLEEP_TIME_MS);
+ Log.endSession();
+ mCompleteCount.incrementAndGet();
+ }
+ }
+
+ TestLoggingContainer mTestSystemLogger;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ MockitoAnnotations.initMocks(this);
+ mTestSystemLogger = new TestLoggingContainer();
+ Log.setLoggingContainer(mTestSystemLogger);
+ Log.restartSessionCounter();
+ Log.sCleanStaleSessions = null;
+ Log.sSessionMapper.clear();
+ Log.setContext(mComponentContextFixture.getTestDouble().getApplicationContext());
+ Log.sSessionCleanupTimeoutMs = new Log.ISessionCleanupTimeoutMs() {
+ @Override
+ // Set to the default value of Timeouts.getStaleSessionCleanupTimeoutMillis without
+ // needing to query.
+ public long get() {
+ return 30000;
+ }
+ };
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ mTestSystemLogger = null;
+ Log.setLoggingContainer(new SystemLoggingContainer());
+ super.tearDown();
+ }
+
+ public void testSingleThreadSession() throws Exception {
+ String sessionName = "LT.sTS";
+ Log.startSession(sessionName);
+ sleepyMethod(TEST_SLEEP_TIME_MS);
+ Log.endSession();
+
+ verifyEventResult(Session.START_SESSION, sessionName, "", 0, TEST_VERIFY_TIMEOUT_MS);
+ verifyMethodCall("", sessionName, 0, "", TEST_ENTER_METHOD1, TEST_VERIFY_TIMEOUT_MS);
+ verifyEventResult(Session.END_SUBSESSION, sessionName, "", 0, TEST_VERIFY_TIMEOUT_MS);
+ verifyEndEventResult(sessionName, "", 0, TEST_VERIFY_TIMEOUT_MS);
+
+ assertEquals(Log.sSessionMapper.size(), 0);
+ assertEquals(true, mTestSystemLogger.isMessagesEmpty());
+ }
+
+ public void testSingleHandlerThreadSession() throws Exception {
+ String sessionName = "LT.tSHTS";
+ Log.startSession(sessionName);
+ Session subsession = Log.createSubsession();
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = subsession;
+ mSleepyHandler.obtainMessage(EVENT_START_TEST_SLEEPY_METHOD, args).sendToTarget();
+ Log.endSession();
+
+ verifyEventResult(Session.START_SESSION, sessionName, "", 0, TEST_VERIFY_TIMEOUT_MS);
+ verifyEventResult(Session.CREATE_SUBSESSION, sessionName, "", 0, TEST_VERIFY_TIMEOUT_MS);
+ verifyContinueEventResult(sessionName, "lTSH.hM", "", 0, TEST_VERIFY_TIMEOUT_MS);
+ verifyEventResult(Session.END_SUBSESSION, sessionName, "", 0, TEST_VERIFY_TIMEOUT_MS);
+ verifyMethodCall(sessionName, "lTSH.hM", 0, "_0", TEST_ENTER_METHOD1,
+ TEST_VERIFY_TIMEOUT_MS);
+ verifyEventResult(Session.END_SUBSESSION, "lTSH.hM", "_0", 0, TEST_VERIFY_TIMEOUT_MS);
+ verifyEndEventResult(sessionName, "", 0, TEST_VERIFY_TIMEOUT_MS);
+
+ assertEquals(Log.sSessionMapper.size(), 0);
+ assertEquals(true, mTestSystemLogger.isMessagesEmpty());
+ }
+
+ public void testSpawnMultipleThreadSessions() throws Exception {
+ final String sessionName = "LT.lTR";
+ mCompleteCount = new AtomicInteger(0);
+ for (int i = 0; i < TEST_THREAD_COUNT; i++) {
+ Thread.sleep(10);
+ new Thread(new LogTestRunnable(sessionName)).start();
+ }
+
+ // Poll until all of the threads have completed
+ while (mCompleteCount.get() < TEST_THREAD_COUNT) {
+ Thread.sleep(1000);
+ }
+
+ // Loop through verification separately to spawn threads fast so there is possible overlap
+ // (verifyEventResult(...) delays)
+ for (int i = 0; i < TEST_THREAD_COUNT; i++) {
+ verifyEventResult(Session.START_SESSION, sessionName, "", i, 0);
+ verifyMethodCall("", sessionName, i, "", TEST_ENTER_METHOD2, 0);
+ verifyMethodCall("", sessionName, i, "", TEST_ENTER_METHOD1, 0);
+ verifyEventResult(Session.END_SUBSESSION, sessionName, "", i, 0);
+ verifyEndEventResult(sessionName, "", i, 0);
+ }
+
+ assertEquals(Log.sSessionMapper.size(), 0);
+ assertEquals(true, mTestSystemLogger.isMessagesEmpty());
+ }
+
+ public void testSpawnMultipleThreadMultipleHandlerSession() throws Exception {
+ String sessionName = "LT.tSMTMHS";
+ Log.startSession(sessionName);
+ Session subsession = Log.createSubsession();
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = subsession;
+ mSleepyMultipleHandler.obtainMessage(EVENT_START_TEST_SLEEPY_MULTIPLE_METHOD,
+ args).sendToTarget();
+ Log.endSession();
+
+ verifyEventResult(Session.START_SESSION, sessionName, "", 0, TEST_VERIFY_TIMEOUT_MS);
+ verifyEventResult(Session.END_SUBSESSION, sessionName, "", 0, TEST_VERIFY_TIMEOUT_MS);
+ verifyEventResult(Session.CREATE_SUBSESSION, sessionName, "", 0, TEST_VERIFY_TIMEOUT_MS);
+ verifyContinueEventResult(sessionName, "lTSCH.hM", "_0", 0, TEST_VERIFY_TIMEOUT_MS);
+ verifyMethodCall(sessionName, "lTSCH.hM", 0, "_0", TEST_ENTER_METHOD3,
+ TEST_VERIFY_TIMEOUT_MS);
+ verifyEventResult(Session.END_SUBSESSION, "lTSCH.hM", "_0", 0, TEST_VERIFY_TIMEOUT_MS);
+ verifyEventResult(Session.CREATE_SUBSESSION, "lTSCH.hM", "_0", 0, TEST_VERIFY_TIMEOUT_MS);
+ verifyContinueEventResult(sessionName + "->" + "lTSCH.hM", "lTSH.hM", "_0_0", 0,
+ TEST_VERIFY_TIMEOUT_MS);
+ verifyMethodCall(sessionName + "->lTSCH.hM", "lTSH.hM", 0, "_0_0", TEST_ENTER_METHOD1,
+ TEST_VERIFY_TIMEOUT_MS);
+ verifyEventResult(Session.END_SUBSESSION, "lTSH.hM", "_0_0", 0, TEST_VERIFY_TIMEOUT_MS);
+ verifyEndEventResult(sessionName, "", 0, TEST_VERIFY_TIMEOUT_MS);
+
+ assertEquals(Log.sSessionMapper.size(), 0);
+ assertEquals(true, mTestSystemLogger.isMessagesEmpty());
+ }
+
+ public void testSpawnMultipleThreadMultipleHandlerSessions() throws Exception {
+ String sessionName = "LT.tSMTMHSs";
+ isHandlerCompleteWithEvents = false;
+ for (int i = 0; i < TEST_THREAD_COUNT; i++) {
+ Log.startSession(sessionName);
+ Session subsession = Log.createSubsession();
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = subsession;
+ mSleepyMultipleHandler.obtainMessage(EVENT_START_TEST_SLEEPY_MULTIPLE_METHOD,
+ args).sendToTarget();
+ Log.endSession();
+ }
+ // Send a message that denotes the last message that is sent. We poll until this message
+ // is processed in order to verify the results without waiting an arbitrary amount of time
+ // (that can change per device).
+ mSleepyMultipleHandler.obtainMessage(EVENT_LAST_MESSAGE).sendToTarget();
+
+ while (!isHandlerCompleteWithEvents) {
+ Thread.sleep(1000);
+ }
+
+ for (int i = 0; i < TEST_THREAD_COUNT; i++) {
+ verifyEventResult(Session.START_SESSION, sessionName, "", i, 0);
+ verifyEventResult(Session.END_SUBSESSION, sessionName, "", i, 0);
+ verifyEventResult(Session.CREATE_SUBSESSION, sessionName, "", i, 0);
+ verifyContinueEventResult(sessionName, "lTSCH.hM", "_0", i, 0);
+ verifyMethodCall(sessionName, "lTSCH.hM", i, "_0", TEST_ENTER_METHOD3, 0);
+ verifyEventResult(Session.END_SUBSESSION, "lTSCH.hM", "_0", i, 0);
+ verifyEventResult(Session.CREATE_SUBSESSION, "lTSCH.hM", "_0", i, 0);
+ verifyContinueEventResult(sessionName + "->" + "lTSCH.hM", "lTSH.hM", "_0_0", i, 0);
+ verifyMethodCall(sessionName + "->lTSCH.hM", "lTSH.hM", i, "_0_0", TEST_ENTER_METHOD1,
+ 0);
+ verifyEventResult(Session.END_SUBSESSION, "lTSH.hM", "_0_0", i, 0);
+ verifyEndEventResult(sessionName, "", i, 0);
+ }
+
+ assertEquals(Log.sSessionMapper.size(), 0);
+ assertEquals(true, mTestSystemLogger.isMessagesEmpty());
+ }
+
+ public void testCancelSubsession() throws Exception {
+ String sessionName = "LT.tCS";
+ Log.startSession(sessionName);
+ Session subsession = Log.createSubsession();
+ Log.cancelSubsession(subsession);
+ Log.endSession();
+
+ verifyEventResult(Session.START_SESSION, sessionName, "", 0, 0);
+ verifyEventResult(Session.CREATE_SUBSESSION, sessionName, "", 0, 0);
+ verifyEventResult(Session.END_SUBSESSION, sessionName, "", 0, 0);
+ verifyEndEventResult(sessionName, "", 0, 0);
+
+ assertEquals(Log.sSessionMapper.size(), 0);
+ assertEquals(true, mTestSystemLogger.isMessagesEmpty());
+ }
+
+ public void testInternalExternalCallToMethod() throws Exception {
+ String sessionName = "LT.tIECTM";
+ Log.startSession(sessionName);
+ internalExternalMethod();
+ Log.endSession();
+
+ verifyEventResult(Session.START_SESSION, sessionName, "", 0, 0);
+ verifyEventResult(Session.CREATE_SUBSESSION, sessionName, "", 0, 0);
+ verifyContinueEventResult(sessionName, "LT.iEM", "", 0, 0);
+ verifyEventResult(Session.END_SUBSESSION, sessionName, "", 0, 0);
+ verifyMethodCall(sessionName, "LT.iEM", 0, "_0", TEST_ENTER_METHOD4, 0);
+ verifyEventResult(Session.END_SUBSESSION, "LT.iEM", "_0", 0, 0);
+ verifyEndEventResult(sessionName, "", 0, 0);
+
+ assertEquals(Log.sSessionMapper.size(), 0);
+ assertEquals(true, mTestSystemLogger.isMessagesEmpty());
+ }
+
+ public void testGarbageCollectionWithTimeout() throws Exception {
+ String sessionName = "LT.tGCWT";
+
+ // Don't end session (Oops!)
+ Log.startSession(sessionName);
+ internalDanglingMethod();
+ Log.sSessionCleanupHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ android.util.Log.i(TESTING_TAG, "Running Test SessionCleanupHandler method.");
+ Log.cleanupStaleSessions(1000);
+ }
+ }, 1000);
+
+ verifyEventResult(Session.START_SESSION, sessionName, "", 0, 0);
+ verifyEventResult(Session.CREATE_SUBSESSION, sessionName, "", 0, 0);
+ verifyContinueEventResult(sessionName, "LT.iEM", "", 0, 0);
+ verifyMethodCall(sessionName, "LT.iEM", 0, "_0", TEST_ENTER_METHOD4, 0);
+
+ // Verify the session is still active in sSessionMapper
+ assertEquals(Log.sSessionMapper.size(), 1);
+ assertEquals(true, mTestSystemLogger.isMessagesEmpty());
+
+ // Keep a weak reference to the object to check if it eventually gets garbage collected.
+ int threadId = Log.getCallingThreadId();
+ WeakReference<Session> sessionRef = new WeakReference<>(
+ Log.sSessionMapper.get(threadId));
+
+ Thread.sleep(1100);
+ assertEquals(0, Log.sSessionMapper.size());
+ // "Suggest" that the GC collects the now isolated Session and subsession and wait for it
+ // to occur.
+ System.gc();
+ Thread.sleep(1000);
+ assertEquals(null, sessionRef.get());
+ }
+
+ private void verifyMethodCall(String parentSessionName, String methodName, int sessionId,
+ String subsession, String shortMethodName, int timeoutMs) {
+ if (!parentSessionName.isEmpty()){
+ parentSessionName += "->";
+ }
+ boolean isMessageReceived = mTestSystemLogger.didReceiveMessage(timeoutMs,
+ buildExpectedResult(parentSessionName + methodName, sessionId, subsession,
+ shortMethodName));
+
+ assertEquals(true, isMessageReceived);
+ }
+
+ private String buildExpectedSession(String shortMethodName, int sessionId) {
+ return shortMethodName + "@" + Log.getBase64Encoding(sessionId);
+ }
+
+ private String buildExpectedResult(String shortMethodName, int sessionId,
+ String subsessionId, String logText) {
+ return TEST_CLASS_NAME + " " + buildExpectedSession(shortMethodName, sessionId) +
+ subsessionId + ": " + logText;
+ }
+
+ private void verifyContinueEventResult(String shortOldMethodName, String shortNewMethodName,
+ String subsession, int sessionId, int timeoutMs) {
+ String expectedSession = buildExpectedSession(shortNewMethodName, sessionId);
+ boolean isMessageReceived = mTestSystemLogger.didReceiveMessage(timeoutMs,
+ Session.CONTINUE_SUBSESSION + " " + shortOldMethodName + "->" + expectedSession +
+ subsession);
+ assertEquals(true, isMessageReceived);
+ }
+
+ private void verifyEventResult(String event, String shortMethodName, String subsession,
+ int sessionId, int timeoutMs) {
+ String expectedSession = buildExpectedSession(shortMethodName, sessionId);
+ boolean isMessageReceived = mTestSystemLogger.didReceiveMessage(timeoutMs, expectedSession +
+ subsession + ": " + event);
+ assertEquals(true, isMessageReceived);
+ }
+
+ private void verifyEndEventResult(String shortMethodName, String subsession, int sessionId,
+ int timeoutMs) {
+ String expectedSession = buildExpectedSession(shortMethodName, sessionId);
+ boolean isMessageReceived = mTestSystemLogger.didReceiveMessage(timeoutMs,
+ Session.END_SESSION + " " + expectedSession + subsession);
+ assertEquals(true, isMessageReceived);
+ }
+
+ private void internalExternalMethod() {
+ Log.startSession("LT.iEM");
+ Log.i(TEST_CLASS_NAME, TEST_ENTER_METHOD4);
+ Log.endSession();
+ }
+
+ private void internalDanglingMethod() {
+ Log.startSession("LT.iEM");
+ Log.i(TEST_CLASS_NAME, TEST_ENTER_METHOD4);
+ }
+
+ private void sleepyCallerMethod(int timeToSleepMs) {
+ Log.i(TEST_CLASS_NAME, TEST_ENTER_METHOD2);
+ try {
+ Thread.sleep(timeToSleepMs);
+ sleepyMethod(rng.nextInt(TEST_SLEEP_TIME_MS));
+ } catch (InterruptedException e) {
+ // This should not happen
+ Assert.fail("Thread sleep interrupted: " + e.getMessage());
+ }
+
+ }
+
+ private void sleepyMultipleMethod() {
+ Log.i(TEST_CLASS_NAME, TEST_ENTER_METHOD3);
+ Session subsession = Log.createSubsession();
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = subsession;
+ mSleepyHandler.obtainMessage(EVENT_START_TEST_SLEEPY_METHOD, args).sendToTarget();
+ try {
+ Thread.sleep(TEST_SLEEP_TIME_MS);
+ } catch (InterruptedException e) {
+ // This should not happen
+ Assert.fail("Thread sleep interrupted: " + e.getMessage());
+ }
+ }
+
+ private void sleepyMethod(int timeToSleepMs) {
+ Log.i(TEST_CLASS_NAME, TEST_ENTER_METHOD1);
+ try {
+ Thread.sleep(timeToSleepMs);
+ } catch (InterruptedException e) {
+ // This should not happen
+ Assert.fail("Thread sleep interrupted: " + e.getMessage());
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/src/com/android/server/telecom/tests/MissedCallNotifierImplTest.java b/tests/src/com/android/server/telecom/tests/MissedCallNotifierImplTest.java
new file mode 100644
index 0000000..ba32a5d
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/MissedCallNotifierImplTest.java
@@ -0,0 +1,295 @@
+/*
+ * 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.server.telecom.tests;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.net.Uri;
+import android.os.UserHandle;
+import android.telephony.TelephonyManager;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.Constants;
+import com.android.server.telecom.MissedCallNotifier;
+import com.android.server.telecom.TelecomBroadcastIntentProcessor;
+import com.android.server.telecom.components.TelecomBroadcastReceiver;
+import com.android.server.telecom.ui.MissedCallNotifierImpl;
+
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Arrays;
+import java.util.HashSet;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isNull;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class MissedCallNotifierImplTest extends TelecomTestCase {
+
+ private static final Uri TEL_CALL_HANDLE = Uri.parse("tel:+11915552620");
+ private static final Uri SIP_CALL_HANDLE = Uri.parse("sip:testaddress@testdomain.com");
+ private static final String CALLER_NAME = "Fake Name";
+ private static final String MISSED_CALL_TITLE = "Missed Call";
+ private static final String MISSED_CALLS_TITLE = "Missed Calls";
+ private static final String MISSED_CALLS_MSG = "%s missed calls";
+ private static final String USER_CALL_ACTIVITY_LABEL = "Phone";
+
+ private static final int REQUEST_ID = 0;
+ private static final long CALL_TIMESTAMP;
+ static {
+ CALL_TIMESTAMP = System.currentTimeMillis() - 60 * 1000 * 5;
+ }
+
+ @Mock
+ private NotificationManager mNotificationManager;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ MockitoAnnotations.initMocks(this);
+
+ mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
+ mNotificationManager = (NotificationManager) mContext.getSystemService(
+ Context.NOTIFICATION_SERVICE);
+ TelephonyManager fakeTelephonyManager = (TelephonyManager) mContext.getSystemService(
+ Context.TELEPHONY_SERVICE);
+ when(fakeTelephonyManager.getNetworkCountryIso()).thenReturn("US");
+ doReturn(new ApplicationInfo()).when(mContext).getApplicationInfo();
+ doReturn("com.android.server.telecom.tests").when(mContext).getPackageName();
+
+ mComponentContextFixture.putResource(R.string.notification_missedCallTitle,
+ MISSED_CALL_TITLE);
+ mComponentContextFixture.putResource(R.string.notification_missedCallsTitle,
+ MISSED_CALLS_TITLE);
+ mComponentContextFixture.putResource(R.string.notification_missedCallsMsg,
+ MISSED_CALLS_MSG);
+ mComponentContextFixture.putResource(R.string.userCallActivityLabel,
+ USER_CALL_ACTIVITY_LABEL);
+ }
+
+ public void testCancelNotification() {
+ Notification.Builder builder1 = makeNotificationBuilder("builder1");
+ Notification.Builder builder2 = makeNotificationBuilder("builder2");
+ MissedCallNotifierImpl.NotificationBuilderFactory fakeBuilderFactory =
+ makeNotificationBuilderFactory(builder1, builder1, builder2, builder2);
+
+ MissedCallNotifier missedCallNotifier = new MissedCallNotifierImpl(mContext,
+ fakeBuilderFactory);
+
+ Call fakeCall = makeFakeCall(TEL_CALL_HANDLE, CALLER_NAME, CALL_TIMESTAMP);
+
+ missedCallNotifier.showMissedCallNotification(fakeCall);
+ missedCallNotifier.clearMissedCalls();
+ missedCallNotifier.showMissedCallNotification(fakeCall);
+
+ ArgumentCaptor<Integer> requestIdCaptor = ArgumentCaptor.forClass(
+ Integer.class);
+ verify(mNotificationManager, times(2)).notifyAsUser(isNull(String.class),
+ requestIdCaptor.capture(), any(Notification.class), eq(UserHandle.CURRENT));
+ verify(mNotificationManager).cancelAsUser(any(String.class), eq(requestIdCaptor.getValue()),
+ eq(UserHandle.CURRENT));
+
+ // Verify that the second call to showMissedCallNotification behaves like it were the first.
+ verify(builder2).setContentText(CALLER_NAME);
+ }
+
+ public void testNotifyMultipleMissedCalls() {
+ Notification.Builder[] builders = new Notification.Builder[4];
+
+ for (int i = 0; i < 4; i++) {
+ builders[i] = makeNotificationBuilder("builder" + Integer.toString(i));
+ }
+
+ Call fakeCall = makeFakeCall(TEL_CALL_HANDLE, CALLER_NAME, CALL_TIMESTAMP);
+
+ MissedCallNotifierImpl.NotificationBuilderFactory fakeBuilderFactory =
+ makeNotificationBuilderFactory(builders);
+
+ MissedCallNotifier missedCallNotifier = new MissedCallNotifierImpl(mContext,
+ fakeBuilderFactory);
+
+ missedCallNotifier.showMissedCallNotification(fakeCall);
+ missedCallNotifier.showMissedCallNotification(fakeCall);
+
+ // The following captor is to capture the two notifications that got passed into
+ // notifyAsUser. This distinguishes between the builders used for the full notification
+ // (i.e. the one potentially containing sensitive information, such as phone numbers),
+ // and the builders used for the notifications shown on the lockscreen, which have been
+ // scrubbed of such sensitive info. The notifications which are used as arguments
+ // to notifyAsUser are the versions which contain sensitive information.
+ ArgumentCaptor<Notification> notificationArgumentCaptor = ArgumentCaptor.forClass(
+ Notification.class);
+ verify(mNotificationManager, times(2)).notifyAsUser(isNull(String.class), eq(1),
+ notificationArgumentCaptor.capture(), eq(UserHandle.CURRENT));
+ HashSet<String> privateNotifications = new HashSet<>();
+ for (Notification n : notificationArgumentCaptor.getAllValues()) {
+ privateNotifications.add(n.toString());
+ }
+
+ for (int i = 0; i < 4; i++) {
+ Notification.Builder builder = builders[i];
+ verify(builder).setWhen(CALL_TIMESTAMP);
+ if (i >= 2) {
+ // The builders after the first two are for multiple missed calls. The notification
+ // for subsequent missed calls is expected to be different in terms of the text
+ // contents of the notification, and that is verified here.
+ if (privateNotifications.contains(builder.toString())) {
+ verify(builder).setContentText(String.format(MISSED_CALLS_MSG, 2));
+ verify(builder).setContentTitle(MISSED_CALLS_TITLE);
+ } else {
+ verify(builder).setContentText(MISSED_CALLS_TITLE);
+ verify(builder).setContentTitle(USER_CALL_ACTIVITY_LABEL);
+ }
+ verify(builder, never()).addAction(any(Notification.Action.class));
+ } else {
+ if (privateNotifications.contains(builder.toString())) {
+ verify(builder).setContentText(CALLER_NAME);
+ verify(builder).setContentTitle(MISSED_CALL_TITLE);
+ } else {
+ verify(builder).setContentText(MISSED_CALL_TITLE);
+ verify(builder).setContentTitle(USER_CALL_ACTIVITY_LABEL);
+ }
+ }
+ }
+ }
+
+ public void testNotifySingleCall() {
+ Notification.Builder builder1 = makeNotificationBuilder("builder1");
+ Notification.Builder builder2 = makeNotificationBuilder("builder2");
+ MissedCallNotifierImpl.NotificationBuilderFactory fakeBuilderFactory =
+ makeNotificationBuilderFactory(builder1, builder2);
+
+ MissedCallNotifier missedCallNotifier = new MissedCallNotifierImpl(mContext,
+ fakeBuilderFactory);
+
+ Call fakeCall = makeFakeCall(TEL_CALL_HANDLE, CALLER_NAME, CALL_TIMESTAMP);
+ missedCallNotifier.showMissedCallNotification(fakeCall);
+
+ ArgumentCaptor<Notification> notificationArgumentCaptor = ArgumentCaptor.forClass(
+ Notification.class);
+ verify(mNotificationManager).notifyAsUser(isNull(String.class), eq(1),
+ notificationArgumentCaptor.capture(), eq(UserHandle.CURRENT));
+
+ Notification.Builder builder;
+ Notification.Builder publicBuilder;
+
+ if (notificationArgumentCaptor.getValue().toString().equals("builder1")) {
+ builder = builder1;
+ publicBuilder = builder2;
+ } else {
+ builder = builder2;
+ publicBuilder = builder1;
+ }
+
+ verify(builder).setWhen(CALL_TIMESTAMP);
+ verify(publicBuilder).setWhen(CALL_TIMESTAMP);
+
+ verify(builder).setContentText(CALLER_NAME);
+ verify(publicBuilder).setContentText(MISSED_CALL_TITLE);
+
+ verify(builder).setContentTitle(MISSED_CALL_TITLE);
+ verify(publicBuilder).setContentTitle(USER_CALL_ACTIVITY_LABEL);
+
+ // Create two intents that correspond to call-back and respond back with SMS, and assert
+ // that these pending intents have in fact been registered.
+ Intent callBackIntent = new Intent(
+ TelecomBroadcastIntentProcessor.ACTION_CALL_BACK_FROM_NOTIFICATION,
+ TEL_CALL_HANDLE,
+ mContext,
+ TelecomBroadcastReceiver.class);
+ Intent smsIntent = new Intent(
+ TelecomBroadcastIntentProcessor.ACTION_SEND_SMS_FROM_NOTIFICATION,
+ Uri.fromParts(Constants.SCHEME_SMSTO, TEL_CALL_HANDLE.getSchemeSpecificPart(), null),
+ mContext,
+ TelecomBroadcastReceiver.class);
+
+ assertNotNull(PendingIntent.getBroadcast(mContext, REQUEST_ID,
+ callBackIntent, PendingIntent.FLAG_NO_CREATE));
+ assertNotNull(PendingIntent.getBroadcast(mContext, REQUEST_ID,
+ smsIntent, PendingIntent.FLAG_NO_CREATE));
+ }
+
+ public void testNoSmsBackAfterMissedSipCall() {
+ Notification.Builder builder1 = makeNotificationBuilder("builder1");
+ MissedCallNotifierImpl.NotificationBuilderFactory fakeBuilderFactory =
+ makeNotificationBuilderFactory(builder1);
+
+ MissedCallNotifier missedCallNotifier = new MissedCallNotifierImpl(mContext,
+ fakeBuilderFactory);
+
+ Call fakeCall = makeFakeCall(SIP_CALL_HANDLE, CALLER_NAME, CALL_TIMESTAMP);
+ missedCallNotifier.showMissedCallNotification(fakeCall);
+
+ // Create two intents that correspond to call-back and respond back with SMS, and assert
+ // that in the case of a SIP call, no SMS intent is generated.
+ Intent callBackIntent = new Intent(
+ TelecomBroadcastIntentProcessor.ACTION_CALL_BACK_FROM_NOTIFICATION,
+ SIP_CALL_HANDLE,
+ mContext,
+ TelecomBroadcastReceiver.class);
+ Intent smsIntent = new Intent(
+ TelecomBroadcastIntentProcessor.ACTION_SEND_SMS_FROM_NOTIFICATION,
+ Uri.fromParts(Constants.SCHEME_SMSTO, SIP_CALL_HANDLE.getSchemeSpecificPart(),
+ null),
+ mContext,
+ TelecomBroadcastReceiver.class);
+
+ assertNotNull(PendingIntent.getBroadcast(mContext, REQUEST_ID,
+ callBackIntent, PendingIntent.FLAG_NO_CREATE));
+ assertNull(PendingIntent.getBroadcast(mContext, REQUEST_ID,
+ smsIntent, PendingIntent.FLAG_NO_CREATE));
+ }
+
+ private Notification.Builder makeNotificationBuilder(String label) {
+ Notification.Builder builder = spy(new Notification.Builder(mContext));
+ Notification notification = mock(Notification.class);
+ when(notification.toString()).thenReturn(label);
+ when(builder.toString()).thenReturn(label);
+ doReturn(notification).when(builder).build();
+ return builder;
+ }
+
+ private Call makeFakeCall(Uri handle, String name, long timestamp) {
+ Call fakeCall = mock(Call.class);
+ when(fakeCall.getHandle()).thenReturn(handle);
+ when(fakeCall.getName()).thenReturn(name);
+ when(fakeCall.getCreationTimeMillis()).thenReturn(timestamp);
+ return fakeCall;
+ }
+
+ private MissedCallNotifierImpl.NotificationBuilderFactory makeNotificationBuilderFactory(
+ Notification.Builder... builders) {
+ MissedCallNotifierImpl.NotificationBuilderFactory builderFactory =
+ mock(MissedCallNotifierImpl.NotificationBuilderFactory.class);
+ when(builderFactory.getBuilder(mContext)).thenReturn(builders[0],
+ Arrays.copyOfRange(builders, 1, builders.length));
+ return builderFactory;
+ }
+}
diff --git a/tests/src/com/android/server/telecom/tests/MockitoHelper.java b/tests/src/com/android/server/telecom/tests/MockitoHelper.java
index 5193bba..3425b0e 100644
--- a/tests/src/com/android/server/telecom/tests/MockitoHelper.java
+++ b/tests/src/com/android/server/telecom/tests/MockitoHelper.java
@@ -18,9 +18,7 @@
import com.android.server.telecom.Log;
-import android.annotation.TargetApi;
import android.content.Context;
-import android.os.Looper;
/**
* Helper for Mockito-based test cases.
@@ -28,10 +26,6 @@
public final class MockitoHelper {
private static final String DEXCACHE = "dexmaker.dexcache";
- private Thread mRequestThread;
- private ClassLoader mRequestThreadOriginalClassLoader;
- private ClassLoader mMainThreadOriginalClassLoader;
-
/**
* Creates a new helper, which in turn will set the context classloader so
* it can load Mockito resources.
@@ -39,24 +33,6 @@
* @param packageClass test case class
*/
public void setUp(Context context, Class<?> packageClass) throws Exception {
- // makes a copy of the context classloader
- mRequestThread = Thread.currentThread();
- mRequestThreadOriginalClassLoader = mRequestThread.getContextClassLoader();
- mMainThreadOriginalClassLoader = Looper.getMainLooper().getThread().getContextClassLoader();
-
- ClassLoader newClassLoader = packageClass.getClassLoader();
-
- Log.v(this, "Changing context classloader for thread %s from %s to %s",
- mRequestThread.getName(),
- mRequestThreadOriginalClassLoader,
- newClassLoader);
- mRequestThread.setContextClassLoader(newClassLoader);
-
- Log.v(this, "Changing context classloader for MAIN thread from %s to %s",
- mMainThreadOriginalClassLoader,
- newClassLoader);
- Looper.getMainLooper().getThread().setContextClassLoader(newClassLoader);
-
String dexCache = context.getCacheDir().toString();
Log.v(this, "Setting property %s to %s", DEXCACHE, dexCache);
System.setProperty(DEXCACHE, dexCache);
@@ -67,7 +43,6 @@
*/
public void tearDown() throws Exception {
Log.v(this, "Restoring context classloaders");
- mRequestThread.setContextClassLoader(mRequestThreadOriginalClassLoader);
Log.v(this, "Clearing property %s", DEXCACHE);
System.clearProperty(DEXCACHE);
}
diff --git a/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java b/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
index f815fed..03b1946 100644
--- a/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
+++ b/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
@@ -16,44 +16,55 @@
package com.android.server.telecom.tests;
-import android.os.Binder;
+import android.content.ComponentName;
+import android.content.Context;
+import android.graphics.BitmapFactory;
+import android.graphics.Rect;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Process;
+import android.os.UserHandle;
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
+import android.telecom.TelecomManager;
+import android.util.Xml;
import com.android.internal.telecom.IConnectionService;
import com.android.internal.util.FastXmlSerializer;
import com.android.server.telecom.Log;
import com.android.server.telecom.PhoneAccountRegistrar;
+import com.android.server.telecom.PhoneAccountRegistrar.DefaultPhoneAccountHandle;
+import org.mockito.Mock;
import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlSerializer;
-import android.content.ComponentName;
-import android.content.Context;
-import android.graphics.BitmapFactory;
-import android.graphics.drawable.Icon;
-import android.net.Uri;
-import android.os.Parcel;
-import android.telecom.PhoneAccount;
-import android.telecom.PhoneAccountHandle;
-import android.util.Xml;
-
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.util.Arrays;
+import java.util.Set;
public class PhoneAccountRegistrarTest extends TelecomTestCase {
private static final int MAX_VERSION = Integer.MAX_VALUE;
private static final String FILE_NAME = "phone-account-registrar-test-1223.xml";
private PhoneAccountRegistrar mRegistrar;
+ @Mock
+ private TelecomManager mTelecomManager;
@Override
public void setUp() throws Exception {
super.setUp();
+ MockitoAnnotations.initMocks(this);
mComponentContextFixture = new ComponentContextFixture();
+ mComponentContextFixture.setTelecomManager(mTelecomManager);
new File(
mComponentContextFixture.getTestDouble().getApplicationContext().getFilesDir(),
FILE_NAME)
@@ -88,9 +99,18 @@
}
public void testPhoneAccount() throws Exception {
+ Bundle testBundle = new Bundle();
+ testBundle.putInt("EXTRA_INT_1", 1);
+ testBundle.putInt("EXTRA_INT_100", 100);
+ testBundle.putBoolean("EXTRA_BOOL_TRUE", true);
+ testBundle.putBoolean("EXTRA_BOOL_FALSE", false);
+ testBundle.putString("EXTRA_STR1", "Hello");
+ testBundle.putString("EXTRA_STR2", "There");
+
PhoneAccount input = makeQuickAccountBuilder("id0", 0)
.addSupportedUriScheme(PhoneAccount.SCHEME_TEL)
.addSupportedUriScheme(PhoneAccount.SCHEME_VOICEMAIL)
+ .setExtras(testBundle)
.build();
PhoneAccount result = roundTripXml(this, input, PhoneAccountRegistrar.sPhoneAccountXml,
mContext);
@@ -98,6 +118,38 @@
assertPhoneAccountEquals(input, result);
}
+ /**
+ * Test to ensure non-supported balues
+ * @throws Exception
+ */
+ public void testPhoneAccountExtrasEdge() throws Exception {
+ Bundle testBundle = new Bundle();
+ // Ensure null values for string are not persisted.
+ testBundle.putString("EXTRA_STR2", null);
+ //
+
+ // Ensure unsupported data types are not persisted.
+ testBundle.putShort("EXTRA_SHORT", (short) 2);
+ testBundle.putByte("EXTRA_BYTE", (byte) 1);
+ testBundle.putParcelable("EXTRA_PARC", new Rect(1, 1, 1, 1));
+ // Put in something valid so the bundle exists.
+ testBundle.putString("EXTRA_OK", "OK");
+
+ PhoneAccount input = makeQuickAccountBuilder("id0", 0)
+ .addSupportedUriScheme(PhoneAccount.SCHEME_TEL)
+ .addSupportedUriScheme(PhoneAccount.SCHEME_VOICEMAIL)
+ .setExtras(testBundle)
+ .build();
+ PhoneAccount result = roundTripXml(this, input, PhoneAccountRegistrar.sPhoneAccountXml,
+ mContext);
+
+ Bundle extras = result.getExtras();
+ assertFalse(extras.keySet().contains("EXTRA_STR2"));
+ assertFalse(extras.keySet().contains("EXTRA_SHORT"));
+ assertFalse(extras.keySet().contains("EXTRA_BYTE"));
+ assertFalse(extras.keySet().contains("EXTRA_PARC"));
+ }
+
public void testState() throws Exception {
PhoneAccountRegistrar.State input = makeQuickState();
PhoneAccountRegistrar.State result = roundTripXml(this, input,
@@ -134,10 +186,11 @@
.setCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER)
.build());
- assertEquals(4, mRegistrar.getAllPhoneAccountHandles().size());
- assertEquals(3, mRegistrar.getCallCapablePhoneAccounts(null, false).size());
- assertEquals(null, mRegistrar.getSimCallManager());
- assertEquals(null, mRegistrar.getOutgoingPhoneAccountForScheme(PhoneAccount.SCHEME_TEL));
+ assertEquals(4, mRegistrar.getAllPhoneAccountsOfCurrentUser().size());
+ assertEquals(3, mRegistrar.getCallCapablePhoneAccountsOfCurrentUser(null, false).size());
+ assertEquals(null, mRegistrar.getSimCallManagerOfCurrentUser());
+ assertEquals(null, mRegistrar.getOutgoingPhoneAccountForSchemeOfCurrentUser(
+ PhoneAccount.SCHEME_TEL));
}
public void testSimCallManager() throws Exception {
@@ -150,7 +203,8 @@
Mockito.mock(IConnectionService.class));
// By default, there is no default outgoing account (nothing has been registered)
- assertNull(mRegistrar.getOutgoingPhoneAccountForScheme(PhoneAccount.SCHEME_TEL));
+ assertNull(
+ mRegistrar.getOutgoingPhoneAccountForSchemeOfCurrentUser(PhoneAccount.SCHEME_TEL));
// Register one tel: account
PhoneAccountHandle telAccount = makeQuickAccountHandle("tel_acct");
@@ -159,7 +213,7 @@
.addSupportedUriScheme(PhoneAccount.SCHEME_TEL)
.build());
PhoneAccountHandle defaultAccount =
- mRegistrar.getOutgoingPhoneAccountForScheme(PhoneAccount.SCHEME_TEL);
+ mRegistrar.getOutgoingPhoneAccountForSchemeOfCurrentUser(PhoneAccount.SCHEME_TEL);
assertEquals(telAccount, defaultAccount);
// Add a SIP account, make sure tel: doesn't change
@@ -168,9 +222,11 @@
.setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
.addSupportedUriScheme(PhoneAccount.SCHEME_SIP)
.build());
- defaultAccount = mRegistrar.getOutgoingPhoneAccountForScheme(PhoneAccount.SCHEME_SIP);
+ defaultAccount = mRegistrar.getOutgoingPhoneAccountForSchemeOfCurrentUser(
+ PhoneAccount.SCHEME_SIP);
assertEquals(sipAccount, defaultAccount);
- defaultAccount = mRegistrar.getOutgoingPhoneAccountForScheme(PhoneAccount.SCHEME_TEL);
+ defaultAccount = mRegistrar.getOutgoingPhoneAccountForSchemeOfCurrentUser(
+ PhoneAccount.SCHEME_TEL);
assertEquals(telAccount, defaultAccount);
// Add a connection manager, make sure tel: doesn't change
@@ -179,12 +235,14 @@
.setCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER)
.addSupportedUriScheme(PhoneAccount.SCHEME_TEL)
.build());
- defaultAccount = mRegistrar.getOutgoingPhoneAccountForScheme(PhoneAccount.SCHEME_TEL);
+ defaultAccount = mRegistrar.getOutgoingPhoneAccountForSchemeOfCurrentUser(
+ PhoneAccount.SCHEME_TEL);
assertEquals(telAccount, defaultAccount);
// Unregister the tel: account, make sure there is no tel: default now.
mRegistrar.unregisterPhoneAccount(telAccount);
- assertNull(mRegistrar.getOutgoingPhoneAccountForScheme(PhoneAccount.SCHEME_TEL));
+ assertNull(
+ mRegistrar.getOutgoingPhoneAccountForSchemeOfCurrentUser(PhoneAccount.SCHEME_TEL));
}
public void testPhoneAccountParceling() throws Exception {
@@ -228,7 +286,7 @@
return new PhoneAccountHandle(
makeQuickConnectionServiceComponentName(),
id,
- Binder.getCallingUserHandle());
+ Process.myUserHandle());
}
private PhoneAccount.Builder makeQuickAccountBuilder(String id, int idx) {
@@ -318,6 +376,18 @@
}
}
+ private static void assertDefaultPhoneAccountHandleEquals(DefaultPhoneAccountHandle a,
+ DefaultPhoneAccountHandle b) {
+ if (a != b) {
+ if (a!= null && b != null) {
+ assertEquals(a.userHandle, b.userHandle);
+ assertPhoneAccountHandleEquals(a.phoneAccountHandle, b.phoneAccountHandle);
+ } else {
+ fail("Default phone account handles are not equal: " + a + ", " + b);
+ }
+ }
+ }
+
private static void assertPhoneAccountEquals(PhoneAccount a, PhoneAccount b) {
if (a != b) {
if (a != null && b != null) {
@@ -330,15 +400,39 @@
assertEquals(a.getLabel(), b.getLabel());
assertEquals(a.getShortDescription(), b.getShortDescription());
assertEquals(a.getSupportedUriSchemes(), b.getSupportedUriSchemes());
+ assertBundlesEqual(a.getExtras(), b.getExtras());
} else {
fail("Phone accounts not equal: " + a + ", " + b);
}
}
}
+ private static void assertBundlesEqual(Bundle a, Bundle b) {
+ if (a == null && b == null) {
+ return;
+ }
+
+ assertNotNull(a);
+ assertNotNull(b);
+ Set<String> keySetA = a.keySet();
+ Set<String> keySetB = b.keySet();
+
+ assertTrue("Bundle keys not the same", keySetA.containsAll(keySetB));
+ assertTrue("Bundle keys not the same", keySetB.containsAll(keySetA));
+
+ for (String keyA : keySetA) {
+ assertEquals("Bundle value not the same", a.get(keyA), b.get(keyA));
+ }
+ }
+
private static void assertStateEquals(
PhoneAccountRegistrar.State a, PhoneAccountRegistrar.State b) {
- assertPhoneAccountHandleEquals(a.defaultOutgoing, b.defaultOutgoing);
+ assertEquals(a.defaultOutgoingAccountHandles.size(),
+ b.defaultOutgoingAccountHandles.size());
+ for (int i = 0; i < a.defaultOutgoingAccountHandles.size(); i++) {
+ assertDefaultPhoneAccountHandleEquals(a.defaultOutgoingAccountHandles.get(i),
+ b.defaultOutgoingAccountHandles.get(i));
+ }
assertEquals(a.accounts.size(), b.accounts.size());
for (int i = 0; i < a.accounts.size(); i++) {
assertPhoneAccountEquals(a.accounts.get(i), b.accounts.get(i));
@@ -350,7 +444,11 @@
s.accounts.add(makeQuickAccount("id0", 0));
s.accounts.add(makeQuickAccount("id1", 1));
s.accounts.add(makeQuickAccount("id2", 2));
- s.defaultOutgoing = new PhoneAccountHandle(new ComponentName("pkg0", "cls0"), "id0");
+ PhoneAccountHandle phoneAccountHandle = new PhoneAccountHandle(
+ new ComponentName("pkg0", "cls0"), "id0");
+ UserHandle userHandle = phoneAccountHandle.getUserHandle();
+ s.defaultOutgoingAccountHandles
+ .put(userHandle, new DefaultPhoneAccountHandle(userHandle, phoneAccountHandle));
return s;
}
}
diff --git a/tests/src/com/android/server/telecom/tests/RingerTest.java b/tests/src/com/android/server/telecom/tests/RingerTest.java
new file mode 100644
index 0000000..b8616ee
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/RingerTest.java
@@ -0,0 +1,194 @@
+/*
+ * 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.server.telecom.tests;
+
+import android.content.Context;
+import android.media.AudioAttributes;
+import android.media.Ringtone;
+import android.net.Uri;
+import android.os.Vibrator;
+
+import com.android.server.telecom.AsyncRingtonePlayer;
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallAudioManager;
+import com.android.server.telecom.CallState;
+import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.SystemSettingsUtil;
+import com.android.server.telecom.InCallTonePlayer;
+import com.android.server.telecom.Ringer;
+import com.android.server.telecom.RingtoneFactory;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class RingerTest extends TelecomTestCase {
+
+ @Mock Ringtone mMockRingtone;
+ @Mock Vibrator mMockVibrator;
+ @Mock RingtoneFactory mMockRingtoneFactory;
+ @Mock SystemSettingsUtil mMockSystemSettings;
+ @Mock CallsManager mMockCallsManager;
+ @Mock CallAudioManager mMockCallAudioManager;
+ @Mock InCallTonePlayer.Factory mMockToneFactory;
+
+ private Ringer mRingerUnderTest;
+
+ // These tests depend on an async handler to execute play() and stop() on the mock ringtone.
+ // In order to verify these results, the test must wait an arbitrary amount of time to make sure
+ // these methods are called.
+ private static final int TEST_TIMEOUT = 100; //milliseconds
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ MockitoAnnotations.initMocks(this);
+ // Don't involve CallAudioManager logic in this unit test
+ doNothing().when(mMockCallAudioManager).setIsRinging(any(Call.class), any(boolean.class));
+ // Assume theatre mode is off for these tests
+ when(mMockSystemSettings.isTheaterModeOn(any(Context.class))).thenReturn(false);
+ // Assume the system is set to enable vibration when ringing
+ when(mMockSystemSettings.canVibrateWhenRinging(any(Context.class))).thenReturn(true);
+ when(mMockVibrator.hasVibrator()).thenReturn(true);
+ mRingerUnderTest = new Ringer(
+ mMockCallAudioManager, mMockCallsManager, mMockToneFactory,
+ mComponentContextFixture.getTestDouble().getApplicationContext(),
+ mMockSystemSettings, new AsyncRingtonePlayer(), mMockRingtoneFactory,
+ mMockVibrator);
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ mRingerUnderTest = null;
+ super.tearDown();
+ }
+
+ private Call createMockRingingCall() {
+ Call mockCall = mock(Call.class);
+ when(mockCall.isIncoming()).thenReturn(true);
+ when(mockCall.getState()).thenReturn(CallState.RINGING);
+ return mockCall;
+ }
+
+ public void testPlayRingtoneOnIncomingCall() throws Exception {
+ Call mockCall = createMockRingingCall();
+ when(mMockRingtoneFactory.getRingtone(any(Uri.class))).thenReturn(mMockRingtone);
+ when(mMockCallsManager.getForegroundCall()).thenReturn(mockCall);
+
+ mRingerUnderTest.onCallAdded(mockCall);
+
+ verify(mMockVibrator).vibrate(any(long[].class), any(int.class),
+ any(AudioAttributes.class));
+ verify(mMockRingtone, timeout(TEST_TIMEOUT)).play();
+ }
+
+ public void testCallAnsweredOnIncomingCall() throws Exception {
+ Call mockCall = createMockRingingCall();
+ when(mMockRingtoneFactory.getRingtone(any(Uri.class))).thenReturn(mMockRingtone);
+ when(mMockCallsManager.getForegroundCall()).thenReturn(mockCall);
+
+ // Make sure the ringtone plays for foreground call
+ mRingerUnderTest.onCallAdded(mockCall);
+
+ verify(mMockRingtone, timeout(TEST_TIMEOUT)).play();
+ verify(mMockVibrator).vibrate(any(long[].class), any(int.class),
+ any(AudioAttributes.class));
+
+ // Answer Call
+ mRingerUnderTest.onIncomingCallAnswered(mockCall);
+
+ verify(mMockRingtone, timeout(TEST_TIMEOUT)).stop();
+ verify(mMockVibrator).cancel();
+ }
+
+ public void testCallWaitingOnBackgroundCall() throws Exception {
+ Call mockForegroundCall = mock(Call.class);
+ Call mockBackgroundCall = createMockRingingCall();
+ // Set foreground call to already answered
+ when(mMockCallsManager.getForegroundCall()).thenReturn(mockForegroundCall);
+ InCallTonePlayer mockInCallTonePlayer = mock(InCallTonePlayer.class);
+ when(mMockToneFactory.createPlayer(any(int.class))).thenReturn(mockInCallTonePlayer);
+
+ // Add new call waiting call
+ mRingerUnderTest.onCallAdded(mockBackgroundCall);
+
+ verify(mockInCallTonePlayer).startTone();
+ }
+
+ public void testCallWaitingOnBackgroundCallDisconnected() throws Exception {
+ Call mockForegroundCall = mock(Call.class);
+ Call mockBackgroundCall = createMockRingingCall();
+ // Set foreground call to already answered
+ when(mMockCallsManager.getForegroundCall()).thenReturn(mockForegroundCall);
+ InCallTonePlayer mockInCallTonePlayer = mock(InCallTonePlayer.class);
+ when(mMockToneFactory.createPlayer(any(int.class))).thenReturn(mockInCallTonePlayer);
+
+ // Add new call waiting call
+ mRingerUnderTest.onCallAdded(mockBackgroundCall);
+
+ verify(mockInCallTonePlayer).startTone();
+
+ // Reject the call waiting call
+ mRingerUnderTest.onIncomingCallRejected(mockBackgroundCall, false, "");
+
+ verify(mockInCallTonePlayer).stopTone();
+ }
+
+ public void testCallDisconnectedWhileRinging() throws Exception {
+ Call mockCall = createMockRingingCall();
+ when(mMockRingtoneFactory.getRingtone(any(Uri.class))).thenReturn(mMockRingtone);
+ when(mMockCallsManager.getForegroundCall()).thenReturn(mockCall);
+
+ // Make sure the ringtone plays for foreground call
+ mRingerUnderTest.onCallAdded(mockCall);
+
+ verify(mMockRingtone, timeout(TEST_TIMEOUT)).play();
+ verify(mMockVibrator).vibrate(any(long[].class), any(int.class),
+ any(AudioAttributes.class));
+
+ // Call Disconnected
+ mRingerUnderTest.onCallRemoved(mockCall);
+
+ verify(mMockRingtone, timeout(TEST_TIMEOUT)).stop();
+ verify(mMockVibrator).cancel();
+ }
+
+ public void testIncomingCallRejected() throws Exception {
+ Call mockCall = createMockRingingCall();
+ when(mMockRingtoneFactory.getRingtone(any(Uri.class))).thenReturn(mMockRingtone);
+ when(mMockCallsManager.getForegroundCall()).thenReturn(mockCall);
+
+ // Make sure the ringtone plays for foreground call
+ mRingerUnderTest.onCallAdded(mockCall);
+
+ verify(mMockRingtone, timeout(TEST_TIMEOUT)).play();
+ verify(mMockVibrator).vibrate(any(long[].class), any(int.class),
+ any(AudioAttributes.class));
+
+ // Answer Call
+ mRingerUnderTest.onIncomingCallRejected(mockCall, false, "");
+
+ verify(mMockRingtone, timeout(TEST_TIMEOUT)).stop();
+ verify(mMockVibrator).cancel();
+ }
+}
diff --git a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
index 4f96cc7..39aeb5d 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
@@ -16,27 +16,33 @@
package com.android.server.telecom.tests;
-import com.google.common.base.Predicate;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.media.AudioManager;
+import android.media.IAudioService;
import android.net.Uri;
import android.os.Bundle;
+import android.os.Debug;
import android.os.Handler;
+import android.os.Process;
import android.os.UserHandle;
import android.telecom.Call;
import android.telecom.CallAudioState;
@@ -48,23 +54,39 @@
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
import android.telecom.VideoProfile;
-import android.telephony.TelephonyManager;
import com.android.internal.telecom.IInCallAdapter;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.telecom.Analytics;
+import com.android.server.telecom.BluetoothPhoneServiceImpl;
+import com.android.server.telecom.CallAudioManager;
+import com.android.server.telecom.CallIntentProcessor;
+import com.android.server.telecom.CallerInfoAsyncQueryFactory;
import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.CallsManagerListenerBase;
+import com.android.server.telecom.ContactsAsyncHelper;
import com.android.server.telecom.HeadsetMediaButton;
import com.android.server.telecom.HeadsetMediaButtonFactory;
import com.android.server.telecom.InCallWakeLockController;
import com.android.server.telecom.InCallWakeLockControllerFactory;
import com.android.server.telecom.Log;
import com.android.server.telecom.MissedCallNotifier;
+import com.android.server.telecom.PhoneAccountRegistrar;
import com.android.server.telecom.ProximitySensorManager;
import com.android.server.telecom.ProximitySensorManagerFactory;
import com.android.server.telecom.TelecomSystem;
+import com.android.server.telecom.components.PrimaryCallReceiver;
+import com.android.server.telecom.components.UserCallIntentProcessor;
+
+import com.google.common.base.Predicate;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import java.io.StringWriter;
+import java.util.Map;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
@@ -74,10 +96,53 @@
static final int TEST_POLL_INTERVAL = 10; // milliseconds
static final int TEST_TIMEOUT = 1000; // milliseconds
- @Mock MissedCallNotifier mMissedCallNotifier;
+ public class HeadsetMediaButtonFactoryF implements HeadsetMediaButtonFactory {
+ @Override
+ public HeadsetMediaButton create(Context context, CallsManager callsManager,
+ TelecomSystem.SyncRoot lock) {
+ return mHeadsetMediaButton;
+ }
+ }
+
+ public class ProximitySensorManagerFactoryF implements ProximitySensorManagerFactory {
+ @Override
+ public ProximitySensorManager create(Context context, CallsManager callsManager) {
+ return mProximitySensorManager;
+ }
+ }
+
+ public class InCallWakeLockControllerFactoryF implements InCallWakeLockControllerFactory {
+ @Override
+ public InCallWakeLockController create(Context context, CallsManager callsManager) {
+ return mInCallWakeLockController;
+ }
+ }
+
+ public static class MissedCallNotifierFakeImpl extends CallsManagerListenerBase
+ implements MissedCallNotifier {
+ @Override
+ public void clearMissedCalls() {
+
+ }
+
+ @Override
+ public void showMissedCallNotification(com.android.server.telecom.Call call) {
+
+ }
+
+ @Override
+ public void updateOnStartup(TelecomSystem.SyncRoot lock, CallsManager callsManager,
+ ContactsAsyncHelper contactsAsyncHelper,
+ CallerInfoAsyncQueryFactory callerInfoAsyncQueryFactory) {
+
+ }
+ }
+
+ MissedCallNotifier mMissedCallNotifier = new MissedCallNotifierFakeImpl();
@Mock HeadsetMediaButton mHeadsetMediaButton;
@Mock ProximitySensorManager mProximitySensorManager;
@Mock InCallWakeLockController mInCallWakeLockController;
+ @Mock BluetoothPhoneServiceImpl mBluetoothPhoneServiceImpl;
final ComponentName mInCallServiceComponentNameX =
new ComponentName(
@@ -139,8 +204,12 @@
CallerInfoAsyncQueryFactoryFixture mCallerInfoAsyncQueryFactoryFixture;
+ IAudioService mAudioService;
+
TelecomSystem mTelecomSystem;
+ private int mNumOutgoingCallsMade;
+
class IdPair {
final String mConnectionId;
final String mCallId;
@@ -154,6 +223,7 @@
@Override
public void setUp() throws Exception {
super.setUp();
+ mNumOutgoingCallsMade = 0;
// First set up information about the In-Call services in the mock Context, since
// Telecom will search for these as soon as it is instantiated
@@ -174,36 +244,42 @@
}
private void setupTelecomSystem() throws Exception {
+ // Use actual implementations instead of mocking the interface out.
HeadsetMediaButtonFactory headsetMediaButtonFactory =
- mock(HeadsetMediaButtonFactory.class);
+ spy(new HeadsetMediaButtonFactoryF());
ProximitySensorManagerFactory proximitySensorManagerFactory =
- mock(ProximitySensorManagerFactory.class);
+ spy(new ProximitySensorManagerFactoryF());
InCallWakeLockControllerFactory inCallWakeLockControllerFactory =
- mock(InCallWakeLockControllerFactory.class);
+ spy(new InCallWakeLockControllerFactoryF());
+ mAudioService = setupAudioService();
mCallerInfoAsyncQueryFactoryFixture = new CallerInfoAsyncQueryFactoryFixture();
- when(headsetMediaButtonFactory.create(
- any(Context.class),
- any(CallsManager.class),
- any(TelecomSystem.SyncRoot.class)))
- .thenReturn(mHeadsetMediaButton);
- when(proximitySensorManagerFactory.create(
- any(Context.class),
- any(CallsManager.class)))
- .thenReturn(mProximitySensorManager);
- when(inCallWakeLockControllerFactory.create(
- any(Context.class),
- any(CallsManager.class)))
- .thenReturn(mInCallWakeLockController);
-
mTelecomSystem = new TelecomSystem(
mComponentContextFixture.getTestDouble(),
mMissedCallNotifier,
mCallerInfoAsyncQueryFactoryFixture.getTestDouble(),
headsetMediaButtonFactory,
proximitySensorManagerFactory,
- inCallWakeLockControllerFactory);
+ inCallWakeLockControllerFactory,
+ new CallAudioManager.AudioServiceFactory() {
+ @Override
+ public IAudioService getAudioService() {
+ return mAudioService;
+ }
+ },
+ new BluetoothPhoneServiceImpl.BluetoothPhoneServiceImplFactory() {
+ @Override
+ public BluetoothPhoneServiceImpl makeBluetoothPhoneServiceImpl(Context context,
+ TelecomSystem.SyncRoot lock, CallsManager callsManager,
+ PhoneAccountRegistrar phoneAccountRegistrar) {
+ return mBluetoothPhoneServiceImpl;
+ }
+ });
+
+ mComponentContextFixture.setTelecomManager(new TelecomManager(
+ mComponentContextFixture.getTestDouble(),
+ mTelecomSystem.getTelecomServiceImpl().getBinder()));
verify(headsetMediaButtonFactory).create(
eq(mComponentContextFixture.getTestDouble().getApplicationContext()),
@@ -233,7 +309,7 @@
mTelecomSystem.getPhoneAccountRegistrar().registerPhoneAccount(mPhoneAccountB0);
mTelecomSystem.getPhoneAccountRegistrar().setUserSelectedOutgoingPhoneAccount(
- mPhoneAccountA0.getAccountHandle());
+ mPhoneAccountA0.getAccountHandle(), Process.myUserHandle());
}
private void setupInCallServices() throws Exception {
@@ -243,6 +319,8 @@
mComponentContextFixture.putResource(
com.android.server.telecom.R.string.incall_default_class,
mInCallServiceComponentNameX.getClassName());
+ mComponentContextFixture.putBooleanResource(
+ com.android.internal.R.bool.config_voice_capable, true);
mInCallServiceFixtureX = new InCallServiceFixture();
mInCallServiceFixtureY = new InCallServiceFixture();
@@ -255,10 +333,41 @@
mInCallServiceFixtureY.getTestDouble());
}
+ /**
+ * Helper method for setting up the fake audio service.
+ * Calls to the fake audio service need to toggle the return
+ * value of AudioManager#isMicrophoneMute.
+ * @return mock of IAudioService
+ */
+ private IAudioService setupAudioService() {
+ IAudioService audioService = mock(IAudioService.class);
+
+ final AudioManager fakeAudioManager =
+ (AudioManager) mComponentContextFixture.getTestDouble()
+ .getApplicationContext().getSystemService(Context.AUDIO_SERVICE);
+
+ try {
+ doAnswer(new Answer() {
+ @Override
+ public Object answer(InvocationOnMock i) {
+ Object[] args = i.getArguments();
+ doReturn(args[0]).when(fakeAudioManager).isMicrophoneMute();
+ return null;
+ }
+ }).when(audioService)
+ .setMicrophoneMute(any(Boolean.class), any(String.class), any(Integer.class));
+
+ } catch (android.os.RemoteException e) {
+ // Do nothing, leave the faked microphone state as-is
+ }
+ return audioService;
+ }
+
private IdPair startOutgoingPhoneCall(
String number,
PhoneAccountHandle phoneAccountHandle,
- ConnectionServiceFixture connectionServiceFixture) throws Exception {
+ ConnectionServiceFixture connectionServiceFixture,
+ UserHandle initiatingUser) throws Exception {
reset(
connectionServiceFixture.getTestDouble(),
mInCallServiceFixtureX.getTestDouble(),
@@ -271,6 +380,7 @@
(mInCallServiceFixtureX.mInCallAdapter != null),
(mInCallServiceFixtureY.mInCallAdapter != null));
+ mNumOutgoingCallsMade++;
int startingNumConnections = connectionServiceFixture.mConnectionById.size();
int startingNumCalls = mInCallServiceFixtureX.mCallById.size();
boolean hasInCallAdapter = mInCallServiceFixtureX.mInCallAdapter != null;
@@ -285,8 +395,19 @@
phoneAccountHandle);
}
+ final UserHandle userHandle = initiatingUser;
+ Context localAppContext = mComponentContextFixture.getTestDouble().getApplicationContext();
+ new UserCallIntentProcessor(localAppContext, userHandle).processIntent(
+ actionCallIntent, null, true /* hasCallAppOp*/);
+ // UserCallIntentProcessor's mContext.sendBroadcastAsUser(...) will call to an empty method
+ // as to not actually try to send an intent to PrimaryCallReceiver. We verify that it was
+ // called correctly in order to continue.
+ verify(localAppContext).sendBroadcastAsUser(actionCallIntent, UserHandle.SYSTEM);
mTelecomSystem.getCallIntentProcessor().processIntent(actionCallIntent);
+ assertEquals(userHandle,
+ actionCallIntent.getParcelableExtra(CallIntentProcessor.KEY_INITIATING_USER));
+
if (!hasInCallAdapter) {
verify(mInCallServiceFixtureX.getTestDouble())
.setInCallAdapter(
@@ -301,7 +422,8 @@
ArgumentCaptor<BroadcastReceiver> newOutgoingCallReceiver =
ArgumentCaptor.forClass(BroadcastReceiver.class);
- verify(mComponentContextFixture.getTestDouble().getApplicationContext())
+ verify(mComponentContextFixture.getTestDouble().getApplicationContext(),
+ times(mNumOutgoingCallsMade))
.sendOrderedBroadcastAsUser(
newOutgoingCallIntent.capture(),
any(UserHandle.class),
@@ -351,6 +473,15 @@
String number,
PhoneAccountHandle phoneAccountHandle,
final ConnectionServiceFixture connectionServiceFixture) throws Exception {
+ return startIncomingPhoneCall(number, phoneAccountHandle, VideoProfile.STATE_AUDIO_ONLY,
+ connectionServiceFixture);
+ }
+
+ private IdPair startIncomingPhoneCall(
+ String number,
+ PhoneAccountHandle phoneAccountHandle,
+ int videoState,
+ final ConnectionServiceFixture connectionServiceFixture) throws Exception {
reset(
connectionServiceFixture.getTestDouble(),
mInCallServiceFixtureX.getTestDouble(),
@@ -381,10 +512,15 @@
eq(true),
eq(false));
+ mConnectionServiceFixtureA.mConnectionById.get(
+ connectionServiceFixture.mLatestConnectionId).videoState = videoState;
+
connectionServiceFixture.sendHandleCreateConnectionComplete(
connectionServiceFixture.mLatestConnectionId);
connectionServiceFixture.sendSetRinging(
connectionServiceFixture.mLatestConnectionId);
+ connectionServiceFixture.sendSetVideoState(
+ connectionServiceFixture.mLatestConnectionId);
// For the case of incoming calls, Telecom connecting the InCall services and adding the
// Call is triggered by the async completion of the CallerInfoAsyncQuery. Once the Call
@@ -494,7 +630,8 @@
String number,
PhoneAccountHandle phoneAccountHandle,
ConnectionServiceFixture connectionServiceFixture) throws Exception {
- IdPair ids = startOutgoingPhoneCall(number, phoneAccountHandle, connectionServiceFixture);
+ IdPair ids = startOutgoingPhoneCall(number, phoneAccountHandle, connectionServiceFixture,
+ Process.myUserHandle());
connectionServiceFixture.sendSetDialing(ids.mConnectionId);
assertEquals(Call.STATE_DIALING, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
@@ -507,6 +644,76 @@
return ids;
}
+ public void testBasicConferenceCall() throws Exception {
+ makeConferenceCall();
+ }
+
+ public void testAddCallToConference1() throws Exception {
+ ParcelableCall conferenceCall = makeConferenceCall();
+ IdPair callId3 = startAndMakeActiveOutgoingCall(
+ "650-555-1214",
+ mPhoneAccountA0.getAccountHandle(),
+ mConnectionServiceFixtureA);
+ // testAddCallToConference{1,2} differ in the order of arguments to InCallAdapter#conference
+ mInCallServiceFixtureX.getInCallAdapter().conference(
+ conferenceCall.getId(), callId3.mCallId);
+ Thread.sleep(200);
+
+ ParcelableCall call3 = mInCallServiceFixtureX.getCall(callId3.mCallId);
+ ParcelableCall updatedConference = mInCallServiceFixtureX.getCall(conferenceCall.getId());
+ assertEquals(conferenceCall.getId(), call3.getParentCallId());
+ assertEquals(3, updatedConference.getChildCallIds().size());
+ assertTrue(updatedConference.getChildCallIds().contains(callId3.mCallId));
+ }
+
+ public void testAddCallToConference2() throws Exception {
+ ParcelableCall conferenceCall = makeConferenceCall();
+ IdPair callId3 = startAndMakeActiveOutgoingCall(
+ "650-555-1214",
+ mPhoneAccountA0.getAccountHandle(),
+ mConnectionServiceFixtureA);
+ mInCallServiceFixtureX.getInCallAdapter().conference(
+ callId3.mCallId, conferenceCall.getId());
+ Thread.sleep(200);
+
+ ParcelableCall call3 = mInCallServiceFixtureX.getCall(callId3.mCallId);
+ ParcelableCall updatedConference = mInCallServiceFixtureX.getCall(conferenceCall.getId());
+ assertEquals(conferenceCall.getId(), call3.getParentCallId());
+ assertEquals(3, updatedConference.getChildCallIds().size());
+ assertTrue(updatedConference.getChildCallIds().contains(callId3.mCallId));
+ }
+
+ private ParcelableCall makeConferenceCall() throws Exception {
+ IdPair callId1 = startAndMakeActiveOutgoingCall(
+ "650-555-1212",
+ mPhoneAccountA0.getAccountHandle(),
+ mConnectionServiceFixtureA);
+
+ IdPair callId2 = startAndMakeActiveOutgoingCall(
+ "650-555-1213",
+ mPhoneAccountA0.getAccountHandle(),
+ mConnectionServiceFixtureA);
+
+ IInCallAdapter inCallAdapter = mInCallServiceFixtureX.getInCallAdapter();
+ inCallAdapter.conference(callId1.mCallId, callId2.mCallId);
+ // Wait for wacky non-deterministic behavior
+ Thread.sleep(200);
+ ParcelableCall call1 = mInCallServiceFixtureX.getCall(callId1.mCallId);
+ ParcelableCall call2 = mInCallServiceFixtureX.getCall(callId2.mCallId);
+ // Check that the two calls end up with a parent in the end
+ assertNotNull(call1.getParentCallId());
+ assertNotNull(call2.getParentCallId());
+ assertEquals(call1.getParentCallId(), call2.getParentCallId());
+
+ // Check to make sure that the parent call made it to the in-call service
+ String parentCallId = call1.getParentCallId();
+ ParcelableCall conferenceCall = mInCallServiceFixtureX.getCall(parentCallId);
+ assertEquals(2, conferenceCall.getChildCallIds().size());
+ assertTrue(conferenceCall.getChildCallIds().contains(callId1.mCallId));
+ assertTrue(conferenceCall.getChildCallIds().contains(callId2.mCallId));
+ return conferenceCall;
+ }
+
public void testSingleOutgoingCallLocalDisconnect() throws Exception {
IdPair ids = startAndMakeActiveOutgoingCall(
"650-555-1212",
@@ -537,6 +744,99 @@
mInCallServiceFixtureY.getCall(ids.mCallId).getState());
}
+ /**
+ * Tests the {@link TelecomManager#acceptRingingCall()} API. Tests simple case of an incoming
+ * audio-only call.
+ *
+ * @throws Exception
+ */
+ public void testTelecomManagerAcceptRingingCall() throws Exception {
+ IdPair ids = startIncomingPhoneCall("650-555-1212", mPhoneAccountA0.getAccountHandle(),
+ mConnectionServiceFixtureA);
+
+ assertEquals(Call.STATE_RINGING, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
+ assertEquals(Call.STATE_RINGING, mInCallServiceFixtureY.getCall(ids.mCallId).getState());
+
+ // Use TelecomManager API to answer the ringing call.
+ TelecomManager telecomManager = (TelecomManager) mComponentContextFixture.getTestDouble()
+ .getApplicationContext().getSystemService(Context.TELECOM_SERVICE);
+ telecomManager.acceptRingingCall();
+
+ verify(mConnectionServiceFixtureA.getTestDouble(), timeout(TEST_TIMEOUT))
+ .answer(ids.mCallId);
+ }
+
+ /**
+ * Tests the {@link TelecomManager#acceptRingingCall()} API. Tests simple case of an incoming
+ * video call, which should be answered as video.
+ *
+ * @throws Exception
+ */
+ public void testTelecomManagerAcceptRingingVideoCall() throws Exception {
+ IdPair ids = startIncomingPhoneCall("650-555-1212", mPhoneAccountA0.getAccountHandle(),
+ VideoProfile.STATE_BIDIRECTIONAL, mConnectionServiceFixtureA);
+
+ assertEquals(Call.STATE_RINGING, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
+ assertEquals(Call.STATE_RINGING, mInCallServiceFixtureY.getCall(ids.mCallId).getState());
+
+ // Use TelecomManager API to answer the ringing call; the default expected behavior is to
+ // answer using whatever video state the ringing call requests.
+ TelecomManager telecomManager = (TelecomManager) mComponentContextFixture.getTestDouble()
+ .getApplicationContext().getSystemService(Context.TELECOM_SERVICE);
+ telecomManager.acceptRingingCall();
+
+ // Answer video API should be called
+ verify(mConnectionServiceFixtureA.getTestDouble(), timeout(TEST_TIMEOUT))
+ .answerVideo(eq(ids.mCallId), eq(VideoProfile.STATE_BIDIRECTIONAL));
+ }
+
+ /**
+ * Tests the {@link TelecomManager#acceptRingingCall(int)} API. Tests answering a video call
+ * as an audio call.
+ *
+ * @throws Exception
+ */
+ public void testTelecomManagerAcceptRingingVideoCallAsAudio() throws Exception {
+ IdPair ids = startIncomingPhoneCall("650-555-1212", mPhoneAccountA0.getAccountHandle(),
+ VideoProfile.STATE_BIDIRECTIONAL, mConnectionServiceFixtureA);
+
+ assertEquals(Call.STATE_RINGING, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
+ assertEquals(Call.STATE_RINGING, mInCallServiceFixtureY.getCall(ids.mCallId).getState());
+
+ // Use TelecomManager API to answer the ringing call.
+ TelecomManager telecomManager = (TelecomManager) mComponentContextFixture.getTestDouble()
+ .getApplicationContext().getSystemService(Context.TELECOM_SERVICE);
+ telecomManager.acceptRingingCall(VideoProfile.STATE_AUDIO_ONLY);
+
+ // The generic answer method on the ConnectionService is used to answer audio-only calls.
+ verify(mConnectionServiceFixtureA.getTestDouble(), timeout(TEST_TIMEOUT))
+ .answer(eq(ids.mCallId));
+ }
+
+ /**
+ * Tests the {@link TelecomManager#acceptRingingCall()} API. Tests simple case of an incoming
+ * video call, where an attempt is made to answer with an invalid video state.
+ *
+ * @throws Exception
+ */
+ public void testTelecomManagerAcceptRingingInvalidVideoState() throws Exception {
+ IdPair ids = startIncomingPhoneCall("650-555-1212", mPhoneAccountA0.getAccountHandle(),
+ VideoProfile.STATE_BIDIRECTIONAL, mConnectionServiceFixtureA);
+
+ assertEquals(Call.STATE_RINGING, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
+ assertEquals(Call.STATE_RINGING, mInCallServiceFixtureY.getCall(ids.mCallId).getState());
+
+ // Use TelecomManager API to answer the ringing call; the default expected behavior is to
+ // answer using whatever video state the ringing call requests.
+ TelecomManager telecomManager = (TelecomManager) mComponentContextFixture.getTestDouble()
+ .getApplicationContext().getSystemService(Context.TELECOM_SERVICE);
+ telecomManager.acceptRingingCall(999 /* invalid videostate */);
+
+ // Answer video API should be called
+ verify(mConnectionServiceFixtureA.getTestDouble(), timeout(TEST_TIMEOUT))
+ .answerVideo(eq(ids.mCallId), eq(VideoProfile.STATE_BIDIRECTIONAL));
+ }
+
// A simple incoming call, similar in scope to the previous test
private IdPair startAndMakeActiveIncomingCall(
String number,
@@ -594,7 +894,8 @@
final IdPair ids = startOutgoingPhoneCall(
"650-555-1212",
mPhoneAccountA0.getAccountHandle(),
- mConnectionServiceFixtureA);
+ mConnectionServiceFixtureA,
+ Process.myUserHandle());
rapidFire(
new Runnable() {
@Override
@@ -663,6 +964,95 @@
mInCallServiceFixtureY.getCall(outgoing.mCallId).getState());
}
+ public void testAnalyticsSingleCall() throws Exception {
+ IdPair testCall = startAndMakeActiveIncomingCall(
+ "650-555-1212",
+ mPhoneAccountA0.getAccountHandle(),
+ mConnectionServiceFixtureA);
+ Map<String, Analytics.CallInfoImpl> analyticsMap = Analytics.cloneData();
+
+ assertTrue(analyticsMap.containsKey(testCall.mCallId));
+
+ Analytics.CallInfoImpl callAnalytics = analyticsMap.get(testCall.mCallId);
+ assertTrue(callAnalytics.startTime > 0);
+ assertEquals(0, callAnalytics.endTime);
+ assertEquals(Analytics.INCOMING_DIRECTION, callAnalytics.callDirection);
+ assertFalse(callAnalytics.isInterrupted);
+ assertNull(callAnalytics.callTerminationReason);
+ assertEquals(mConnectionServiceComponentNameA.flattenToShortString(),
+ callAnalytics.connectionService);
+
+ mConnectionServiceFixtureA.
+ sendSetDisconnected(testCall.mConnectionId, DisconnectCause.ERROR);
+
+ analyticsMap = Analytics.cloneData();
+ callAnalytics = analyticsMap.get(testCall.mCallId);
+ assertTrue(callAnalytics.endTime > 0);
+ assertNotNull(callAnalytics.callTerminationReason);
+ assertEquals(DisconnectCause.ERROR, callAnalytics.callTerminationReason.getCode());
+
+ StringWriter sr = new StringWriter();
+ IndentingPrintWriter ip = new IndentingPrintWriter(sr, " ");
+ Analytics.dump(ip);
+ String dumpResult = sr.toString();
+ String[] expectedFields = {"startTime", "endTime", "direction", "isAdditionalCall",
+ "isInterrupted", "callTechnologies", "callTerminationReason", "connectionServices"};
+ for (String field : expectedFields) {
+ assertTrue(dumpResult.contains(field));
+ }
+ }
+
+ public void testAnalyticsTwoCalls() throws Exception {
+ IdPair testCall1 = startAndMakeActiveIncomingCall(
+ "650-555-1212",
+ mPhoneAccountA0.getAccountHandle(),
+ mConnectionServiceFixtureA);
+ IdPair testCall2 = startAndMakeActiveOutgoingCall(
+ "650-555-1213",
+ mPhoneAccountA0.getAccountHandle(),
+ mConnectionServiceFixtureA);
+
+ Map<String, Analytics.CallInfoImpl> analyticsMap = Analytics.cloneData();
+ assertTrue(analyticsMap.containsKey(testCall1.mCallId));
+ assertTrue(analyticsMap.containsKey(testCall2.mCallId));
+
+ Analytics.CallInfoImpl callAnalytics1 = analyticsMap.get(testCall1.mCallId);
+ Analytics.CallInfoImpl callAnalytics2 = analyticsMap.get(testCall2.mCallId);
+ assertTrue(callAnalytics1.startTime > 0);
+ assertTrue(callAnalytics2.startTime > 0);
+ assertEquals(0, callAnalytics1.endTime);
+ assertEquals(0, callAnalytics2.endTime);
+
+ assertEquals(Analytics.INCOMING_DIRECTION, callAnalytics1.callDirection);
+ assertEquals(Analytics.OUTGOING_DIRECTION, callAnalytics2.callDirection);
+
+ assertTrue(callAnalytics1.isInterrupted);
+ assertTrue(callAnalytics2.isAdditionalCall);
+
+ assertNull(callAnalytics1.callTerminationReason);
+ assertNull(callAnalytics2.callTerminationReason);
+
+ assertEquals(mConnectionServiceComponentNameA.flattenToShortString(),
+ callAnalytics1.connectionService);
+ assertEquals(mConnectionServiceComponentNameA.flattenToShortString(),
+ callAnalytics1.connectionService);
+
+ mConnectionServiceFixtureA.
+ sendSetDisconnected(testCall2.mConnectionId, DisconnectCause.REMOTE);
+ mConnectionServiceFixtureA.
+ sendSetDisconnected(testCall1.mConnectionId, DisconnectCause.ERROR);
+
+ analyticsMap = Analytics.cloneData();
+ callAnalytics1 = analyticsMap.get(testCall1.mCallId);
+ callAnalytics2 = analyticsMap.get(testCall2.mCallId);
+ assertTrue(callAnalytics1.endTime > 0);
+ assertTrue(callAnalytics2.endTime > 0);
+ assertNotNull(callAnalytics1.callTerminationReason);
+ assertNotNull(callAnalytics2.callTerminationReason);
+ assertEquals(DisconnectCause.ERROR, callAnalytics1.callTerminationReason.getCode());
+ assertEquals(DisconnectCause.REMOTE, callAnalytics2.callTerminationReason.getCode());
+ }
+
public void testAudioManagerOperations() throws Exception {
AudioManager audioManager = (AudioManager) mComponentContextFixture.getTestDouble()
.getApplicationContext().getSystemService(Context.AUDIO_SERVICE);
@@ -678,11 +1068,11 @@
.setMode(AudioManager.MODE_IN_CALL);
mInCallServiceFixtureX.mInCallAdapter.mute(true);
- verify(audioManager, timeout(TEST_TIMEOUT))
- .setMicrophoneMute(true);
+ verify(mAudioService, timeout(TEST_TIMEOUT))
+ .setMicrophoneMute(eq(true), any(String.class), any(Integer.class));
mInCallServiceFixtureX.mInCallAdapter.mute(false);
- verify(audioManager, timeout(TEST_TIMEOUT))
- .setMicrophoneMute(false);
+ verify(mAudioService, timeout(TEST_TIMEOUT))
+ .setMicrophoneMute(eq(false), any(String.class), any(Integer.class));
mInCallServiceFixtureX.mInCallAdapter.setAudioRoute(CallAudioState.ROUTE_SPEAKER);
verify(audioManager, timeout(TEST_TIMEOUT))
diff --git a/tests/src/com/android/server/telecom/tests/TelecomTestCase.java b/tests/src/com/android/server/telecom/tests/TelecomTestCase.java
index 144ef66..9c9d0d8 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomTestCase.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomTestCase.java
@@ -23,7 +23,7 @@
import android.test.AndroidTestCase;
public abstract class TelecomTestCase extends AndroidTestCase {
- private static final String TESTING_TAG = "Telecom-TEST";
+ protected static final String TESTING_TAG = "Telecom-TEST";
MockitoHelper mMockitoHelper = new MockitoHelper();
ComponentContextFixture mComponentContextFixture;