Merge "Remove code which prevents making a video call when one is in progress." into mm-wireless-dev
am: f21d66d3d8

* commit 'f21d66d3d8d57440e0e3110a581f04f8ba3d7f4e':
  Remove code which prevents making a video call when one is in progress.
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 56a594b..887c2e7 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -20,7 +20,6 @@
         coreApp="true"
         android:sharedUserId="android.uid.system">
 
-
     <protected-broadcast android:name="android.intent.action.SHOW_MISSED_CALLS_NOTIFICATION" />
 
     <!-- Prevents the activity manager from delaying any activity-start
@@ -63,16 +62,14 @@
             android:label="Process phone account registration"
             android:protectionLevel="signature|system"/>
 
-    <!-- Declare which SDK level this application was built against. This is needed so that IDEs
-         can check for incompatible APIs. -->
-    <uses-sdk android:minSdkVersion="19" />
-
     <application android:label="@string/telecommAppLabel"
             android:icon="@mipmap/ic_launcher_phone"
             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..3984a5d 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -16,9 +16,11 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"Gemiste werkoproep"</string>
     <string name="notification_missedCallsTitle" msgid="1361677948941502522">"Gemiste oproepe"</string>
     <string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> gemiste oproepe"</string>
     <string name="notification_missedCallTicker" msgid="504686252427747209">"Gemiste oproep van <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
@@ -35,11 +37,15 @@
     <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>
+    <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Net noodoproepe word toegelaat."</string>
+    <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..dbd4c30 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -16,9 +16,11 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"ያመለጠ የሥራ ጥሪ"</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 +37,15 @@
     <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-ar/strings.xml b/res/values-ar/strings.xml
index 3d31dc6..0ea39f5 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -16,9 +16,11 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"مكالمة عمل فائتة"</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 +37,15 @@
     <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..353de80
--- /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_missedWorkCallTitle" msgid="6242489980390803090">"Buraxılmış iş çağrısı"</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="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-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
new file mode 100644
index 0000000..057b025
--- /dev/null
+++ b/res/values-b+sr+Latn/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">"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šten poziv"</string>
+    <string name="notification_missedWorkCallTitle" msgid="6242489980390803090">"Propušten poziv za Work"</string>
+    <string name="notification_missedCallsTitle" msgid="1361677948941502522">"Propušteni pozivi"</string>
+    <string name="notification_missedCallsMsg" msgid="4575787816055205600">"Broj propuštenih poziva: <xliff:g id="NUM_MISSED_CALLS">%s</xliff:g>"</string>
+    <string name="notification_missedCallTicker" msgid="504686252427747209">"Propušten poziv od: <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
+    <string name="notification_missedCall_call_back" msgid="2684890353590890187">"Uzvrati poziv"</string>
+    <string name="notification_missedCall_message" msgid="3049928912736917988">"Poruka"</string>
+    <string name="accessibility_call_muted" msgid="2776111226185342220">"Zvuk poziva je isključen."</string>
+    <string name="accessibility_speakerphone_enabled" msgid="1988512040421036359">"Spikerfon je omogućen."</string>
+    <string name="respond_via_sms_canned_response_1" msgid="2461606462788380215">"U gužvi sam. O čemu se radi?"</string>
+    <string name="respond_via_sms_canned_response_2" msgid="4074450431532859214">"Pozvaću te uskoro."</string>
+    <string name="respond_via_sms_canned_response_3" msgid="3496079065723960450">"Pozvaću te kasnije."</string>
+    <string name="respond_via_sms_canned_response_4" msgid="1698989243040062190">"U gužvi sam. Da se čujemo kasnije?"</string>
+    <string name="respond_via_sms_setting_title" msgid="3754000371039709383">"Brzi odgovori"</string>
+    <string name="respond_via_sms_setting_title_2" msgid="6104662227299493906">"Izmena 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 je poslata na broj <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+    <string name="enable_account_preference_title" msgid="2021848090086481720">"Nalozi za pozivanje"</string>
+    <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Dozvoljeni su samo hitni pozivi."</string>
+    <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Ova aplikacija ne može da poziva bez dozvole za telefoniranje."</string>
+    <string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"Da biste uputili poziv, unesite važeći broj."</string>
+    <string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"Trenutno nije moguće dodati poziv."</string>
+    <string name="no_vm_number" msgid="4164780423805688336">"Nedostaje broj za govornu poštu"</string>
+    <string name="no_vm_number_msg" msgid="1300729501030053828">"Nije uskladišten nijedan broj govorne pošte na SIM kartici."</string>
+    <string name="add_vm_number_str" msgid="4676479471644687453">"Dodaj broj"</string>
+    <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"Promeniti podrazumevanu aplikaciju Telefon?"</string>
+    <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"Želite li da koristite aplikaciju <xliff:g id="NEW_APP">%1$s</xliff:g> umesto aplikacije <xliff:g id="CURRENT_APP">%2$s</xliff:g> kao podrazumevanu aplikaciju za pozivanje telefonskih brojeva?"</string>
+    <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"Želite li da koristite aplikaciju <xliff:g id="NEW_APP">%s</xliff:g> kao podrazumevanu aplikaciju za pozivanje telefonskih brojeva?"</string>
+</resources>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index 63d32b1..fa451b7 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -16,9 +16,11 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"Пропуснато служебно обаждане"</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>
@@ -31,15 +33,19 @@
     <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..787c331 100644
--- a/res/values-bn-rBD/strings.xml
+++ b/res/values-bn-rBD/strings.xml
@@ -16,9 +16,11 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"কাজের কল মিস করেছেন"</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 +37,15 @@
     <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-ca/strings.xml b/res/values-ca/strings.xml
index e3cf515..dc5b73c 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -16,9 +16,11 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"Trucada perduda de feina"</string>
     <string name="notification_missedCallsTitle" msgid="1361677948941502522">"Trucades perdudes"</string>
     <string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> trucades perdudes"</string>
     <string name="notification_missedCallTicker" msgid="504686252427747209">"Trucada perduda de <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
@@ -35,11 +37,15 @@
     <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>
+    <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Només es permeten les trucades d\'emergència."</string>
+    <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..dbc7165 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -16,9 +16,11 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"Zmeškaný pracovní 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ícího <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>."</string>
@@ -35,11 +37,15 @@
     <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>
+    <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Jsou povolena pouze tísňová volání."</string>
+    <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..8b1f7df 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -16,9 +16,11 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"Ubesvaret arbejdsopkald"</string>
     <string name="notification_missedCallsTitle" msgid="1361677948941502522">"Ubesvarede opkald"</string>
     <string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> ubesvarede opkald"</string>
     <string name="notification_missedCallTicker" msgid="504686252427747209">"Ubesvarede opkald fra <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
@@ -35,11 +37,15 @@
     <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>
+    <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Kun nødopkald er tilladt."</string>
+    <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..44277c8 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -16,9 +16,11 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"Verpasster geschäftlicher Anruf"</string>
     <string name="notification_missedCallsTitle" msgid="1361677948941502522">"Entgangene Anrufe"</string>
     <string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> entgangene Anrufe"</string>
     <string name="notification_missedCallTicker" msgid="504686252427747209">"Entgangener Anruf von <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
@@ -35,11 +37,15 @@
     <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="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"Geben Sie eine gültige Nummer ein."</string>
+    <string name="enable_account_preference_title" msgid="2021848090086481720">"Anrufkonten"</string>
+    <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Es sind nur Notrufe erlaubt."</string>
+    <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">"Gib 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..af41ee4 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -16,9 +16,11 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"Αναπάντητη κλήση εργασίας"</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>
@@ -31,15 +33,19 @@
     <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">"Αλλαγή της προεπιλεγμένης εφαρμογής 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..a7d9e24 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -16,9 +16,11 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"Missed work call"</string>
     <string name="notification_missedCallsTitle" msgid="1361677948941502522">"Missed calls"</string>
     <string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> missed calls"</string>
     <string name="notification_missedCallTicker" msgid="504686252427747209">"Missed call from <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
@@ -35,11 +37,15 @@
     <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..a7d9e24 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -16,9 +16,11 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"Missed work call"</string>
     <string name="notification_missedCallsTitle" msgid="1361677948941502522">"Missed calls"</string>
     <string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> missed calls"</string>
     <string name="notification_missedCallTicker" msgid="504686252427747209">"Missed call from <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
@@ -35,11 +37,15 @@
     <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..a7d9e24 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -16,9 +16,11 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"Missed work call"</string>
     <string name="notification_missedCallsTitle" msgid="1361677948941502522">"Missed calls"</string>
     <string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> missed calls"</string>
     <string name="notification_missedCallTicker" msgid="504686252427747209">"Missed call from <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
@@ -35,11 +37,15 @@
     <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..5d6e049 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -16,17 +16,19 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"Llamada de trabajo 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 +37,15 @@
     <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>
+    <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Solo se permiten llamadas de emergencia."</string>
+    <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..ab4a174 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -16,9 +16,11 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"Llamada de trabajo 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">"Llamada perdida de <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
@@ -35,11 +37,15 @@
     <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>
+    <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Solo se permiten llamadas de emergencia."</string>
+    <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..a567faf 100644
--- a/res/values-et-rEE/strings.xml
+++ b/res/values-et-rEE/strings.xml
@@ -16,9 +16,11 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"Vastamata kõne töölt"</string>
     <string name="notification_missedCallsTitle" msgid="1361677948941502522">"Vastamata kõned"</string>
     <string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> vastamata kõnet"</string>
     <string name="notification_missedCallTicker" msgid="504686252427747209">"Vastamata kõne helistajalt <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
@@ -35,11 +37,15 @@
     <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>
+    <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Lubatud on ainult hädaabikõned."</string>
+    <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..3cf0454 100644
--- a/res/values-eu-rES/strings.xml
+++ b/res/values-eu-rES/strings.xml
@@ -16,9 +16,11 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"Laneko dei bat galdu duzu"</string>
     <string name="notification_missedCallsTitle" msgid="1361677948941502522">"Dei galduak"</string>
     <string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> dei galdu"</string>
     <string name="notification_missedCallTicker" msgid="504686252427747209">"Deitzaile honen dei galdua: <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
@@ -35,11 +37,15 @@
     <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>
+    <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Larrialdi-deiak bakarrik egin daitezke."</string>
+    <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..c2f6ea9 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -16,9 +16,11 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"تماس کاری ازدست‌رفته"</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 +37,15 @@
     <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-fi/strings.xml b/res/values-fi/strings.xml
index 1ff2b0b..d318f6b 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -16,30 +16,36 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"Vastaamaton työ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>
+    <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Vain hätäpuhelut sallittu"</string>
+    <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..d88a8ed 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -16,9 +16,11 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"Appel professionnel manqué"</string>
     <string name="notification_missedCallsTitle" msgid="1361677948941502522">"Appels manqués"</string>
     <string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> appels manqués"</string>
     <string name="notification_missedCallTicker" msgid="504686252427747209">"Appel manqué de <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
@@ -35,11 +37,15 @@
     <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>
+    <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Seuls les appels d\'urgence sont autorisés."</string>
+    <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..9962c6e 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -16,9 +16,11 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"Appel professionnel manqué"</string>
     <string name="notification_missedCallsTitle" msgid="1361677948941502522">"Appels manqués"</string>
     <string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> appels manqués"</string>
     <string name="notification_missedCallTicker" msgid="504686252427747209">"Appel manqué de <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
@@ -31,15 +33,19 @@
     <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>
+    <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Seuls les appels d\'urgence sont autorisés."</string>
+    <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..ef36d3a 100644
--- a/res/values-gl-rES/strings.xml
+++ b/res/values-gl-rES/strings.xml
@@ -16,9 +16,11 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"Chamada de traballo perdida"</string>
     <string name="notification_missedCallsTitle" msgid="1361677948941502522">"Chamadas perdidas"</string>
     <string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> chamadas perdidas"</string>
     <string name="notification_missedCallTicker" msgid="504686252427747209">"Chamada perdida de <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
@@ -35,11 +37,15 @@
     <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>
+    <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Só se permiten chamadas de emerxencia."</string>
+    <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..f5db5ce 100644
--- a/res/values-gu-rIN/strings.xml
+++ b/res/values-gu-rIN/strings.xml
@@ -16,9 +16,11 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"ચૂકી ગયેલ કાર્ય કૉલ"</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 +37,15 @@
     <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-hi/strings.xml b/res/values-hi/strings.xml
index 760fe63..463cdc4 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -16,9 +16,11 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"कार्यस्थल का छूटा हुआ कॉल"</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 +37,15 @@
     <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..5022e7e 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -16,9 +16,11 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"Propušten poslovni poziv"</string>
     <string name="notification_missedCallsTitle" msgid="1361677948941502522">"Propušteni pozivi"</string>
     <string name="notification_missedCallsMsg" msgid="4575787816055205600">"Broj propuštenih poziva: <xliff:g id="NUM_MISSED_CALLS">%s</xliff:g>"</string>
     <string name="notification_missedCallTicker" msgid="504686252427747209">"Propušten poziv kontakta <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
@@ -29,17 +31,21 @@
     <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>
+    <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Dopušteni su samo hitni pozivi."</string>
+    <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..ab39a81 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -16,9 +16,11 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"Nem fogadott munkahelyi hívás"</string>
     <string name="notification_missedCallsTitle" msgid="1361677948941502522">"Nem fogadott hívások"</string>
     <string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> nem fogadott hívás"</string>
     <string name="notification_missedCallTicker" msgid="504686252427747209">"Nem fogadott hívás: <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
@@ -35,11 +37,15 @@
     <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..56773ea 100644
--- a/res/values-hy-rAM/strings.xml
+++ b/res/values-hy-rAM/strings.xml
@@ -16,9 +16,11 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"Բաց թողնված աշխատանքային զանգ"</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 +37,15 @@
     <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-in/strings.xml b/res/values-in/strings.xml
index 7ad8e00..d8f823f 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -16,13 +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">"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_missedWorkCallTitle" msgid="6242489980390803090">"Panggilan tak terjawab di telepon kerja"</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 +32,20 @@
     <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..56610c5 100644
--- a/res/values-is-rIS/strings.xml
+++ b/res/values-is-rIS/strings.xml
@@ -16,9 +16,11 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"Ósvarað vinnusímtal"</string>
     <string name="notification_missedCallsTitle" msgid="1361677948941502522">"Ósvöruð símtöl"</string>
     <string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> ósvöruð símtöl"</string>
     <string name="notification_missedCallTicker" msgid="504686252427747209">"Ósvarað símtal frá <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
@@ -35,11 +37,15 @@
     <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>
+    <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Aðeins neyðarsímöl eru leyfð."</string>
+    <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..47c5a1b 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -16,9 +16,11 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"Chiamata di lavoro persa"</string>
     <string name="notification_missedCallsTitle" msgid="1361677948941502522">"Chiamate senza risposta"</string>
     <string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> chiamate senza risposta"</string>
     <string name="notification_missedCallTicker" msgid="504686252427747209">"Chiamata senza risposta da <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
@@ -35,11 +37,15 @@
     <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>
+    <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Sono consentite soltanto le chiamate di emergenza."</string>
+    <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..af490ca 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -16,9 +16,11 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"שיחה עסקית שלא נענתה"</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 +37,15 @@
     <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..07e9c9f 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -16,9 +16,11 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"仕事の通話の不在着信"</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 +37,15 @@
     <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-ka-rGE/strings.xml b/res/values-ka-rGE/strings.xml
index ac1c20b..b8986a2 100644
--- a/res/values-ka-rGE/strings.xml
+++ b/res/values-ka-rGE/strings.xml
@@ -16,9 +16,11 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"გამოტოვებული ზარი (სამსახური)"</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 +37,15 @@
     <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-kk-rKZ/strings.xml b/res/values-kk-rKZ/strings.xml
index 89be55c..baf6ef7 100644
--- a/res/values-kk-rKZ/strings.xml
+++ b/res/values-kk-rKZ/strings.xml
@@ -16,13 +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">"Телефон"</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_missedWorkCallTitle" msgid="6242489980390803090">"Өткізіп алынған жұмыс қоңырауы"</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 +37,15 @@
     <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..39cbf3d 100644
--- a/res/values-km-rKH/strings.xml
+++ b/res/values-km-rKH/strings.xml
@@ -16,9 +16,11 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"បានខកខានការហៅចូលពីកន្លែងការងារ"</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 +37,15 @@
     <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..60f8caa 100644
--- a/res/values-kn-rIN/strings.xml
+++ b/res/values-kn-rIN/strings.xml
@@ -16,9 +16,11 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"ಮಿಸ್ಡ್‌ ಕೆಲಸದ ಕರೆ"</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>
@@ -26,20 +28,24 @@
     <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>
+    <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-ko/strings.xml b/res/values-ko/strings.xml
index efb59cd..4c51e98 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -16,9 +16,11 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"부재중 업무 통화"</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 +37,15 @@
     <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-ky-rKG/strings.xml b/res/values-ky-rKG/strings.xml
index 6b5e9c3..c805165 100644
--- a/res/values-ky-rKG/strings.xml
+++ b/res/values-ky-rKG/strings.xml
@@ -16,42 +16,36 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"Кабыл алынбай калган чалуу (жумуш)"</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>
+    <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>
-    <!-- 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..02eda11 100644
--- a/res/values-lo-rLA/strings.xml
+++ b/res/values-lo-rLA/strings.xml
@@ -16,9 +16,11 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"ສາຍບໍ່ໄດ້ຮັບຈາກບ່ອນເຮັດວຽກ"</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 +37,15 @@
     <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 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..c801acd 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -16,9 +16,11 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"Praleistas darbo skambutis"</string>
     <string name="notification_missedCallsTitle" msgid="1361677948941502522">"Praleisti skambučiai"</string>
     <string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> praleisti (-ų) skambučiai (-ų)"</string>
     <string name="notification_missedCallTicker" msgid="504686252427747209">"praleistas skambutis nuo <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
@@ -35,11 +37,15 @@
     <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..8f86db9 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -16,9 +16,11 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"Neatbildēts darba zvans"</string>
     <string name="notification_missedCallsTitle" msgid="1361677948941502522">"Neatbildētie zvani"</string>
     <string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> neatbildēts(-i) zvans(-i)"</string>
     <string name="notification_missedCallTicker" msgid="504686252427747209">"Neatbildēts zvans no: <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
@@ -35,11 +37,15 @@
     <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>
+    <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Ir atļauti tikai ārkārtas zvani."</string>
+    <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..6ce21c0 100644
--- a/res/values-mk-rMK/strings.xml
+++ b/res/values-mk-rMK/strings.xml
@@ -16,9 +16,11 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"Пропуштен работен повик"</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 +37,15 @@
     <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-ml-rIN/strings.xml b/res/values-ml-rIN/strings.xml
index 38fc0e6..e51f662 100644
--- a/res/values-ml-rIN/strings.xml
+++ b/res/values-ml-rIN/strings.xml
@@ -16,9 +16,11 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"മിസ്ഡ് ഔദ്യോഗിക കോൾ"</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>
@@ -31,15 +33,19 @@
     <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..12a17aa 100644
--- a/res/values-mn-rMN/strings.xml
+++ b/res/values-mn-rMN/strings.xml
@@ -16,9 +16,11 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"Аваагүй албаны дуудлага"</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 +37,15 @@
     <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-mr-rIN/strings.xml b/res/values-mr-rIN/strings.xml
index c7c795c..6c9a25d 100644
--- a/res/values-mr-rIN/strings.xml
+++ b/res/values-mr-rIN/strings.xml
@@ -16,9 +16,11 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"कार्याचा कॉल चुकविला"</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>
@@ -26,20 +28,24 @@
     <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..0504627 100644
--- a/res/values-ms-rMY/strings.xml
+++ b/res/values-ms-rMY/strings.xml
@@ -16,9 +16,11 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"Panggilan terlepas daripada tempat kerja"</string>
     <string name="notification_missedCallsTitle" msgid="1361677948941502522">"Panggilan tidak dijawab"</string>
     <string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> panggilan tidak dijawab"</string>
     <string name="notification_missedCallTicker" msgid="504686252427747209">"Panggilan tidak dijawab daripada <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
@@ -26,20 +28,24 @@
     <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>
+    <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Panggilan kecemasan sahaja dibenarkan."</string>
+    <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..5db1d5b 100644
--- a/res/values-my-rMM/strings.xml
+++ b/res/values-my-rMM/strings.xml
@@ -16,9 +16,11 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"လွတ်သွားသည့် အလုပ်ဆိုင်ရာ ခ​ေါ်ဆိုမှု"</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 +37,15 @@
     <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..04588eb 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -16,9 +16,11 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"Tapt jobbanrop"</string>
     <string name="notification_missedCallsTitle" msgid="1361677948941502522">"Tapte anrop"</string>
     <string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> tapte anrop"</string>
     <string name="notification_missedCallTicker" msgid="504686252427747209">"Tapt anrop fra <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
@@ -27,19 +29,23 @@
     <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>
+    <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Kun nødanrop er mulig."</string>
+    <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..a804221 100644
--- a/res/values-ne-rNP/strings.xml
+++ b/res/values-ne-rNP/strings.xml
@@ -16,9 +16,11 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"छुटेको कार्यको कल"</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 +37,15 @@
     <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..829bf0b 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -16,9 +16,11 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"Gemiste zakelijke oproep"</string>
     <string name="notification_missedCallsTitle" msgid="1361677948941502522">"Gemiste oproepen"</string>
     <string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> gemiste oproepen"</string>
     <string name="notification_missedCallTicker" msgid="504686252427747209">"Gemiste oproep van <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
@@ -35,11 +37,15 @@
     <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..41e1a4a 100644
--- a/res/values-pa-rIN/strings.xml
+++ b/res/values-pa-rIN/strings.xml
@@ -16,9 +16,11 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"ਕੰਮ ਨਾਲ ਸਬੰਧਿਤ ਖੁੰਝੀ ਕਾਲ"</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 +37,15 @@
     <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-pl/strings.xml b/res/values-pl/strings.xml
index 515190c..813bf29 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -16,9 +16,11 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"Nieodebrane połączenie (praca)"</string>
     <string name="notification_missedCallsTitle" msgid="1361677948941502522">"Połączenia nieodebrane"</string>
     <string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> nieodebranych połączeń"</string>
     <string name="notification_missedCallTicker" msgid="504686252427747209">"Nieodebrane połączenie z <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
@@ -35,11 +37,15 @@
     <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>
+    <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Dozwolone są tylko połączenia alarmowe."</string>
+    <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..cc3db6e 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -16,9 +16,11 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"Chamada de trabalho não atendida"</string>
     <string name="notification_missedCallsTitle" msgid="1361677948941502522">"Chamadas não atendidas"</string>
     <string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> chamadas não atendidas"</string>
     <string name="notification_missedCallTicker" msgid="504686252427747209">"Chamada não atendida de <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
@@ -35,11 +37,15 @@
     <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..9dea13f 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -16,9 +16,11 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"Chamada de trabalho perdida"</string>
     <string name="notification_missedCallsTitle" msgid="1361677948941502522">"Chamadas perdidas"</string>
     <string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> chamadas perdidas"</string>
     <string name="notification_missedCallTicker" msgid="504686252427747209">"Chamada perdida de <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
@@ -35,11 +37,15 @@
     <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>
+    <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Somente chamadas de emergência são permitidas."</string>
+    <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..fd7050d 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -16,13 +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">"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_missedWorkCallTitle" msgid="6242489980390803090">"Apel de serviciu 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 +33,19 @@
     <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>
+    <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Sunt permise doar apelurile de urgență."</string>
+    <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..54d5711 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -16,9 +16,11 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"Пропущенный звонок (работа)"</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 +37,15 @@
     <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="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..f1b014c 100644
--- a/res/values-si-rLK/strings.xml
+++ b/res/values-si-rLK/strings.xml
@@ -16,9 +16,11 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"මග හැරුණ කාර්යාල ඇමතුම"</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 +37,15 @@
     <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-sk/strings.xml b/res/values-sk/strings.xml
index 030d517..9139650 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -16,14 +16,16 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"Zmeškaný pracovný 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 +37,15 @@
     <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..374266e 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -16,9 +16,11 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"Zgrešen delovni klic"</string>
     <string name="notification_missedCallsTitle" msgid="1361677948941502522">"Neodgovorjeni klici"</string>
     <string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> neodgovorjenih klicev"</string>
     <string name="notification_missedCallTicker" msgid="504686252427747209">"Neodgovorjeni klic od <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
@@ -35,11 +37,15 @@
     <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>
+    <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Dovoljeno je samo opravljanje klicev v sili."</string>
+    <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..b328b7f 100644
--- a/res/values-sq-rAL/strings.xml
+++ b/res/values-sq-rAL/strings.xml
@@ -16,13 +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">"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_missedWorkCallTitle" msgid="6242489980390803090">"Telefonatë pune 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 +37,15 @@
     <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>
+    <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Lejohen vetëm telefonatat e urgjencës."</string>
+    <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..2eaadc5 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -16,9 +16,11 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"Пропуштен позив за Work"</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>
@@ -26,7 +28,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 +37,15 @@
     <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-sv/strings.xml b/res/values-sv/strings.xml
index a2fa42d..ddd421c 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -16,14 +16,16 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"Missat jobbsamtal"</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 +37,15 @@
     <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..4686e15 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -16,9 +16,11 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"Simu ya kazini ambayo hukujibu"</string>
     <string name="notification_missedCallsTitle" msgid="1361677948941502522">"Simu zisizojibiwa"</string>
     <string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> simu ambazo hazijajibiwa"</string>
     <string name="notification_missedCallTicker" msgid="504686252427747209">"Simu isiyojibiwa kutoka <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
@@ -35,11 +37,15 @@
     <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>
+    <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Piga simu za dharura pekee."</string>
+    <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..d22d047 100644
--- a/res/values-ta-rIN/strings.xml
+++ b/res/values-ta-rIN/strings.xml
@@ -16,13 +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">"ஃபோன்"</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_missedWorkCallTitle" msgid="6242489980390803090">"தவறிய அழைப்பு (பணி)"</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 +37,15 @@
     <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..83d2074 100644
--- a/res/values-te-rIN/strings.xml
+++ b/res/values-te-rIN/strings.xml
@@ -16,13 +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">"ఫోన్"</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_missedWorkCallTitle" msgid="6242489980390803090">"మిస్డ్ కార్యాలయ కాల్"</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 +33,19 @@
     <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..ee9b6fd 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -16,9 +16,11 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"มีสายจากที่ทำงานที่ไม่ได้รับ"</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 +37,15 @@
     <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-tl/strings.xml b/res/values-tl/strings.xml
index ca749b4..2de1ac5 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -16,9 +16,11 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"Hindi nasagot na tawag sa trabaho"</string>
     <string name="notification_missedCallsTitle" msgid="1361677948941502522">"Mga hindi nasagot na tawag"</string>
     <string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> (na) hindi nasagot na tawag"</string>
     <string name="notification_missedCallTicker" msgid="504686252427747209">"Hindi nasagot na tawag mula kay <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
@@ -26,20 +28,24 @@
     <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>
+    <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Ang mga pang-emergency na tawag lang ang pinapayagan."</string>
+    <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..b3c6698 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -16,9 +16,11 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"İşle ilgili cevapsız çağrı"</string>
     <string name="notification_missedCallsTitle" msgid="1361677948941502522">"Cevapsız çağrılar"</string>
     <string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> cevapsız çağrı"</string>
     <string name="notification_missedCallTicker" msgid="504686252427747209">"Cevapsız çağrı: <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
@@ -35,11 +37,15 @@
     <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>
+    <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Yalnızca acil durum çağrılarına izin veriliyor."</string>
+    <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..7bdfe8c 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -16,9 +16,11 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"Пропущений дзвінок на робочий телефон"</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 +37,15 @@
     <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..ccb3eb0 100644
--- a/res/values-ur-rPK/strings.xml
+++ b/res/values-ur-rPK/strings.xml
@@ -16,9 +16,11 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"کام سے متعلق چھوٹی ہوئی کال"</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 +37,15 @@
     <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-uz-rUZ/strings.xml b/res/values-uz-rUZ/strings.xml
index 1cd5da6..c42152f 100644
--- a/res/values-uz-rUZ/strings.xml
+++ b/res/values-uz-rUZ/strings.xml
@@ -16,30 +16,36 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"Javobsiz ishchi qo‘ng‘irog‘i"</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>
+    <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Faqat favqulodda qo‘ng‘iroqlarga ruxsat berilgan."</string>
+    <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..df5f1c4 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -16,9 +16,11 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"Cuộc gọi nhỡ về công việc"</string>
     <string name="notification_missedCallsTitle" msgid="1361677948941502522">"Cuộc gọi nhỡ"</string>
     <string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> cuộc gọi nhỡ"</string>
     <string name="notification_missedCallTicker" msgid="504686252427747209">"Cuộc gọi nhỡ từ <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
@@ -35,11 +37,15 @@
     <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..eda2169 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -16,9 +16,11 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"未接工作来电"</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 +37,15 @@
     <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-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index c00d0b4..7310bc0 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -16,9 +16,11 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"未接工作來電"</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 +37,15 @@
     <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..137f641 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -16,9 +16,11 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"未接公司來電"</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 +37,15 @@
     <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-zu/strings.xml b/res/values-zu/strings.xml
index acb00f3..5e1cad7 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -16,9 +16,11 @@
 
 <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_missedWorkCallTitle" msgid="6242489980390803090">"Ugeje ikholi yomsebenzi"</string>
     <string name="notification_missedCallsTitle" msgid="1361677948941502522">"Amakholi akuphuthele"</string>
     <string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> amakholi akulahlekele"</string>
     <string name="notification_missedCallTicker" msgid="504686252427747209">"Uphuthelwe ikholi ephuma ku-<xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
@@ -35,11 +37,15 @@
     <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>
+    <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Amakholi aphuthumayo kuphela avunyelwe."</string>
+    <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/res/values/strings.xml b/res/values/strings.xml
index ac8bd49..16b1752 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -30,6 +30,8 @@
     <!-- Notification strings -->
     <!-- Missed call notification label, used when there's exactly one missed call -->
     <string name="notification_missedCallTitle">Missed call</string>
+    <!-- Missed work call notification label, used when there's exactly one missed call [CHAR LIMIT=NONE] -->
+    <string name="notification_missedWorkCallTitle">Missed work call</string>
     <!-- Missed call notification label, used when there are two or more missed calls -->
     <string name="notification_missedCallsTitle">Missed calls</string>
     <!-- Missed call notification message used when there are multiple missed calls -->
@@ -85,7 +87,7 @@
 
     <!-- Message indicating that the user is not allowed to make non-emergency outgoing phone calls
          due to a user restriction -->
-    <string name="outgoing_call_not_allowed_user_restriction">Only emergency calls are allowed by the device owner.</string>
+    <string name="outgoing_call_not_allowed_user_restriction">Only emergency calls are allowed.</string>
 
     <!-- Message indicating that the user is not allowed to make non-emergency outgoing phone calls
          due to the lack of the CALL_PHONE permission -->
diff --git a/scripts/telecom_testing.sh b/scripts/telecom_testing.sh
new file mode 100644
index 0000000..fa48040
--- /dev/null
+++ b/scripts/telecom_testing.sh
@@ -0,0 +1,92 @@
+lite_test_telecom() {
+  usage="
+  Usage: lite_test_telecom [-c CLASSNAME] [-d] [-a | -i] [-e], where
+
+  -c CLASSNAME          Run tests only for the specified class/method. CLASSNAME
+                          should be of the form SomeClassTest or SomeClassTest#testMethod.
+  -d                    Waits for a debugger to attach before starting to run tests.
+  -i                    Rebuild and reinstall the test apk before running tests (mmm).
+  -a                    Rebuild all dependencies and reinstall the test apk before/
+                          running tests (mmma).
+  -e                    Run code coverage. Coverage will be output into the coverage/
+                          directory in the repo root.
+  -h                    This help message.
+  "
+
+  OPTIND=1
+  class=
+  install=false
+  installwdep=false
+  debug=false
+  coverage=false
+
+  while getopts "c:hadie" opt; do
+    case "$opt" in
+      h)
+        echo "$usage"
+        return 0;;
+      \?)
+        echo "$usage"
+        return 0;;
+      c)
+        class=$OPTARG;;
+      d)
+        debug=true;;
+      i)
+        install=true;;
+      a)
+        install=true
+        installwdep=true;;
+      e)
+        coverage=true;;
+    esac
+  done
+
+  T=$(gettop)
+
+  if [ $install = true ] ; then
+    olddir=$(pwd)
+    cd $T
+    if [ $coverage = true ] ; then
+      emma_opt="EMMA_INSTRUMENT_STATIC=true"
+    else
+      emma_opt="EMMA_INSTRUMENT_STATIC=false"
+    fi
+    # Build and exit script early if build fails
+    if [ $installwdep = true ] ; then
+      ANDROID_COMPILE_WITH_JACK=false mmma -j40 "packages/services/Telecomm/tests" ${emma_opt}
+    else
+      ANDROID_COMPILE_WITH_JACK=false mmm "packages/services/Telecomm/tests" ${emma_opt}
+    fi
+    if [ $? -ne 0 ] ; then
+      echo "Make failed! try using -a instead of -i if building with coverage"
+      return
+    fi
+
+    adb install -r -t "out/target/product/$TARGET_PRODUCT/data/app/TelecomUnitTests/TelecomUnitTests.apk"
+    if [ $? -ne 0 ] ; then
+      cd "$olddir"
+      return $?
+    fi
+    cd "$olddir"
+  fi
+
+  e_options=""
+  if [ -n "$class" ] ; then
+    e_options="${e_options} -e class com.android.server.telecom.tests.${class}"
+  fi
+  if [ $debug = true ] ; then
+    e_options="${e_options} -e debug 'true'"
+  fi
+  if [ $coverage = true ] ; then
+    e_options="${e_options} -e coverage 'true'"
+  fi
+  adb shell am instrument ${e_options} -w com.android.server.telecom.tests/android.test.InstrumentationTestRunner
+
+  if [ $coverage = true ] ; then
+    adb root
+    adb wait-for-device
+    adb pull /data/user/0/com.android.server.telecom.tests/files/coverage.ec /tmp/
+    java -cp external/emma/lib/emma.jar emma report -r html -sp packages/services/Telecomm/src -in out/target/common/obj/APPS/TelecomUnitTests_intermediates/coverage.em -in /tmp/coverage.ec
+  fi
+}
diff --git a/src/com/android/server/telecom/Analytics.java b/src/com/android/server/telecom/Analytics.java
new file mode 100644
index 0000000..0ba8592
--- /dev/null
+++ b/src/com/android/server/telecom/Analytics.java
@@ -0,0 +1,306 @@
+/*
+ * 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.os.Parcelable;
+import android.telecom.ParcelableCallAnalytics;
+import android.telecom.DisconnectCause;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.CallerInfo;
+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'
+                    + "    connectionService: " + connectionService + '\n'
+                    + "}\n";
+        }
+
+        public ParcelableCallAnalytics toParcelableAnalytics() {
+            // Rounds up to the nearest second.
+            long callDuration = endTime == 0 ? 0 : endTime - startTime;
+            callDuration += (callDuration % MILLIS_IN_1_SECOND == 0) ?
+                    0 : (MILLIS_IN_1_SECOND - callDuration % MILLIS_IN_1_SECOND);
+            return new ParcelableCallAnalytics(
+                    // rounds down to nearest 5 minute mark
+                    startTime - startTime % ParcelableCallAnalytics.MILLIS_IN_5_MINUTES,
+                    callDuration,
+                    callDirection,
+                    isAdditionalCall,
+                    isInterrupted,
+                    callTechnologies,
+                    callTerminationReason == null ?
+                            ParcelableCallAnalytics.STILL_CONNECTED :
+                            callTerminationReason.getCode(),
+                    isEmergency,
+                    connectionService,
+                    createdFromExistingConnection);
+        }
+
+        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 = ParcelableCallAnalytics.CALLTYPE_UNKNOWN;
+    public static final int INCOMING_DIRECTION = ParcelableCallAnalytics.CALLTYPE_INCOMING;
+    public static final int OUTGOING_DIRECTION = ParcelableCallAnalytics.CALLTYPE_OUTGOING;
+
+    // Constants for call technology
+    public static final int CDMA_PHONE = ParcelableCallAnalytics.CDMA_PHONE;
+    public static final int GSM_PHONE = ParcelableCallAnalytics.GSM_PHONE;
+    public static final int IMS_PHONE = ParcelableCallAnalytics.IMS_PHONE;
+    public static final int SIP_PHONE = ParcelableCallAnalytics.SIP_PHONE;
+    public static final int THIRD_PARTY_PHONE = ParcelableCallAnalytics.THIRD_PARTY_PHONE;
+
+    public static final long MILLIS_IN_1_SECOND = ParcelableCallAnalytics.MILLIS_IN_1_SECOND;
+
+    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 ParcelableCallAnalytics[] dumpToParcelableAnalytics() {
+        ParcelableCallAnalytics[] result;
+        synchronized (sLock) {
+            result = new ParcelableCallAnalytics[sCallIdToInfo.size()];
+            int idx = 0;
+            for (CallInfoImpl entry : sCallIdToInfo.values()) {
+                result[idx] = entry.toParcelableAnalytics();
+                idx++;
+            }
+            sCallIdToInfo.clear();
+        }
+        return result;
+    }
+
+    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/AsyncBlockCheckTask.java b/src/com/android/server/telecom/AsyncBlockCheckTask.java
new file mode 100644
index 0000000..282a4eb
--- /dev/null
+++ b/src/com/android/server/telecom/AsyncBlockCheckTask.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2016 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.os.AsyncTask;
+import android.os.Handler;
+
+import com.android.internal.telephony.BlockChecker;
+
+/**
+ * An {@link AsyncTask} that checks if a call needs to be blocked.
+ * <p> An {@link AsyncTask} is used to perform the block check to avoid blocking the main thread.
+ * The block check itself is performed in the {@link AsyncTask#doInBackground(Object[])}. However
+ * a {@link Handler} passed by the caller is used to perform additional possibly intensive
+ * operations such as call screening.
+ */
+class AsyncBlockCheckTask extends AsyncTask<String, Void, Boolean> {
+    private final Context mContext;
+    private final Call mIncomingCall;
+    private final CallScreening mCallScreening;
+    private final boolean mShouldSendToVoicemail;
+    private final Handler mHandler = new Handler();
+    private final Object mCallScreeningListenerLock = new Object();
+
+    private Session mLogSubsession;
+    private Runnable mBlockCheckTimeoutRunnable = new Runnable("ABCT.oPE") {
+        @Override
+        public void loggedRun() {
+            synchronized (mCallScreeningListenerLock) {
+                if (mCallScreeningListener != null) {
+                    timeoutBlockCheck();
+                    mCallScreeningListener = null;
+                }
+            }
+        }
+    };
+    private CallScreening.Listener mCallScreeningListener;
+
+    AsyncBlockCheckTask(Context context, Call incomingCall, CallScreening callScreening,
+                        CallScreening.Listener callScreeningListener,
+                        boolean shouldSendToVoicemail) {
+        mContext = context;
+        mIncomingCall = incomingCall;
+        mCallScreening = callScreening;
+        mCallScreeningListener = callScreeningListener;
+        mShouldSendToVoicemail = shouldSendToVoicemail;
+    }
+
+    @Override
+    protected void onPreExecute() {
+        mLogSubsession = Log.createSubsession();
+        mHandler.postDelayed(mBlockCheckTimeoutRunnable.prepare(),
+                Timeouts.getBlockCheckTimeoutMillis(mContext.getContentResolver()));
+    }
+
+    private void timeoutBlockCheck() {
+        Log.event(mIncomingCall, Log.Events.BLOCK_CHECK_TIMED_OUT);
+        mCallScreeningListener.onCallScreeningCompleted(
+                mIncomingCall,
+                true /*shouldAllowCall*/,
+                false /*shouldReject*/,
+                false /*shouldAddToCallLog*/,
+                false /*shouldShowNotification*/);
+    }
+
+    @Override
+    protected Boolean doInBackground(String... params) {
+        try {
+            Log.continueSession(mLogSubsession, "ABCT.DIB");
+            Log.event(mIncomingCall, Log.Events.BLOCK_CHECK_INITIATED);
+            return BlockChecker.isBlocked(mContext, params[0]);
+        } finally {
+            Log.endSession();
+        }
+    }
+
+    @Override
+    protected void onPostExecute(Boolean isBlocked) {
+        synchronized (mCallScreeningListenerLock) {
+            mHandler.removeCallbacks(null);
+            mBlockCheckTimeoutRunnable.cancel();
+            if (mCallScreeningListener != null) {
+                processIsBlockedLocked(isBlocked);
+                mCallScreeningListener = null;
+            }
+        }
+    }
+
+    private void processIsBlockedLocked(boolean isBlocked) {
+        Log.event(mIncomingCall, Log.Events.BLOCK_CHECK_FINISHED);
+        if (isBlocked) {
+            mCallScreeningListener.onCallScreeningCompleted(
+                    mIncomingCall,
+                    false /*shouldAllowCall*/,
+                    true /*shouldReject*/,
+                    false /*shouldAddToCallLog*/,
+                    false /*shouldShowNotification*/);
+        } else if (mShouldSendToVoicemail) {
+            mCallScreeningListener.onCallScreeningCompleted(
+                    mIncomingCall,
+                    false /*shouldAllowCall*/,
+                    true /*shouldReject*/,
+                    true /*shouldAddToCallLog*/,
+                    true /*shouldShowNotification*/);
+        } else {
+            mCallScreening.screenCall();
+        }
+    }
+}
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..8dd5a7b 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,21 +38,34 @@
  * 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() {
                 @Override
                 public void onServiceConnected(int profile, BluetoothProfile proxy) {
-                    mBluetoothHeadset = (BluetoothHeadset) proxy;
-                    Log.v(this, "- Got BluetoothHeadset: " + mBluetoothHeadset);
-                    updateBluetoothState();
+                    Log.startSession("BMSL.oSC");
+                    try {
+                        mBluetoothHeadset = (BluetoothHeadset) proxy;
+                        Log.v(this, "- Got BluetoothHeadset: " + mBluetoothHeadset);
+                        updateBluetoothState();
+                    } finally {
+                        Log.endSession();
+                    }
                 }
 
                 @Override
                 public void onServiceDisconnected(int profile) {
-                    mBluetoothHeadset = null;
-                    Log.v(this, "Lost BluetoothHeadset: " + mBluetoothHeadset);
-                    updateBluetoothState();
+                    Log.startSession("BMSL.oSD");
+                    try {
+                        mBluetoothHeadset = null;
+                        Log.v(this, "Lost BluetoothHeadset: " + mBluetoothHeadset);
+                        updateBluetoothState();
+                    } finally {
+                        Log.endSession();
+                    }
                 }
            };
 
@@ -59,36 +75,55 @@
     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            String action = intent.getAction();
+            Log.startSession("BM.oR");
+            try {
+                String action = intent.getAction();
 
-            if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) {
-                int bluetoothHeadsetState = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE,
-                                                          BluetoothHeadset.STATE_DISCONNECTED);
-                Log.d(this, "mReceiver: HEADSET_STATE_CHANGED_ACTION");
-                Log.d(this, "==> new state: %s ", bluetoothHeadsetState);
-                updateBluetoothState();
-            } else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
-                int bluetoothHeadsetAudioState =
-                        intent.getIntExtra(BluetoothHeadset.EXTRA_STATE,
-                                           BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
-                Log.d(this, "mReceiver: HEADSET_AUDIO_STATE_CHANGED_ACTION");
-                Log.d(this, "==> new state: %s", bluetoothHeadsetAudioState);
-                updateBluetoothState();
+                if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) {
+                    int bluetoothHeadsetState = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE,
+                            BluetoothHeadset.STATE_DISCONNECTED);
+                    Log.d(this, "mReceiver: HEADSET_STATE_CHANGED_ACTION");
+                    Log.d(this, "==> new state: %s ", bluetoothHeadsetState);
+                    updateBluetoothState();
+                } else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
+                    int bluetoothHeadsetAudioState =
+                            intent.getIntExtra(BluetoothHeadset.EXTRA_STATE,
+                                    BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
+                    Log.d(this, "mReceiver: HEADSET_AUDIO_STATE_CHANGED_ACTION");
+                    Log.d(this, "==> new state: %s", bluetoothHeadsetAudioState);
+                    updateBluetoothState();
+                }
+            } finally {
+                Log.endSession();
             }
         }
     };
 
+    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("BM.cBA") {
+        @Override
+        public void loggedRun() {
+            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 +137,10 @@
         context.registerReceiver(mReceiver, intentFilter);
     }
 
+    public void setBluetoothStateListener(BluetoothStateListener bluetoothStateListener) {
+        mBluetoothStateListener = bluetoothStateListener;
+    }
+
     //
     // Bluetooth helper methods.
     //
@@ -121,7 +160,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 +231,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 +244,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 +257,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 +274,22 @@
         // instantly. (See isBluetoothAudioConnectedOrPending() above.)
         mBluetoothConnectionPending = true;
         mBluetoothConnectionRequestTime = SystemClock.elapsedRealtime();
+        mHandler.removeCallbacks(mBluetoothConnectionTimeout.getRunnableToCancel());
+        mBluetoothConnectionTimeout.cancel();
+        // If the mBluetoothConnectionTimeout runnable has run, the session had been cleared...
+        // Create a new Session before putting it back in the queue to possibly run again.
+        mHandler.postDelayed(mBluetoothConnectionTimeout.prepare(),
+                Timeouts.getBluetoothPendingTimeoutMillis(mContext.getContentResolver()));
     }
 
-    void disconnectBluetoothAudio() {
+    @VisibleForTesting
+    public void disconnectBluetoothAudio() {
         Log.v(this, "disconnectBluetoothAudio()...");
         if (mBluetoothHeadset != null) {
             mBluetoothHeadset.disconnectAudio();
         }
+        mHandler.removeCallbacks(mBluetoothConnectionTimeout.getRunnableToCancel());
+        mBluetoothConnectionTimeout.cancel();
         mBluetoothConnectionPending = false;
     }
 
diff --git a/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java b/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java
index dab4545..6680eb6 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,12 +86,13 @@
      * 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) {
                 enforceModifyPermission();
-
+                Log.startSession("BPSI.aC");
                 long token = Binder.clearCallingIdentity();
                 try {
                     Log.i(TAG, "BT - answering call");
@@ -96,6 +104,7 @@
                     return false;
                 } finally {
                     Binder.restoreCallingIdentity(token);
+                    Log.endSession();
                 }
 
             }
@@ -105,7 +114,7 @@
         public boolean hangupCall() throws RemoteException {
             synchronized (mLock) {
                 enforceModifyPermission();
-
+                Log.startSession("BPSI.hC");
                 long token = Binder.clearCallingIdentity();
                 try {
                     Log.i(TAG, "BT - hanging up call");
@@ -117,6 +126,7 @@
                     return false;
                 } finally {
                     Binder.restoreCallingIdentity(token);
+                    Log.endSession();
                 }
             }
         }
@@ -125,7 +135,7 @@
         public boolean sendDtmf(int dtmf) throws RemoteException {
             synchronized (mLock) {
                 enforceModifyPermission();
-
+                Log.startSession("BPSI.sD");
                 long token = Binder.clearCallingIdentity();
                 try {
                     Log.i(TAG, "BT - sendDtmf %c", Log.DEBUG ? dtmf : '.');
@@ -140,6 +150,7 @@
                     return false;
                 } finally {
                     Binder.restoreCallingIdentity(token);
+                    Log.endSession();
                 }
             }
         }
@@ -148,7 +159,7 @@
         public String getNetworkOperator() throws RemoteException {
             synchronized (mLock) {
                 enforceModifyPermission();
-
+                Log.startSession("BPSI.gNO");
                 long token = Binder.clearCallingIdentity();
                 try {
                     Log.i(TAG, "getNetworkOperator");
@@ -162,6 +173,7 @@
                     }
                 } finally {
                     Binder.restoreCallingIdentity(token);
+                    Log.endSession();
                 }
             }
         }
@@ -170,7 +182,7 @@
         public String getSubscriberNumber() throws RemoteException {
             synchronized (mLock) {
                 enforceModifyPermission();
-
+                Log.startSession("BPSI.gSN");
                 long token = Binder.clearCallingIdentity();
                 try {
                     Log.i(TAG, "getSubscriberNumber");
@@ -189,6 +201,7 @@
                     return address;
                 } finally {
                     Binder.restoreCallingIdentity(token);
+                    Log.endSession();
                 }
             }
         }
@@ -197,7 +210,7 @@
         public boolean listCurrentCalls() throws RemoteException {
             synchronized (mLock) {
                 enforceModifyPermission();
-
+                Log.startSession("BPSI.lCC");
                 long token = Binder.clearCallingIdentity();
                 try {
                     // only log if it is after we recently updated the headset state or else it can
@@ -213,6 +226,7 @@
                     return true;
                 } finally {
                     Binder.restoreCallingIdentity(token);
+                    Log.endSession();
                 }
             }
         }
@@ -221,7 +235,7 @@
         public boolean queryPhoneState() throws RemoteException {
             synchronized (mLock) {
                 enforceModifyPermission();
-
+                Log.startSession("BPSI.qPS");
                 long token = Binder.clearCallingIdentity();
                 try {
                     Log.i(TAG, "queryPhoneState");
@@ -229,6 +243,7 @@
                     return true;
                 } finally {
                     Binder.restoreCallingIdentity(token);
+                    Log.endSession();
                 }
             }
         }
@@ -237,13 +252,14 @@
         public boolean processChld(int chld) throws RemoteException {
             synchronized (mLock) {
                 enforceModifyPermission();
-
+                Log.startSession("BPSI.pC");
                 long token = Binder.clearCallingIdentity();
                 try {
                     Log.i(TAG, "processChld %d", chld);
                     return BluetoothPhoneServiceImpl.this.processChld(chld);
                 } finally {
                     Binder.restoreCallingIdentity(token);
+                    Log.endSession();
                 }
             }
         }
@@ -271,7 +287,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 */);
@@ -309,12 +326,6 @@
         }
 
         @Override
-        public void onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall) {
-            // The BluetoothPhoneService does not need to respond to changes in foreground calls,
-            // which are always accompanied by call state changes anyway.
-        }
-
-        @Override
         public void onIsConferencedChanged(Call call) {
             /*
              * Filter certain onIsConferencedChanged callbacks. Unfortunately this needs to be done
@@ -350,27 +361,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 +402,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 +444,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 +503,7 @@
                     if (!conferenceable.isEmpty()) {
                         mCallsManager.conference(activeCall, conferenceable.get(0));
                         return true;
-                   }
+                    }
                 }
             }
         }
@@ -544,7 +562,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 +639,6 @@
      *      changed.
      */
     private void updateHeadsetWithCallState(boolean force) {
-        CallsManager callsManager = mCallsManager;
         Call activeCall = mCallsManager.getActiveCall();
         Call ringingCall = mCallsManager.getRingingCall();
         Call heldCall = mCallsManager.getHeldCall();
@@ -676,11 +693,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 +829,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 fbfd2a4..7b342bc 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;
@@ -68,13 +69,18 @@
     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);
@@ -106,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
@@ -164,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();
                         }
                     }
                 }
@@ -178,28 +187,34 @@
                         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() {
-        @Override
-        public void run() {
-            synchronized (mLock) {
-                processDirectToVoicemail();
-            }
+    private class CallSessionCookie {
+        Call mSessionCall;
+        Session mSession;
+
+        public CallSessionCookie(Call call, Session session) {
+            mSessionCall = call;
+            mSession = session;
         }
-    };
+    }
 
-    /** True if this is an incoming call. */
-    private final boolean mIsIncoming;
-
-    /** 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.
+    /**
+     * 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
@@ -223,6 +238,8 @@
 
     private PhoneAccountHandle mTargetPhoneAccountHandle;
 
+    private UserHandle mInitiatingUser;
+
     private final Handler mHandler = new Handler(Looper.getMainLooper());
 
     private final List<Call> mConferenceableCalls = new ArrayList<>();
@@ -301,6 +318,8 @@
 
     private boolean mIsConference = false;
 
+    private final boolean mShouldAttachToExistingConnection;
+
     private Call mParentCall = null;
 
     private List<Call> mChildCalls = new LinkedList<>();
@@ -323,6 +342,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;
 
@@ -339,6 +360,11 @@
      */
     private long mCallDataUsage = DATA_USAGE_NOT_SET;
 
+    private boolean mIsWorkCall;
+
+    // Set to true once the NewOutgoingCallIntentBroadcast comes back and is processed.
+    private boolean mIsNewOutgoingCallIntentBroadcastDone = false;
+
     /**
      * Persists the specified parameters and initializes the new instance.
      *
@@ -351,9 +377,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,
@@ -364,8 +394,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;
@@ -374,12 +406,17 @@
         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);
     }
@@ -396,10 +433,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,
@@ -410,15 +451,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) {
@@ -431,6 +474,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);
     }
@@ -446,7 +510,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),
@@ -484,7 +548,8 @@
         return sb.toString();
     }
 
-    int getState() {
+    @VisibleForTesting
+    public int getState() {
         return mState;
     }
 
@@ -511,6 +576,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
@@ -535,6 +608,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.
@@ -547,6 +621,7 @@
                 mDisconnectTimeMillis = 0;
             } else if (mState == CallState.DISCONNECTED) {
                 mDisconnectTimeMillis = System.currentTimeMillis();
+                mAnalytics.setCallEndTime(mDisconnectTimeMillis);
                 setLocallyDisconnecting(false);
                 fixParentAfterDisconnect();
             }
@@ -609,7 +684,8 @@
         return mRingbackRequested;
     }
 
-    boolean isConference() {
+    @VisibleForTesting
+    public boolean isConference() {
         return mIsConference;
     }
 
@@ -617,6 +693,10 @@
         return mHandle;
     }
 
+    public String getPostDialDigits() {
+        return mPostDialDigits;
+    }
+
     int getHandlePresentation() {
         return mHandlePresentation;
     }
@@ -697,6 +777,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;
     }
 
@@ -704,7 +785,8 @@
         return mDisconnectCause;
     }
 
-    boolean isEmergencyCall() {
+    @VisibleForTesting
+    public boolean isEmergencyCall() {
         return mIsEmergencyCall;
     }
 
@@ -719,7 +801,8 @@
         return getHandle();
     }
 
-    GatewayInfo getGatewayInfo() {
+    @VisibleForTesting
+    public GatewayInfo getGatewayInfo() {
         return mGatewayInfo;
     }
 
@@ -727,11 +810,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) {
@@ -741,21 +826,52 @@
 
     }
 
-    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) {
                 l.onTargetPhoneAccountChanged(this);
             }
+            configureIsWorkCall();
         }
     }
 
-    boolean isIncoming() {
-        return mIsIncoming;
+    @VisibleForTesting
+    public boolean isIncoming() {
+        return mCallDirection == CALL_DIRECTION_INCOMING;
+    }
+
+    public boolean isWorkCall() {
+        return mIsWorkCall;
+    }
+
+    private void configureIsWorkCall() {
+        PhoneAccountRegistrar phoneAccountRegistrar = mCallsManager.getPhoneAccountRegistrar();
+        boolean isWorkCall = false;
+        PhoneAccount phoneAccount =
+                phoneAccountRegistrar.getPhoneAccountUnchecked(mTargetPhoneAccountHandle);
+        if (phoneAccount != null) {
+            final UserHandle userHandle;
+            if (phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_MULTI_USER)) {
+                userHandle = mInitiatingUser;
+            } else {
+                userHandle = mTargetPhoneAccountHandle.getUserHandle();
+            }
+            if (userHandle != null) {
+                isWorkCall = UserUtil.isManagedProfile(mContext, userHandle);
+            }
+        }
+        mIsWorkCall = isWorkCall;
+    }
+
+    boolean shouldAttachToExistingConnection() {
+        return mShouldAttachToExistingConnection;
     }
 
     /**
@@ -763,7 +879,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)) {
@@ -816,23 +933,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;
     }
 
@@ -845,13 +967,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);
     }
 
@@ -876,20 +1000,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;
@@ -903,7 +1027,13 @@
      * @param phoneAccountRegistrar The phone account registrar.
      */
     void startCreateConnection(PhoneAccountRegistrar phoneAccountRegistrar) {
-        Preconditions.checkState(mCreateConnectionProcessor == null);
+        if (mCreateConnectionProcessor != null) {
+            Log.w(this, "mCreateConnectionProcessor in startCreateConnection is not null. This is" +
+                    " due to a race between NewOutgoingCallIntentBroadcaster and " +
+                    "phoneAccountSelected, but is harmlessly resolved by ignoring the second " +
+                    "invocation.");
+            return;
+        }
         mCreateConnectionProcessor = new CreateConnectionProcessor(this, mRepository, this,
                 phoneAccountRegistrar, mContext);
         mCreateConnectionProcessor.process();
@@ -931,26 +1061,37 @@
             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 Runnable("C.hCCS") {
+                    @Override
+                    public void loggedRun() {
+                         synchronized (mLock) {
+                             processDirectToVoicemail();
+                         }
+                    }
+                }.prepare(), 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;
         }
     }
 
@@ -960,18 +1101,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;
         }
     }
 
@@ -1001,14 +1146,29 @@
         }
     }
 
-    void disconnect() {
+    /**
+     * Silences the ringer.
+     */
+    void silence() {
+        if (mConnectionService == null) {
+            Log.w(this, "silence() request on a call without a connection service.");
+        } else {
+            Log.i(this, "Send silence to connection service for call %s", this);
+            Log.event(this, Log.Events.SILENCE);
+            mConnectionService.silence(this);
+        }
+    }
+
+    @VisibleForTesting
+    public void disconnect() {
         disconnect(false);
     }
 
     /**
      * Attempts to disconnect the call through the connection service.
      */
-    void disconnect(boolean wasViaNewOutgoingCallBroadcaster) {
+    @VisibleForTesting
+    public void disconnect(boolean wasViaNewOutgoingCallBroadcaster) {
         Log.event(this, Log.Events.REQUEST_DISCONNECT);
 
         // Track that the call is now locally disconnecting.
@@ -1069,7 +1229,8 @@
      *
      * @param videoState The video state in which to answer the call.
      */
-    void answer(int videoState) {
+    @VisibleForTesting
+    public void answer(int videoState) {
         Preconditions.checkNotNull(mConnectionService);
 
         // Check to verify that the call is still in the ringing state. A call can change states
@@ -1090,7 +1251,8 @@
      * @param rejectWithMessage Whether to send a text message as part of the call rejection.
      * @param textMessage An optional text message to send as part of the rejection.
      */
-    void reject(boolean rejectWithMessage, String textMessage) {
+    @VisibleForTesting
+    public void reject(boolean rejectWithMessage, String textMessage) {
         Preconditions.checkNotNull(mConnectionService);
 
         // Check to verify that the call is still in the ringing state. A call can change states
@@ -1129,7 +1291,8 @@
     }
 
     /** Checks if this is a live call or not. */
-    boolean isAlive() {
+    @VisibleForTesting
+    public boolean isAlive() {
         switch (mState) {
             case CallState.NEW:
             case CallState.RINGING:
@@ -1167,7 +1330,8 @@
     /**
      * @return the uri of the contact associated with this call.
      */
-    Uri getContactUri() {
+    @VisibleForTesting
+    public Uri getContactUri() {
         if (mCallerInfo == null || !mCallerInfo.contactExists) {
             return getHandle();
         }
@@ -1212,7 +1376,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)) {
@@ -1222,7 +1387,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)) {
@@ -1280,11 +1446,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;
     }
 
@@ -1411,17 +1579,27 @@
         mCallerInfo = null;
         if (!TextUtils.isEmpty(number)) {
             Log.v(this, "Looking up information for: %s.", Log.piiHandle(number));
-            mHandler.post(new Runnable() {
+            mHandler.post(new Runnable("C.sCIL") {
                 @Override
-                public void run() {
-                    mCallerInfoAsyncQueryFactory.startQuery(
-                            mQueryToken,
-                            mContext,
-                            number,
-                            mCallerInfoQueryListener,
-                            Call.this);
+                public void loggedRun() {
+                    Session subsubsession = null;
+                    try {
+                        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);
+                        }
+                    }
                 }
-            });
+            }.prepare());
         }
     }
 
@@ -1441,15 +1619,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);
@@ -1461,7 +1649,7 @@
         Trace.endSection();
     }
 
-    CallerInfo getCallerInfo() {
+    public CallerInfo getCallerInfo() {
         return mCallerInfo;
     }
 
@@ -1484,7 +1672,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(
@@ -1636,11 +1826,7 @@
     }
 
     public boolean isUnknown() {
-        return mIsUnknown;
-    }
-
-    public void setIsUnknown(boolean isUnknown) {
-        mIsUnknown = isUnknown;
+        return mCallDirection == CALL_DIRECTION_UNKNOWN;
     }
 
     /**
@@ -1661,6 +1847,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:
@@ -1707,4 +1909,16 @@
     public long getCallDataUsage() {
         return mCallDataUsage;
     }
+
+    /**
+     * Returns true if the call is outgoing and the NEW_OUTGOING_CALL ordered broadcast intent
+     * has come back to telecom and was processed.
+     */
+    public boolean isNewOutgoingCallIntentBroadcastDone() {
+        return mIsNewOutgoingCallIntentBroadcastDone;
+    }
+
+    public void setNewOutgoingCallIntentBroadcastIsDone() {
+        mIsNewOutgoingCallIntentBroadcastDone = true;
+    }
 }
diff --git a/src/com/android/server/telecom/CallAudioManager.java b/src/com/android/server/telecom/CallAudioManager.java
index 23284e3..1a5cc7b 100644
--- a/src/com/android/server/telecom/CallAudioManager.java
+++ b/src/com/android/server/telecom/CallAudioManager.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2014 The Android Open Source Project
+ * 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.
@@ -11,331 +11,276 @@
  * 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.
+ * 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.annotation.NonNull;
 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.media.ToneGenerator;
 import android.telecom.CallAudioState;
+import android.telecom.VideoProfile;
+import android.util.SparseArray;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.IndentingPrintWriter;
-import com.android.internal.util.Preconditions;
 
-import java.util.Objects;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.LinkedHashSet;
 
-/**
- * This class manages audio modes, streams and other properties.
- */
-final class CallAudioManager extends CallsManagerListenerBase
-        implements WiredHeadsetManager.Listener, DockManager.Listener {
-    private static final int STREAM_NONE = -1;
+public class CallAudioManager extends CallsManagerListenerBase {
 
-    private static final String STREAM_DESCRIPTION_NONE = "STEAM_NONE";
-    private static final String STREAM_DESCRIPTION_ALARM = "STEAM_ALARM";
-    private static final String STREAM_DESCRIPTION_BLUETOOTH_SCO = "STREAM_BLUETOOTH_SCO";
-    private static final String STREAM_DESCRIPTION_DTMF = "STREAM_DTMF";
-    private static final String STREAM_DESCRIPTION_MUSIC = "STREAM_MUSIC";
-    private static final String STREAM_DESCRIPTION_NOTIFICATION = "STREAM_NOTIFICATION";
-    private static final String STREAM_DESCRIPTION_RING = "STREAM_RING";
-    private static final String STREAM_DESCRIPTION_SYSTEM = "STREAM_SYSTEM";
-    private static final String STREAM_DESCRIPTION_VOICE_CALL = "STREAM_VOICE_CALL";
+    public interface AudioServiceFactory {
+        IAudioService getAudioService();
+    }
 
-    private static final String MODE_DESCRIPTION_INVALID = "MODE_INVALID";
-    private static final String MODE_DESCRIPTION_CURRENT = "MODE_CURRENT";
-    private static final String MODE_DESCRIPTION_NORMAL = "MODE_NORMAL";
-    private static final String MODE_DESCRIPTION_RINGTONE = "MODE_RINGTONE";
-    private static final String MODE_DESCRIPTION_IN_CALL = "MODE_IN_CALL";
-    private static final String MODE_DESCRIPTION_IN_COMMUNICATION = "MODE_IN_COMMUNICATION";
+    private final String LOG_TAG = CallAudioManager.class.getSimpleName();
 
-    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;
+    private final LinkedHashSet<Call> mActiveOrDialingCalls;
+    private final LinkedHashSet<Call> mRingingCalls;
+    private final LinkedHashSet<Call> mHoldingCalls;
+    private final Set<Call> mCalls;
+    private final SparseArray<LinkedHashSet<Call>> mCallStateToCalls;
 
-    private final Handler mAudioManagerHandler = new Handler(Looper.getMainLooper()) {
-
-        private AudioManager mAudioManager;
-
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MSG_AUDIO_MANAGER_INITIALIZE: {
-                    mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
-                    break;
-                }
-                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);
-                    }
-                    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());
-
-                            } 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.
-                        }
-                    }
-
-                    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;
-            }
-        }
-    };
-
-    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 CallAudioRouteStateMachine mCallAudioRouteStateMachine;
+    private final CallAudioModeStateMachine mCallAudioModeStateMachine;
     private final CallsManager mCallsManager;
+    private final InCallTonePlayer.Factory mPlayerFactory;
+    private final Ringer mRinger;
+    private final RingbackPlayer mRingbackPlayer;
+    private final DtmfLocalTonePlayer mDtmfLocalTonePlayer;
 
-    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;
+    private Call mForegroundCall;
+    private boolean mIsTonePlaying = false;
 
-    CallAudioManager(
-            Context context,
-            TelecomSystem.SyncRoot lock,
-            StatusBarNotifier statusBarNotifier,
-            WiredHeadsetManager wiredHeadsetManager,
-            DockManager dockManager,
-            CallsManager callsManager) {
-        mContext = context;
-        mLock = lock;
-        mAudioManagerHandler.obtainMessage(MSG_AUDIO_MANAGER_INITIALIZE, 0, 0).sendToTarget();
-        mStatusBarNotifier = statusBarNotifier;
-        mBluetoothManager = new BluetoothManager(context, this);
-        mWiredHeadsetManager = wiredHeadsetManager;
+    public CallAudioManager(CallAudioRouteStateMachine callAudioRouteStateMachine,
+            CallsManager callsManager,
+            CallAudioModeStateMachine callAudioModeStateMachine,
+            InCallTonePlayer.Factory playerFactory,
+            Ringer ringer,
+            RingbackPlayer ringbackPlayer,
+            DtmfLocalTonePlayer dtmfLocalTonePlayer) {
+        mActiveOrDialingCalls = new LinkedHashSet<>();
+        mRingingCalls = new LinkedHashSet<>();
+        mHoldingCalls = new LinkedHashSet<>();
+        mCalls = new HashSet<>();
+        mCallStateToCalls = new SparseArray<LinkedHashSet<Call>>() {{
+            put(CallState.ACTIVE, mActiveOrDialingCalls);
+            put(CallState.DIALING, mActiveOrDialingCalls);
+            put(CallState.RINGING, mRingingCalls);
+            put(CallState.ON_HOLD, mHoldingCalls);
+        }};
+
+        mCallAudioRouteStateMachine = callAudioRouteStateMachine;
+        mCallAudioModeStateMachine = callAudioModeStateMachine;
         mCallsManager = callsManager;
+        mPlayerFactory = playerFactory;
+        mRinger = ringer;
+        mRingbackPlayer = ringbackPlayer;
+        mDtmfLocalTonePlayer = dtmfLocalTonePlayer;
 
-        mWiredHeadsetManager.addListener(this);
-        mDockManager = dockManager;
-        mDockManager.addListener(this);
-
-        saveAudioState(getInitialAudioState(null));
-        mAudioFocusStreamType = STREAM_NONE;
-    }
-
-    CallAudioState getCallAudioState() {
-        return mCallAudioState;
-    }
-
-    @Override
-    public void onCallAdded(Call call) {
-        Log.v(this, "onCallAdded");
-        onCallUpdated(call);
-
-        if (hasFocus() && getForegroundCall() == call) {
-            if (!call.isIncoming()) {
-                // Unmute new outgoing call.
-                setSystemAudioState(false, mCallAudioState.getRoute(),
-                        mCallAudioState.getSupportedRouteMask());
-            }
-        }
-    }
-
-    @Override
-    public void onCallRemoved(Call call) {
-        Log.v(this, "onCallRemoved");
-        // 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);
-        }
+        mPlayerFactory.setCallAudioManager(this);
+        mCallAudioModeStateMachine.setCallAudioManager(this);
     }
 
     @Override
     public void onCallStateChanged(Call call, int oldState, int newState) {
-        Log.v(this, "onCallStateChanged : oldState = %d, newState = %d", oldState, newState);
-        onCallUpdated(call);
+        if (call.getParentCall() != null) {
+            // No audio management for calls in a conference.
+            return;
+        }
+        Log.d(LOG_TAG, "Call state changed for TC@%s: %s -> %s", call.getId(),
+                CallState.toString(oldState), CallState.toString(newState));
+
+        if (mCallStateToCalls.get(oldState) != null) {
+            mCallStateToCalls.get(oldState).remove(call);
+        }
+        if (mCallStateToCalls.get(newState) != null) {
+            mCallStateToCalls.get(newState).add(call);
+        }
+
+        updateForegroundCall();
+        if (newState == CallState.DISCONNECTED) {
+            playToneForDisconnectedCall(call);
+        }
+
+        onCallLeavingState(call, oldState);
+        onCallEnteringState(call, newState);
+    }
+
+    @Override
+    public void onCallAdded(Call call) {
+        if (call.getParentCall() != null) {
+            return; // Don't do audio handling for calls in a conference.
+        }
+
+        if (mCalls.contains(call)) {
+            Log.w(LOG_TAG, "Call TC@%s is being added twice.", call.getId());
+            return; // No guarantees that the same call won't get added twice.
+        }
+
+        Log.d(LOG_TAG, "Call added with id TC@%s in state %s", call.getId(),
+                CallState.toString(call.getState()));
+
+        if (mCallStateToCalls.get(call.getState()) != null) {
+            mCallStateToCalls.get(call.getState()).add(call);
+        }
+        updateForegroundCall();
+        mCalls.add(call);
+
+        onCallEnteringState(call, call.getState());
+    }
+
+    @Override
+    public void onCallRemoved(Call call) {
+        if (call.getParentCall() != null) {
+            return; // Don't do audio handling for calls in a conference.
+        }
+
+        if (!mCalls.contains(call)) {
+            return; // No guarantees that the same call won't get removed twice.
+        }
+
+        Log.d(LOG_TAG, "Call removed with id TC@%s in state %s", call.getId(),
+                CallState.toString(call.getState()));
+
+        if (mCallStateToCalls.get(call.getState()) != null) {
+            mCallStateToCalls.get(call.getState()).remove(call);
+        }
+
+        updateForegroundCall();
+        mCalls.remove(call);
+
+        onCallLeavingState(call, call.getState());
+
+        if (mCallsManager.getCalls().isEmpty()) {
+            Log.v(this, "all calls removed, resetting system audio to default state");
+            mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+                    CallAudioRouteStateMachine.REINITIALIZE);
+        }
     }
 
     @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;
-        }
-
-        setSystemAudioState(false /* isMute */, route, mCallAudioState.getSupportedRouteMask());
+        // This is called after the UI answers the call, but before the connection service
+        // sets the call to active. Only thing to handle for mode here is the audio speedup thing.
 
         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;
-            updateAudioStreamAndMode(call);
+            if (mForegroundCall == call) {
+                Log.i(LOG_TAG, "Invoking the MT_AUDIO_SPEEDUP mechanism. Transitioning into " +
+                        "an active in-call audio state before connection service has " +
+                        "connected the call.");
+                if (mCallStateToCalls.get(call.getState()) != null) {
+                    mCallStateToCalls.get(call.getState()).remove(call);
+                }
+                mActiveOrDialingCalls.add(call);
+                mCallAudioModeStateMachine.sendMessage(
+                        CallAudioModeStateMachine.MT_AUDIO_SPEEDUP_FOR_RINGING_CALL,
+                        makeArgsForModeStateMachine());
+            }
+        }
+
+        if (mRingingCalls.size() == 0) {
+            mRinger.stopRinging();
+            mRinger.stopCallWaiting();
         }
     }
 
     @Override
-    public void onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall) {
-        onCallUpdated(newForegroundCall);
-        // Ensure that the foreground call knows about the latest audio state.
-        updateAudioForForegroundCall();
+    public void onSessionModifyRequestReceived(Call call, VideoProfile videoProfile) {
+        if (videoProfile == null) {
+            return;
+        }
+
+        if (call != mForegroundCall) {
+            // We only play tones for foreground calls.
+            return;
+        }
+
+        int previousVideoState = call.getVideoState();
+        int newVideoState = videoProfile.getVideoState();
+        Log.v(this, "onSessionModifyRequestReceived : videoProfile = " + VideoProfile
+                .videoStateToString(newVideoState));
+
+        boolean isUpgradeRequest = !VideoProfile.isReceptionEnabled(previousVideoState) &&
+                VideoProfile.isReceptionEnabled(newVideoState);
+
+        if (isUpgradeRequest) {
+            mPlayerFactory.createPlayer(InCallTonePlayer.TONE_VIDEO_UPGRADE).startTone();
+        }
     }
 
     @Override
     public void onIsVoipAudioModeChanged(Call call) {
-        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()) {
+        if (call != mForegroundCall) {
             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());
+        mCallAudioModeStateMachine.sendMessage(
+                CallAudioModeStateMachine.FOREGROUND_VOIP_MODE_CHANGE,
+                makeArgsForModeStateMachine());
     }
 
     @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);
-            }
+    public void onRingbackRequested(Call call, boolean shouldRingback) {
+        if (call == mForegroundCall && shouldRingback) {
+            mRingbackPlayer.startRingbackForCall(call);
         } else {
-            // Device just undocked, remove from speakerphone if possible.
-            if (mCallAudioState.getRoute() == CallAudioState.ROUTE_SPEAKER) {
-                setAudioRoute(CallAudioState.ROUTE_WIRED_OR_EARPIECE);
-            }
+            mRingbackPlayer.stopRingbackForCall(call);
         }
     }
 
+    @Override
+    public void onIncomingCallRejected(Call call, boolean rejectWithMessage, String message) {
+        // This gets called after the UI rejects a call but before the CS processes the rejection.
+        // Will get called before the state change from ringing to not ringing.
+
+        if (mRingingCalls.size() == 0 || call == mRingingCalls.iterator().next()) {
+            mRinger.stopRinging();
+            mRinger.stopCallWaiting();
+        }
+    }
+
+    @Override
+    public void onIsConferencedChanged(Call call) {
+        // This indicates a conferencing change, which shouldn't impact any audio mode stuff.
+        Call parentCall = call.getParentCall();
+        if (parentCall == null) {
+            // Indicates that the call should be tracked for audio purposes. Treat it as if it were
+            // just added.
+            Log.i(LOG_TAG, "Call TC@" + call.getId() + " left conference and will" +
+                            " now be tracked by CallAudioManager.");
+            onCallAdded(call);
+        } else {
+            // The call joined a conference, so stop tracking it.
+            if (mCallStateToCalls.get(call.getState()) != null) {
+                mCallStateToCalls.get(call.getState()).remove(call);
+            }
+
+            updateForegroundCall();
+            mCalls.remove(call);
+        }
+    }
+
+    public CallAudioState getCallAudioState() {
+        return mCallAudioRouteStateMachine.getCurrentCallAudioState();
+    }
+
+    public Call getForegroundCall() {
+        if (mForegroundCall != null && mForegroundCall.getState() != CallState.ON_HOLD) {
+            return mForegroundCall;
+        }
+        return null;
+    }
+
     void toggleMute() {
-        mute(!mCallAudioState.isMuted());
+        mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+                CallAudioRouteStateMachine.TOGGLE_MUTE);
     }
 
     void mute(boolean shouldMute) {
-        if (!hasFocus()) {
-            return;
-        }
-
         Log.v(this, "mute, shouldMute: %b", shouldMute);
 
         // Don't mute if there are any emergency calls.
@@ -344,15 +289,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,468 +299,264 @@
      * @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());
-        }
-    }
-
-    /**
-     * Sets the audio stream and mode based on whether a call is ringing.
-     *
-     * @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) {
-        if (mIsRinging != isRinging) {
-            Log.i(this, "setIsRinging %b -> %b (call = %s)", mIsRinging, isRinging, call);
-            mIsRinging = isRinging;
-            updateAudioStreamAndMode(call);
-        }
-    }
-
-    /**
-     * Sets the tone playing status. Some tones can play even when there are no live calls and this
-     * status indicates that we should keep audio focus even for tones that play beyond the life of
-     * calls.
-     *
-     * @param isPlayingNew The status to set.
-     */
-    void setIsTonePlaying(boolean isPlayingNew) {
-        if (mIsTonePlaying != isPlayingNew) {
-            Log.v(this, "mIsTonePlaying %b -> %b.", mIsTonePlaying, isPlayingNew);
-            mIsTonePlaying = isPlayingNew;
-            updateAudioStreamAndMode();
-        }
-    }
-
-    /**
-     * 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 &&
-                            call == mCallToSpeedUpMTAudio) {
-            mCallToSpeedUpMTAudio = null;
-        }
-    }
-
-    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 */);
-    }
-
-    private void updateAudioStreamAndMode(Call callToUpdate) {
-        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);
-        } else {
-            Call foregroundCall = getForegroundCall();
-            Call waitingForAccountSelectionCall = mCallsManager
-                    .getFirstCallWithState(CallState.SELECT_PHONE_ACCOUNT);
-            Call call = mCallsManager.getForegroundCall();
-            if (foregroundCall == null && call != null && call == mCallToSpeedUpMTAudio) {
-                Log.v(this, "updateAudioStreamAndMode : no foreground, speeding up MT audio.");
-                requestAudioFocusAndSetMode(AudioManager.STREAM_VOICE_CALL,
-                                                         AudioManager.MODE_IN_CALL);
-            } else if (foregroundCall != null && !foregroundCall.isDisconnected() &&
-                    waitingForAccountSelectionCall == null) {
-                // In the case where there is a call that is waiting for account selection,
-                // this will fall back to abandonAudioFocus() below, which temporarily exits
-                // the in-call audio mode. This is to allow TalkBack to speak the "Call with"
-                // dialog information at media volume as opposed to through the earpiece.
-                // Once exiting the "Call with" dialog, the audio focus will return to an in-call
-                // audio mode when this method (updateAudioStreamAndMode) is called again.
-                int mode = foregroundCall.getIsVoipAudioMode() ?
-                        AudioManager.MODE_IN_COMMUNICATION : AudioManager.MODE_IN_CALL;
-                Log.v(this, "updateAudioStreamAndMode : foreground");
-                requestAudioFocusAndSetMode(AudioManager.STREAM_VOICE_CALL, mode);
-            } else if (mIsTonePlaying) {
-                // There is no call, however, we are still playing a tone, so keep focus.
-                // Since there is no call from which to determine the mode, use the most
-                // recently used mode instead.
-                Log.v(this, "updateAudioStreamAndMode : tone playing");
-                requestAudioFocusAndSetMode(
-                        AudioManager.STREAM_VOICE_CALL, mMostRecentlyUsedMode);
-            } else if (!hasRingingForegroundCall() && mCallsManager.hasOnlyDisconnectedCalls()) {
-                Log.v(this, "updateAudioStreamAndMode : no ringing call");
-                abandonAudioFocus();
-            } else {
-                // mIsRinging is false, but there is a foreground ringing call present. Don't
-                // abandon audio focus immediately to prevent audio focus from getting lost between
-                // the time it takes for the foreground call to transition from RINGING to ACTIVE/
-                // DISCONNECTED. When the call eventually transitions to the next state, audio
-                // 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) {
-        Log.v(this, "requestAudioFocusAndSetMode : stream: %s -> %s, mode: %s",
-                streamTypeToString(mAudioFocusStreamType), streamTypeToString(stream),
-                modeToString(mode));
-        Preconditions.checkState(stream != STREAM_NONE);
-
-        // Even if we already have focus, if the stream is different we update audio manager to give
-        // it a hint about the purpose of our focus.
-        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();
-        }
-        mAudioFocusStreamType = stream;
-
-        setMode(mode);
-    }
-
-    private void abandonAudioFocus() {
-        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();
-            mAudioFocusStreamType = STREAM_NONE;
-            mCallToSpeedUpMTAudio = null;
-        }
-    }
-
-    /**
-     * Sets the audio mode.
-     *
-     * @param newMode Mode constant from AudioManager.MODE_*.
-     */
-    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());
-    }
-
-    private void updateAudioForForegroundCall() {
-        Call call = mCallsManager.getForegroundCall();
-        if (call != null && call.getConnectionService() != null) {
-            call.getConnectionService().onCallAudioStateChanged(call, mCallAudioState);
-        }
-    }
-
-    /**
-     * Returns the current foreground call in order to properly set the audio mode.
-     */
-    private Call getForegroundCall() {
-        Call call = mCallsManager.getForegroundCall();
-
-        // We ignore any foreground call that is in the ringing state because we deal with ringing
-        // calls exclusively through the mIsRinging variable set by {@link Ringer}.
-        if (call != null && call.getState() == CallState.RINGING) {
-            return null;
-        }
-
-        return call;
-    }
-
-    private boolean hasRingingForegroundCall() {
-        Call call = mCallsManager.getForegroundCall();
-        return call != null && call.getState() == CallState.RINGING;
-    }
-
-    private boolean hasFocus() {
-        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.
-     *
-     * @param streamType The stream type.
-     * @return Human readable description.
-     */
-    private String streamTypeToString(int streamType) {
-        switch (streamType) {
-            case STREAM_NONE:
-                return STREAM_DESCRIPTION_NONE;
-            case AudioManager.STREAM_ALARM:
-                return STREAM_DESCRIPTION_ALARM;
-            case AudioManager.STREAM_BLUETOOTH_SCO:
-                return STREAM_DESCRIPTION_BLUETOOTH_SCO;
-            case AudioManager.STREAM_DTMF:
-                return STREAM_DESCRIPTION_DTMF;
-            case AudioManager.STREAM_MUSIC:
-                return STREAM_DESCRIPTION_MUSIC;
-            case AudioManager.STREAM_NOTIFICATION:
-                return STREAM_DESCRIPTION_NOTIFICATION;
-            case AudioManager.STREAM_RING:
-                return STREAM_DESCRIPTION_RING;
-            case AudioManager.STREAM_SYSTEM:
-                return STREAM_DESCRIPTION_SYSTEM;
-            case AudioManager.STREAM_VOICE_CALL:
-                return STREAM_DESCRIPTION_VOICE_CALL;
+        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_BASELINE_ROUTE);
+                return;
             default:
-                return "STEAM_OTHER_" + streamType;
+                Log.wtf(this, "Invalid route specified: %d", route);
         }
     }
 
-    /**
-     * Translates an {@link AudioManager} mode into a human readable string.
-     *
-     * @param mode The mode.
-     * @return The string.
-     */
-    private String modeToString(int mode) {
-        switch (mode) {
-            case AudioManager.MODE_INVALID:
-                return MODE_DESCRIPTION_INVALID;
-            case AudioManager.MODE_CURRENT:
-                return MODE_DESCRIPTION_CURRENT;
-            case AudioManager.MODE_NORMAL:
-                return MODE_DESCRIPTION_NORMAL;
-            case AudioManager.MODE_RINGTONE:
-                return MODE_DESCRIPTION_RINGTONE;
-            case AudioManager.MODE_IN_CALL:
-                return MODE_DESCRIPTION_IN_CALL;
-            case AudioManager.MODE_IN_COMMUNICATION:
-                return MODE_DESCRIPTION_IN_COMMUNICATION;
-            default:
-                return "MODE_OTHER_" + mode;
+    void silenceRingers() {
+        for (Call call : mRingingCalls) {
+            call.silence();
         }
+
+        mRingingCalls.clear();
+        mRinger.stopRinging();
+        mRinger.stopCallWaiting();
+        mCallAudioModeStateMachine.sendMessage(CallAudioModeStateMachine.NO_MORE_RINGING_CALLS,
+                makeArgsForModeStateMachine());
     }
 
-    /**
-     * Dumps the state of the {@link CallAudioManager}.
-     *
-     * @param pw The {@code IndentingPrintWriter} to write the state to.
-     */
-    public void dump(IndentingPrintWriter pw) {
-        pw.println("mAudioState: " + mCallAudioState);
-        pw.println("mBluetoothManager:");
+    @VisibleForTesting
+    public void startRinging() {
+        mRinger.startRinging(mForegroundCall);
+    }
+
+    @VisibleForTesting
+    public void startCallWaiting() {
+        mRinger.startCallWaiting(mRingingCalls.iterator().next());
+    }
+
+    @VisibleForTesting
+    public void stopRinging() {
+        mRinger.stopRinging();
+    }
+
+    @VisibleForTesting
+    public void stopCallWaiting() {
+        mRinger.stopCallWaiting();
+    }
+
+    @VisibleForTesting
+    public void setCallAudioRouteFocusState(int focusState) {
+        mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+                CallAudioRouteStateMachine.SWITCH_FOCUS, focusState);
+    }
+
+    void dump(IndentingPrintWriter pw) {
+        pw.println("Active or dialing calls:");
         pw.increaseIndent();
-        mBluetoothManager.dump(pw);
+        dumpCallsInCollection(pw, mActiveOrDialingCalls);
         pw.decreaseIndent();
-        if (mWiredHeadsetManager != null) {
-            pw.println("mWiredHeadsetManager:");
-            pw.increaseIndent();
-            mWiredHeadsetManager.dump(pw);
-            pw.decreaseIndent();
-        } else {
-            pw.println("mWiredHeadsetManager: null");
-        }
-        pw.println("mAudioFocusStreamType: " + streamTypeToString(mAudioFocusStreamType));
-        pw.println("mIsRinging: " + mIsRinging);
-        pw.println("mIsTonePlaying: " + mIsTonePlaying);
-        pw.println("mWasSpeakerOn: " + mWasSpeakerOn);
-        pw.println("mMostRecentlyUsedMode: " + modeToString(mMostRecentlyUsedMode));
+
+        pw.println("Ringing calls:");
+        pw.increaseIndent();
+        dumpCallsInCollection(pw, mRingingCalls);
+        pw.decreaseIndent();
+
+        pw.println("Holding calls:");
+        pw.increaseIndent();
+        dumpCallsInCollection(pw, mHoldingCalls);
+        pw.decreaseIndent();
+
+        pw.println("Foreground call:");
+        pw.println(mForegroundCall);
     }
-}
+
+    @VisibleForTesting
+    public void setIsTonePlaying(boolean isTonePlaying) {
+        mIsTonePlaying = isTonePlaying;
+        mCallAudioModeStateMachine.sendMessage(
+                isTonePlaying ? CallAudioModeStateMachine.TONE_STARTED_PLAYING
+                        : CallAudioModeStateMachine.TONE_STOPPED_PLAYING,
+                makeArgsForModeStateMachine());
+    }
+
+    private void onCallLeavingState(Call call, int state) {
+        switch (state) {
+            case CallState.ACTIVE:
+                onCallLeavingActiveOrDialing();
+                break;
+            case CallState.RINGING:
+                onCallLeavingRinging();
+                break;
+            case CallState.ON_HOLD:
+                onCallLeavingHold();
+                break;
+            case CallState.DIALING:
+                stopRingbackForCall(call);
+                onCallLeavingActiveOrDialing();
+        }
+    }
+
+    private void onCallEnteringState(Call call, int state) {
+        switch (state) {
+            case CallState.ACTIVE:
+                onCallEnteringActiveOrDialing();
+                break;
+            case CallState.RINGING:
+                onCallEnteringRinging();
+                break;
+            case CallState.ON_HOLD:
+                onCallEnteringHold();
+                break;
+            case CallState.DIALING:
+                onCallEnteringActiveOrDialing();
+                playRingbackForCall(call);
+                break;
+        }
+    }
+
+    private void onCallLeavingActiveOrDialing() {
+        if (mActiveOrDialingCalls.size() == 0) {
+            mCallAudioModeStateMachine.sendMessage(
+                    CallAudioModeStateMachine.NO_MORE_ACTIVE_OR_DIALING_CALLS,
+                    makeArgsForModeStateMachine());
+        }
+    }
+
+    private void onCallLeavingRinging() {
+        if (mRingingCalls.size() == 0) {
+            mCallAudioModeStateMachine.sendMessage(CallAudioModeStateMachine.NO_MORE_RINGING_CALLS,
+                    makeArgsForModeStateMachine());
+        }
+    }
+
+    private void onCallLeavingHold() {
+        if (mHoldingCalls.size() == 0) {
+            mCallAudioModeStateMachine.sendMessage(CallAudioModeStateMachine.NO_MORE_HOLDING_CALLS,
+                    makeArgsForModeStateMachine());
+        }
+    }
+
+    private void onCallEnteringActiveOrDialing() {
+        if (mActiveOrDialingCalls.size() == 1) {
+            mCallAudioModeStateMachine.sendMessage(
+                    CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL,
+                    makeArgsForModeStateMachine());
+        }
+    }
+
+    private void onCallEnteringRinging() {
+        if (mRingingCalls.size() == 1) {
+            mCallAudioModeStateMachine.sendMessage(CallAudioModeStateMachine.NEW_RINGING_CALL,
+                    makeArgsForModeStateMachine());
+        }
+    }
+
+    private void onCallEnteringHold() {
+        if (mHoldingCalls.size() == 1) {
+            mCallAudioModeStateMachine.sendMessage(CallAudioModeStateMachine.NEW_HOLDING_CALL,
+                    makeArgsForModeStateMachine());
+        }
+    }
+
+    private void updateForegroundCall() {
+        Call oldForegroundCall = mForegroundCall;
+        if (mActiveOrDialingCalls.size() > 0) {
+            mForegroundCall = mActiveOrDialingCalls.iterator().next();
+        } else if (mRingingCalls.size() > 0) {
+            mForegroundCall = mRingingCalls.iterator().next();
+        } else if (mHoldingCalls.size() > 0) {
+            mForegroundCall = mHoldingCalls.iterator().next();
+        } else {
+            mForegroundCall = null;
+        }
+
+        if (mForegroundCall != oldForegroundCall) {
+            mDtmfLocalTonePlayer.onForegroundCallChanged(oldForegroundCall, mForegroundCall);
+        }
+    }
+
+    @NonNull
+    private CallAudioModeStateMachine.MessageArgs makeArgsForModeStateMachine() {
+        return new CallAudioModeStateMachine.MessageArgs(
+                mActiveOrDialingCalls.size() > 0,
+                mRingingCalls.size() > 0,
+                mHoldingCalls.size() > 0,
+                mIsTonePlaying,
+                mForegroundCall != null && mForegroundCall.getIsVoipAudioMode(),
+                Log.createSubsession());
+    }
+
+    private void playToneForDisconnectedCall(Call call) {
+        if (mForegroundCall != null && call != mForegroundCall && mCalls.size() > 1) {
+            Log.v(LOG_TAG, "Omitting tone because we are not foreground" +
+                    " and there is another call.");
+            return;
+        }
+
+        if (call.getDisconnectCause() != null) {
+            int toneToPlay = InCallTonePlayer.TONE_INVALID;
+
+            Log.v(this, "Disconnect cause: %s.", call.getDisconnectCause());
+
+            switch(call.getDisconnectCause().getTone()) {
+                case ToneGenerator.TONE_SUP_BUSY:
+                    toneToPlay = InCallTonePlayer.TONE_BUSY;
+                    break;
+                case ToneGenerator.TONE_SUP_CONGESTION:
+                    toneToPlay = InCallTonePlayer.TONE_CONGESTION;
+                    break;
+                case ToneGenerator.TONE_CDMA_REORDER:
+                    toneToPlay = InCallTonePlayer.TONE_REORDER;
+                    break;
+                case ToneGenerator.TONE_CDMA_ABBR_INTERCEPT:
+                    toneToPlay = InCallTonePlayer.TONE_INTERCEPT;
+                    break;
+                case ToneGenerator.TONE_CDMA_CALLDROP_LITE:
+                    toneToPlay = InCallTonePlayer.TONE_CDMA_DROP;
+                    break;
+                case ToneGenerator.TONE_SUP_ERROR:
+                    toneToPlay = InCallTonePlayer.TONE_UNOBTAINABLE_NUMBER;
+                    break;
+                case ToneGenerator.TONE_PROP_PROMPT:
+                    toneToPlay = InCallTonePlayer.TONE_CALL_ENDED;
+                    break;
+            }
+
+            Log.d(this, "Found a disconnected call with tone to play %d.", toneToPlay);
+
+            if (toneToPlay != InCallTonePlayer.TONE_INVALID) {
+                mPlayerFactory.createPlayer(toneToPlay).startTone();
+            }
+        }
+    }
+
+    private void playRingbackForCall(Call call) {
+        if (call == mForegroundCall && call.isRingbackRequested()) {
+            mRingbackPlayer.startRingbackForCall(call);
+        }
+    }
+
+    private void stopRingbackForCall(Call call) {
+        mRingbackPlayer.stopRingbackForCall(call);
+    }
+
+    private void dumpCallsInCollection(IndentingPrintWriter pw, Collection<Call> calls) {
+        for (Call call : calls) {
+            if (call != null) pw.println(call.getId());
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/server/telecom/CallAudioModeStateMachine.java b/src/com/android/server/telecom/CallAudioModeStateMachine.java
new file mode 100644
index 0000000..8a1aef8
--- /dev/null
+++ b/src/com/android/server/telecom/CallAudioModeStateMachine.java
@@ -0,0 +1,514 @@
+/*
+ * 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.media.AudioManager;
+import android.os.Message;
+import android.util.SparseArray;
+
+import com.android.internal.util.IState;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+
+public class CallAudioModeStateMachine extends StateMachine {
+    public static class MessageArgs {
+        public boolean hasActiveOrDialingCalls;
+        public boolean hasRingingCalls;
+        public boolean hasHoldingCalls;
+        public boolean isTonePlaying;
+        public boolean foregroundCallIsVoip;
+        public Session session;
+
+        public MessageArgs(boolean hasActiveOrDialingCalls, boolean hasRingingCalls,
+                boolean hasHoldingCalls, boolean isTonePlaying, boolean foregroundCallIsVoip,
+                Session session) {
+            this.hasActiveOrDialingCalls = hasActiveOrDialingCalls;
+            this.hasRingingCalls = hasRingingCalls;
+            this.hasHoldingCalls = hasHoldingCalls;
+            this.isTonePlaying = isTonePlaying;
+            this.foregroundCallIsVoip = foregroundCallIsVoip;
+            this.session = session;
+        }
+
+        @Override
+        public String toString() {
+            return "MessageArgs{" +
+                    "hasActiveCalls=" + hasActiveOrDialingCalls +
+                    ", hasRingingCalls=" + hasRingingCalls +
+                    ", hasHoldingCalls=" + hasHoldingCalls +
+                    ", isTonePlaying=" + isTonePlaying +
+                    ", foregroundCallIsVoip=" + foregroundCallIsVoip +
+                    ", session=" + session +
+                    '}';
+        }
+    }
+
+    public static final int INITIALIZE = 1;
+    // These ENTER_*_FOCUS commands are for testing.
+    public static final int ENTER_CALL_FOCUS_FOR_TESTING = 2;
+    public static final int ENTER_COMMS_FOCUS_FOR_TESTING = 3;
+    public static final int ENTER_RING_FOCUS_FOR_TESTING = 4;
+    public static final int ENTER_TONE_OR_HOLD_FOCUS_FOR_TESTING = 5;
+    public static final int ABANDON_FOCUS_FOR_TESTING = 6;
+
+    public static final int NO_MORE_ACTIVE_OR_DIALING_CALLS = 1001;
+    public static final int NO_MORE_RINGING_CALLS = 1002;
+    public static final int NO_MORE_HOLDING_CALLS = 1003;
+
+    public static final int NEW_ACTIVE_OR_DIALING_CALL = 2001;
+    public static final int NEW_RINGING_CALL = 2002;
+    public static final int NEW_HOLDING_CALL = 2003;
+    public static final int MT_AUDIO_SPEEDUP_FOR_RINGING_CALL = 2004;
+
+    public static final int TONE_STARTED_PLAYING = 3001;
+    public static final int TONE_STOPPED_PLAYING = 3002;
+
+    public static final int FOREGROUND_VOIP_MODE_CHANGE = 4001;
+
+    public static final int RUN_RUNNABLE = 9001;
+
+    private static final SparseArray<String> MESSAGE_CODE_TO_NAME = new SparseArray<String>() {{
+        put(ENTER_CALL_FOCUS_FOR_TESTING, "ENTER_CALL_FOCUS_FOR_TESTING");
+        put(ENTER_COMMS_FOCUS_FOR_TESTING, "ENTER_COMMS_FOCUS_FOR_TESTING");
+        put(ENTER_RING_FOCUS_FOR_TESTING, "ENTER_RING_FOCUS_FOR_TESTING");
+        put(ENTER_TONE_OR_HOLD_FOCUS_FOR_TESTING, "ENTER_TONE_OR_HOLD_FOCUS_FOR_TESTING");
+        put(ABANDON_FOCUS_FOR_TESTING, "ABANDON_FOCUS_FOR_TESTING");
+        put(NO_MORE_ACTIVE_OR_DIALING_CALLS, "NO_MORE_ACTIVE_OR_DIALING_CALLS");
+        put(NO_MORE_RINGING_CALLS, "NO_MORE_RINGING_CALLS");
+        put(NO_MORE_HOLDING_CALLS, "NO_MORE_HOLDING_CALLS");
+        put(NEW_ACTIVE_OR_DIALING_CALL, "NEW_ACTIVE_OR_DIALING_CALL");
+        put(NEW_RINGING_CALL, "NEW_RINGING_CALL");
+        put(NEW_HOLDING_CALL, "NEW_HOLDING_CALL");
+        put(MT_AUDIO_SPEEDUP_FOR_RINGING_CALL, "MT_AUDIO_SPEEDUP_FOR_RINGING_CALL");
+        put(TONE_STARTED_PLAYING, "TONE_STARTED_PLAYING");
+        put(TONE_STOPPED_PLAYING, "TONE_STOPPED_PLAYING");
+        put(FOREGROUND_VOIP_MODE_CHANGE, "FOREGROUND_VOIP_MODE_CHANGE");
+
+        put(RUN_RUNNABLE, "RUN_RUNNABLE");
+    }};
+
+    public static final String TONE_HOLD_STATE_NAME = OtherFocusState.class.getSimpleName();
+    public static final String UNFOCUSED_STATE_NAME = UnfocusedState.class.getSimpleName();
+    public static final String CALL_STATE_NAME = SimCallFocusState.class.getSimpleName();
+    public static final String RING_STATE_NAME = RingingFocusState.class.getSimpleName();
+    public static final String COMMS_STATE_NAME = VoipCallFocusState.class.getSimpleName();
+
+    private class BaseState extends State {
+        @Override
+        public boolean processMessage(Message msg) {
+            switch (msg.what) {
+                case ENTER_CALL_FOCUS_FOR_TESTING:
+                    transitionTo(mSimCallFocusState);
+                    return HANDLED;
+                case ENTER_COMMS_FOCUS_FOR_TESTING:
+                    transitionTo(mVoipCallFocusState);
+                    return HANDLED;
+                case ENTER_RING_FOCUS_FOR_TESTING:
+                    transitionTo(mRingingFocusState);
+                    return HANDLED;
+                case ENTER_TONE_OR_HOLD_FOCUS_FOR_TESTING:
+                    transitionTo(mOtherFocusState);
+                    return HANDLED;
+                case ABANDON_FOCUS_FOR_TESTING:
+                    transitionTo(mUnfocusedState);
+                    return HANDLED;
+                case INITIALIZE:
+                    mIsInitialized = true;
+                    return HANDLED;
+                case RUN_RUNNABLE:
+                    java.lang.Runnable r = (java.lang.Runnable) msg.obj;
+                    r.run();
+                    return HANDLED;
+                default:
+                    return NOT_HANDLED;
+            }
+        }
+    }
+
+    private class UnfocusedState extends BaseState {
+        @Override
+        public void enter() {
+            if (mIsInitialized) {
+                Log.i(LOG_TAG, "Abandoning audio focus: now UNFOCUSED");
+                mAudioManager.abandonAudioFocusForCall();
+                mAudioManager.setMode(AudioManager.MODE_NORMAL);
+
+                mMostRecentMode = AudioManager.MODE_NORMAL;
+                mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.NO_FOCUS);
+            }
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            if (super.processMessage(msg) == HANDLED) {
+                return HANDLED;
+            }
+            MessageArgs args = (MessageArgs) msg.obj;
+            switch (msg.what) {
+                case NO_MORE_ACTIVE_OR_DIALING_CALLS:
+                    // Do nothing.
+                    return HANDLED;
+                case NO_MORE_RINGING_CALLS:
+                    // Do nothing.
+                    return HANDLED;
+                case NO_MORE_HOLDING_CALLS:
+                    // Do nothing.
+                    return HANDLED;
+                case NEW_ACTIVE_OR_DIALING_CALL:
+                    transitionTo(args.foregroundCallIsVoip
+                            ? mVoipCallFocusState : mSimCallFocusState);
+                    return HANDLED;
+                case NEW_RINGING_CALL:
+                    transitionTo(mRingingFocusState);
+                    return HANDLED;
+                case NEW_HOLDING_CALL:
+                    // This really shouldn't happen, but transition to the focused state anyway.
+                    Log.w(LOG_TAG, "Call was surprisingly put into hold from an unknown state." +
+                            " Args are: \n" + args.toString());
+                    transitionTo(mOtherFocusState);
+                    return HANDLED;
+                case TONE_STARTED_PLAYING:
+                    // This shouldn't happen either, but perform the action anyway.
+                    Log.w(LOG_TAG, "Tone started playing unexpectedly. Args are: \n"
+                            + args.toString());
+                    return HANDLED;
+                default:
+                    // The forced focus switch commands are handled by BaseState.
+                    return NOT_HANDLED;
+            }
+        }
+    }
+
+    private class RingingFocusState extends BaseState {
+        @Override
+        public void enter() {
+            Log.i(LOG_TAG, "Audio focus entering RINGING state");
+            mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_RING,
+                    AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
+            if (mMostRecentMode == AudioManager.MODE_IN_CALL) {
+                // Preserving behavior from the old CallAudioManager.
+                Log.i(LOG_TAG, "Transition from IN_CALL -> RINGTONE."
+                        + "  Resetting to NORMAL first.");
+                mAudioManager.setMode(AudioManager.MODE_NORMAL);
+            }
+            mAudioManager.setMode(AudioManager.MODE_RINGTONE);
+
+            mCallAudioManager.stopCallWaiting();
+            mCallAudioManager.startRinging();
+            mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.HAS_FOCUS);
+        }
+
+        @Override
+        public void exit() {
+            // Audio mode and audio stream will be set by the next state.
+            mCallAudioManager.stopRinging();
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            if (super.processMessage(msg) == HANDLED) {
+                return HANDLED;
+            }
+            MessageArgs args = (MessageArgs) msg.obj;
+            switch (msg.what) {
+                case NO_MORE_ACTIVE_OR_DIALING_CALLS:
+                    // Do nothing. Loss of an active call should not impact ringer.
+                    return HANDLED;
+                case NO_MORE_HOLDING_CALLS:
+                    // Do nothing and keep ringing.
+                    return HANDLED;
+                case NO_MORE_RINGING_CALLS:
+                    // If there are active or holding calls, switch to the appropriate focus.
+                    // Otherwise abandon focus.
+                    if (args.hasActiveOrDialingCalls) {
+                        if (args.foregroundCallIsVoip) {
+                            transitionTo(mVoipCallFocusState);
+                        } else {
+                            transitionTo(mSimCallFocusState);
+                        }
+                    } else if (args.hasHoldingCalls || args.isTonePlaying) {
+                        transitionTo(mOtherFocusState);
+                    } else {
+                        transitionTo(mUnfocusedState);
+                    }
+                    return HANDLED;
+                case NEW_ACTIVE_OR_DIALING_CALL:
+                    // If a call becomes active suddenly, give it priority over ringing.
+                    transitionTo(args.foregroundCallIsVoip
+                            ? mVoipCallFocusState : mSimCallFocusState);
+                    return HANDLED;
+                case NEW_RINGING_CALL:
+                    Log.w(LOG_TAG, "Unexpected behavior! New ringing call appeared while in " +
+                            "ringing state.");
+                    return HANDLED;
+                case NEW_HOLDING_CALL:
+                    // This really shouldn't happen, but transition to the focused state anyway.
+                    Log.w(LOG_TAG, "Call was surprisingly put into hold while ringing." +
+                            " Args are: " + args.toString());
+                    transitionTo(mOtherFocusState);
+                    return HANDLED;
+                case MT_AUDIO_SPEEDUP_FOR_RINGING_CALL:
+                    // This happens when an IMS call is answered by the in-call UI. Special case
+                    // that we have to deal with for some reason.
+
+                    // VOIP calls should never invoke this mechanism, so transition directly to
+                    // the sim call focus state.
+                    transitionTo(mSimCallFocusState);
+                    return HANDLED;
+                default:
+                    // The forced focus switch commands are handled by BaseState.
+                    return NOT_HANDLED;
+            }
+        }
+    }
+
+    private class SimCallFocusState extends BaseState {
+        @Override
+        public void enter() {
+            Log.i(LOG_TAG, "Audio focus entering SIM CALL state");
+            mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_VOICE_CALL,
+                    AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
+            mAudioManager.setMode(AudioManager.MODE_IN_CALL);
+            mMostRecentMode = AudioManager.MODE_IN_CALL;
+            mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.HAS_FOCUS);
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            if (super.processMessage(msg) == HANDLED) {
+                return HANDLED;
+            }
+            MessageArgs args = (MessageArgs) msg.obj;
+            switch (msg.what) {
+                case NO_MORE_ACTIVE_OR_DIALING_CALLS:
+                    // Switch to either ringing, holding, or inactive
+                    transitionTo(destinationStateAfterNoMoreActiveCalls(args));
+                    return HANDLED;
+                case NO_MORE_RINGING_CALLS:
+                    // Don't transition state, but stop any call-waiting tones that may have been
+                    // playing.
+                    if (args.isTonePlaying) {
+                        mCallAudioManager.stopCallWaiting();
+                    }
+                    return HANDLED;
+                case NO_MORE_HOLDING_CALLS:
+                    // Do nothing.
+                    return HANDLED;
+                case NEW_ACTIVE_OR_DIALING_CALL:
+                    // Do nothing. Already active.
+                    return HANDLED;
+                case NEW_RINGING_CALL:
+                    // Don't make a call ring over an active call, but do play a call waiting tone.
+                    mCallAudioManager.startCallWaiting();
+                    return HANDLED;
+                case NEW_HOLDING_CALL:
+                    // Don't do anything now. Putting an active call on hold will be handled when
+                    // NO_MORE_ACTIVE_CALLS is processed.
+                    return HANDLED;
+                case FOREGROUND_VOIP_MODE_CHANGE:
+                    if (args.foregroundCallIsVoip) {
+                        transitionTo(mVoipCallFocusState);
+                    }
+                    return HANDLED;
+                default:
+                    // The forced focus switch commands are handled by BaseState.
+                    return NOT_HANDLED;
+            }
+        }
+    }
+
+    private class VoipCallFocusState extends BaseState {
+        @Override
+        public void enter() {
+            Log.i(LOG_TAG, "Audio focus entering VOIP CALL state");
+            mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_VOICE_CALL,
+                    AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
+            mAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
+            mMostRecentMode = AudioManager.MODE_IN_COMMUNICATION;
+            mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.HAS_FOCUS);
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            if (super.processMessage(msg) == HANDLED) {
+                return HANDLED;
+            }
+            MessageArgs args = (MessageArgs) msg.obj;
+            switch (msg.what) {
+                case NO_MORE_ACTIVE_OR_DIALING_CALLS:
+                    // Switch to either ringing, holding, or inactive
+                    transitionTo(destinationStateAfterNoMoreActiveCalls(args));
+                    return HANDLED;
+                case NO_MORE_RINGING_CALLS:
+                    // Don't transition state, but stop any call-waiting tones that may have been
+                    // playing.
+                    if (args.isTonePlaying) {
+                        mCallAudioManager.stopCallWaiting();
+                    }
+                    return HANDLED;
+                case NO_MORE_HOLDING_CALLS:
+                    // Do nothing.
+                    return HANDLED;
+                case NEW_ACTIVE_OR_DIALING_CALL:
+                    // Do nothing. Already active.
+                    return HANDLED;
+                case NEW_RINGING_CALL:
+                    // Don't make a call ring over an active call, but do play a call waiting tone.
+                    mCallAudioManager.startCallWaiting();
+                    return HANDLED;
+                case NEW_HOLDING_CALL:
+                    // Don't do anything now. Putting an active call on hold will be handled when
+                    // NO_MORE_ACTIVE_CALLS is processed.
+                    return HANDLED;
+                case FOREGROUND_VOIP_MODE_CHANGE:
+                    if (!args.foregroundCallIsVoip) {
+                        transitionTo(mSimCallFocusState);
+                    }
+                    return HANDLED;
+                default:
+                    // The forced focus switch commands are handled by BaseState.
+                    return NOT_HANDLED;
+            }
+        }
+    }
+
+    /**
+     * This class is used for calls on hold and end-of-call tones.
+     */
+    private class OtherFocusState extends BaseState {
+        @Override
+        public void enter() {
+            Log.i(LOG_TAG, "Audio focus entering TONE/HOLDING state");
+            mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_VOICE_CALL,
+                    AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
+            mAudioManager.setMode(mMostRecentMode);
+            mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.HAS_FOCUS);
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            if (super.processMessage(msg) == HANDLED) {
+                return HANDLED;
+            }
+            MessageArgs args = (MessageArgs) msg.obj;
+            switch (msg.what) {
+                case NO_MORE_HOLDING_CALLS:
+                    if (args.hasActiveOrDialingCalls) {
+                        transitionTo(args.foregroundCallIsVoip
+                                ? mVoipCallFocusState : mSimCallFocusState);
+                    } else if (args.hasRingingCalls) {
+                        transitionTo(mRingingFocusState);
+                    } else if (!args.isTonePlaying) {
+                        transitionTo(mUnfocusedState);
+                    }
+                    // Do nothing if a tone is playing.
+                    return HANDLED;
+                case NEW_ACTIVE_OR_DIALING_CALL:
+                    transitionTo(args.foregroundCallIsVoip
+                            ? mVoipCallFocusState : mSimCallFocusState);
+                    return HANDLED;
+                case NEW_RINGING_CALL:
+                    // Apparently this is current behavior. Should this be the case?
+                    transitionTo(mRingingFocusState);
+                    return HANDLED;
+                case NEW_HOLDING_CALL:
+                    // Do nothing.
+                    return HANDLED;
+                case NO_MORE_RINGING_CALLS:
+                    // If there are no more ringing calls in this state, then stop any call-waiting
+                    // tones that may be playing.
+                    mCallAudioManager.stopCallWaiting();
+                    return HANDLED;
+                case TONE_STOPPED_PLAYING:
+                    if (!args.hasActiveOrDialingCalls && !args.hasRingingCalls
+                            && !args.hasHoldingCalls && !args.isTonePlaying) {
+                        transitionTo(mUnfocusedState);
+                    }
+                default:
+                    return NOT_HANDLED;
+            }
+        }
+    }
+
+    private static final String LOG_TAG = CallAudioModeStateMachine.class.getSimpleName();
+
+    private final BaseState mUnfocusedState = new UnfocusedState();
+    private final BaseState mRingingFocusState = new RingingFocusState();
+    private final BaseState mSimCallFocusState = new SimCallFocusState();
+    private final BaseState mVoipCallFocusState = new VoipCallFocusState();
+    private final BaseState mOtherFocusState = new OtherFocusState();
+
+    private final AudioManager mAudioManager;
+    private CallAudioManager mCallAudioManager;
+
+    private int mMostRecentMode;
+    private boolean mIsInitialized = false;
+
+    public CallAudioModeStateMachine(AudioManager audioManager) {
+        super(CallAudioModeStateMachine.class.getSimpleName());
+        mAudioManager = audioManager;
+        mMostRecentMode = AudioManager.MODE_NORMAL;
+
+        addState(mUnfocusedState);
+        addState(mRingingFocusState);
+        addState(mSimCallFocusState);
+        addState(mVoipCallFocusState);
+        addState(mOtherFocusState);
+        setInitialState(mUnfocusedState);
+        start();
+        sendMessage(INITIALIZE);
+    }
+
+    public void setCallAudioManager(CallAudioManager callAudioManager) {
+        mCallAudioManager = callAudioManager;
+    }
+
+    public String getCurrentStateName() {
+        IState currentState = getCurrentState();
+        return currentState == null ? "no state" : currentState.getName();
+    }
+
+    @Override
+    protected void onPreHandleMessage(Message msg) {
+        if (msg.obj != null && msg.obj instanceof MessageArgs) {
+            Log.continueSession(((MessageArgs) msg.obj).session, "CAMSM.pM_" + msg.what);
+            Log.i(LOG_TAG, "Message received: %s.", MESSAGE_CODE_TO_NAME.get(msg.what));
+        } else if (msg.what == RUN_RUNNABLE && msg.obj instanceof Runnable) {
+            Log.i(LOG_TAG, "Running runnable for testing");
+        } else {
+                Log.w(LOG_TAG, "Message sent must be of type nonnull MessageArgs, but got " +
+                        (msg.obj == null ? "null" : msg.obj.getClass().getSimpleName()));
+                Log.w(LOG_TAG, "The message was of code %d = %s",
+                        msg.what, MESSAGE_CODE_TO_NAME.get(msg.what));
+        }
+    }
+
+    @Override
+    protected void onPostHandleMessage(Message msg) {
+        Log.endSession();
+    }
+
+    private BaseState destinationStateAfterNoMoreActiveCalls(MessageArgs args) {
+        if (args.hasRingingCalls) {
+             return mRingingFocusState;
+        } else if (args.hasHoldingCalls || args.isTonePlaying) {
+             return mOtherFocusState;
+        } else {
+             return mUnfocusedState;
+        }
+    }
+}
\ 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..7bb213a
--- /dev/null
+++ b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
@@ -0,0 +1,1217 @@
+/*
+ * 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.SystemProperties;
+import android.os.UserHandle;
+import android.telecom.CallAudioState;
+import android.util.SparseArray;
+
+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;
+    // Wired headset, earpiece, or speakerphone, in that order of precedence.
+    public static final int SWITCH_BASELINE_ROUTE = 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;
+
+    // Used in testing to execute verifications. Not compatible with subsessions.
+    public static final int RUN_RUNNABLE = 9001;
+
+    /** Valid values for mAudioFocusType */
+    public static final int NO_FOCUS = 1;
+    public static final int HAS_FOCUS = 2;
+
+    private static final SparseArray<String> MESSAGE_CODE_TO_NAME = new SparseArray<String>() {{
+        put(CONNECT_WIRED_HEADSET, "CONNECT_WIRED_HEADSET");
+        put(DISCONNECT_WIRED_HEADSET, "DISCONNECT_WIRED_HEADSET");
+        put(CONNECT_BLUETOOTH, "CONNECT_BLUETOOTH");
+        put(DISCONNECT_BLUETOOTH, "DISCONNECT_BLUETOOTH");
+        put(CONNECT_DOCK, "CONNECT_DOCK");
+        put(DISCONNECT_DOCK, "DISCONNECT_DOCK");
+
+        put(SWITCH_EARPIECE, "SWITCH_EARPIECE");
+        put(SWITCH_BLUETOOTH, "SWITCH_BLUETOOTH");
+        put(SWITCH_HEADSET, "SWITCH_HEADSET");
+        put(SWITCH_SPEAKER, "SWITCH_SPEAKER");
+        put(SWITCH_BASELINE_ROUTE, "SWITCH_BASELINE_ROUTE");
+
+        put(REINITIALIZE, "REINITIALIZE");
+
+        put(MUTE_ON, "MUTE_ON");
+        put(MUTE_OFF, "MUTE_OFF");
+        put(TOGGLE_MUTE, "TOGGLE_MUTE");
+
+        put(SWITCH_FOCUS, "SWITCH_FOCUS");
+
+        put(RUN_RUNNABLE, "RUN_RUNNABLE");
+    }};
+
+    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) {
+            String messageCodeName = MESSAGE_CODE_TO_NAME.get(msg.what, "unknown");
+            Log.continueSession((Session) msg.obj, "CARSM.pM_" + messageCodeName);
+            Log.i(this, "Message received: %s=%d", messageCodeName, 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) {
+            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;
+                    if (mDoesDeviceSupportEarpieceRoute) {
+                        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_BASELINE_ROUTE:
+                    if ((mAvailableRoutes & ROUTE_EARPIECE) != 0) {
+                        sendInternalMessage(SWITCH_EARPIECE);
+                    } else if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) {
+                        sendInternalMessage(SWITCH_HEADSET);
+                    } else if (!mDoesDeviceSupportEarpieceRoute) {
+                        sendInternalMessage(SWITCH_SPEAKER);
+                    } else {
+                        Log.e(this, new IllegalStateException(),
+                                "Neither headset nor earpiece are available, but there is an " +
+                                        "earpiece on the device. 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(newState);
+            updateInternalCallAudioState();
+        }
+
+        @Override
+        public void updateSystemAudioState() {
+            updateInternalCallAudioState();
+            setSystemAudioState(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(newState);
+            updateInternalCallAudioState();
+        }
+
+        @Override
+        public void updateSystemAudioState() {
+            updateInternalCallAudioState();
+            setSystemAudioState(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_BASELINE_ROUTE);
+                    }
+                    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(newState);
+            updateInternalCallAudioState();
+        }
+
+        @Override
+        public void updateSystemAudioState() {
+            updateInternalCallAudioState();
+            setSystemAudioState(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_BASELINE_ROUTE);
+                    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(newState);
+            updateInternalCallAudioState();
+        }
+
+        @Override
+        public void updateSystemAudioState() {
+            updateInternalCallAudioState();
+            setSystemAudioState(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_BASELINE_ROUTE);
+                    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 final boolean mDoesDeviceSupportEarpieceRoute;
+
+    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;
+    private CallAudioState mLastKnownCallAudioState;
+
+    public CallAudioRouteStateMachine(
+            Context context,
+            CallsManager callsManager,
+            BluetoothManager bluetoothManager,
+            WiredHeadsetManager wiredHeadsetManager,
+            StatusBarNotifier statusBarNotifier,
+            CallAudioManager.AudioServiceFactory audioServiceFactory,
+            boolean doesDeviceSupportEarpieceRoute) {
+        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;
+        mDoesDeviceSupportEarpieceRoute = doesDeviceSupportEarpieceRoute;
+
+        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);
+    }
+
+    /**
+     * 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;
+        mLastKnownCallAudioState = 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 or runnables)
+     * @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(newCallAudioState);
+                updateInternalCallAudioState();
+                return;
+            case MUTE_OFF:
+                setMuteOn(false);
+                newCallAudioState = new CallAudioState(mIsMuted,
+                        mCurrentCallAudioState.getRoute(),
+                        mAvailableRoutes);
+                setSystemAudioState(newCallAudioState);
+                updateInternalCallAudioState();
+                return;
+            case TOGGLE_MUTE:
+                if (mIsMuted) {
+                    sendInternalMessage(MUTE_OFF);
+                } else {
+                    sendInternalMessage(MUTE_ON);
+                }
+                return;
+            case RUN_RUNNABLE:
+                java.lang.Runnable r = (java.lang.Runnable) msg.obj;
+                r.run();
+                return;
+            default:
+                Log.e(this, new IllegalStateException(),
+                        "Unexpected message code");
+        }
+    }
+
+    public void quitStateMachine() {
+        quitNow();
+    }
+
+    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 newCallAudioState) {
+        Log.i(this, "setSystemAudioState: changing from %s to %s", mLastKnownCallAudioState,
+                newCallAudioState);
+        Log.event(mCallsManager.getForegroundCall(), Log.Events.AUDIO_ROUTE,
+                CallAudioState.audioRouteToString(newCallAudioState.getRoute()));
+
+        if (!newCallAudioState.equals(mLastKnownCallAudioState)) {
+            mCallsManager.onCallAudioStateChanged(mLastKnownCallAudioState, newCallAudioState);
+            updateAudioForForegroundCall(newCallAudioState);
+            mLastKnownCallAudioState = 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 if (mDoesDeviceSupportEarpieceRoute){
+            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();
+        final int route;
+
+        if ((supportedRouteMask & ROUTE_BLUETOOTH) != 0) {
+            route = ROUTE_BLUETOOTH;
+        } else if ((supportedRouteMask & ROUTE_WIRED_HEADSET) != 0) {
+            route = ROUTE_WIRED_HEADSET;
+        } else if ((supportedRouteMask & ROUTE_EARPIECE) != 0) {
+            route = ROUTE_EARPIECE;
+        } else {
+            route = ROUTE_SPEAKER;
+        }
+
+        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();
+    }
+
+    public static boolean doesDeviceSupportEarpieceRoute() {
+        String[] characteristics = SystemProperties.get("ro.build.characteristics").split(",");
+        for (String characteristic : characteristics) {
+            if ("watch".equals(characteristic)) {
+                return false;
+            }
+        }
+        return true;
+    }
+}
\ 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 ea9402e..2172a81 100644
--- a/src/com/android/server/telecom/CallIntentProcessor.java
+++ b/src/com/android/server/telecom/CallIntentProcessor.java
@@ -8,7 +8,9 @@
 import android.os.Bundle;
 import android.os.Trace;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.telecom.Connection;
+import android.telecom.DefaultDialerManager;
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
@@ -24,6 +26,30 @@
  * which interacts with the rest of Telecom, both of which run only as the primary user.
  */
 public class CallIntentProcessor {
+    public interface Adapter {
+        void processOutgoingCallIntent(Context context, CallsManager callsManager,
+                Intent intent);
+        void processIncomingCallIntent(CallsManager callsManager, Intent intent);
+        void processUnknownCallIntent(CallsManager callsManager, Intent intent);
+    }
+
+    public static class AdapterImpl implements Adapter {
+        @Override
+        public void processOutgoingCallIntent(Context context, CallsManager callsManager,
+                Intent intent) {
+            CallIntentProcessor.processOutgoingCallIntent(context, callsManager, intent);
+        }
+
+        @Override
+        public void processIncomingCallIntent(CallsManager callsManager, Intent intent) {
+            CallIntentProcessor.processIncomingCallIntent(callsManager, intent);
+        }
+
+        @Override
+        public void processUnknownCallIntent(CallsManager callsManager, Intent intent) {
+            CallIntentProcessor.processUnknownCallIntent(callsManager, intent);
+        }
+    }
 
     public static final String KEY_IS_UNKNOWN_CALL = "is_unknown_call";
     public static final String KEY_IS_INCOMING_CALL = "is_incoming_call";
@@ -33,6 +59,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;
 
@@ -93,8 +125,12 @@
 
         final boolean isPrivilegedDialer = intent.getBooleanExtra(KEY_IS_PRIVILEGED_DIALER, false);
 
+        fixInitiatingUserIfNecessary(context, intent);
+        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
@@ -103,7 +139,8 @@
             // process will be running throughout the duration of the phone call and should never
             // be killed.
             NewOutgoingCallIntentBroadcaster broadcaster = new NewOutgoingCallIntentBroadcaster(
-                    context, callsManager, call, intent, isPrivilegedDialer);
+                    context, callsManager, call, intent, new PhoneNumberUtilsAdapterImpl(),
+                    isPrivilegedDialer);
             final int result = broadcaster.processIntent();
             final boolean success = result == DisconnectCause.NOT_DISCONNECTED;
 
@@ -113,6 +150,25 @@
         }
     }
 
+    /**
+     * If the call is initiated from managed profile but there is no work dialer installed, treat
+     * the call is initiated from its parent user.
+     */
+    static void fixInitiatingUserIfNecessary(Context context, Intent intent) {
+        final UserHandle initiatingUser = intent.getParcelableExtra(KEY_INITIATING_USER);
+        if (UserUtil.isManagedProfile(context, initiatingUser)) {
+            boolean noDialerInstalled = DefaultDialerManager.getInstalledDialerApplications(context,
+                    initiatingUser.getIdentifier()).size() == 0;
+            if (noDialerInstalled) {
+                final UserManager userManager = UserManager.get(context);
+                UserHandle parentUserHandle =
+                        userManager.getProfileParent(
+                                initiatingUser.getIdentifier()).getUserHandle();
+                intent.putExtra(KEY_INITIATING_USER, parentUserHandle);
+            }
+        }
+    }
+
     static void processIncomingCallIntent(CallsManager callsManager, Intent intent) {
         PhoneAccountHandle phoneAccountHandle = intent.getParcelableExtra(
                 TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
diff --git a/src/com/android/server/telecom/CallLogManager.java b/src/com/android/server/telecom/CallLogManager.java
index 718f2ea..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
@@ -149,9 +160,9 @@
                 call.getCallDataUsage();
 
         int callFeatures = getCallFeatures(call.getVideoStateHistory());
-        logCall(call.getCallerInfo(), logNumber, call.getHandlePresentation(),
-                callLogType, callFeatures, accountHandle, creationTime, age, callDataUsage,
-                call.isEmergencyCall());
+        logCall(call.getCallerInfo(), logNumber, call.getPostDialDigits(),
+                call.getHandlePresentation(), callLogType, callFeatures, accountHandle,
+                creationTime, age, callDataUsage, call.isEmergencyCall(), call.getInitiatingUser());
     }
 
     /**
@@ -159,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.
@@ -170,6 +183,7 @@
     private void logCall(
             CallerInfo callerInfo,
             String number,
+            String postDialDigits,
             int presentation,
             int callType,
             int features,
@@ -177,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
@@ -194,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.");
@@ -259,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
@@ -280,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/CallScreening.java b/src/com/android/server/telecom/CallScreening.java
new file mode 100644
index 0000000..f0ef5a6
--- /dev/null
+++ b/src/com/android/server/telecom/CallScreening.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2016 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.Manifest;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.telecom.DefaultDialerManager;
+import android.telecom.CallScreeningService;
+import android.text.TextUtils;
+
+import com.android.internal.telecom.ICallScreeningAdapter;
+import com.android.internal.telecom.ICallScreeningService;
+
+import java.util.List;
+
+/**
+ * Binds to {@link ICallScreeningService} to allow call blocking. A single instance of this class
+ * handles a single call.
+ */
+public class CallScreening {
+    public interface Listener {
+        void onCallScreeningCompleted(
+                Call call,
+                boolean shouldAllowCall,
+                boolean shouldReject,
+                boolean shouldAddToCallLog,
+                boolean shouldShowNotification);
+    }
+
+    private final Context mContext;
+    private final Listener mListener;
+    private final TelecomSystem.SyncRoot mLock;
+    private final PhoneAccountRegistrar mPhoneAccountRegistrar;
+    private final Handler mHandler = new Handler();
+    private Call mCall;
+    private ICallScreeningService mService;
+    private ServiceConnection mConnection;
+
+    public CallScreening(
+            Context context,
+            Listener listener,
+            TelecomSystem.SyncRoot lock,
+            PhoneAccountRegistrar phoneAccountRegistrar,
+            Call call) {
+        mContext = context;
+        mListener = listener;
+        mLock = lock;
+        mPhoneAccountRegistrar = phoneAccountRegistrar;
+        mCall = call;
+    }
+
+    public void screenCall() {
+        if (!bindService()) {
+            Log.d(this, "no service, giving up");
+            performCleanup();
+        } else {
+            mHandler.postDelayed(new Runnable("CS.sC") {
+                @Override
+                public void loggedRun() {
+                    synchronized (mLock) {
+                        Log.event(mCall, Log.Events.SCREENING_TIMED_OUT);
+                        performCleanup();
+                    }
+                }
+            }.prepare(), Timeouts.getCallScreeningTimeoutMillis(mContext.getContentResolver()));
+        }
+    }
+
+    private void performCleanup() {
+        if (mCall != null) {
+            mListener.onCallScreeningCompleted(mCall, true, false, false, false);
+            mCall = null;
+        }
+        if (mConnection != null) {
+            // We still need to call unbind even if the service disconnected.
+            mContext.unbindService(mConnection);
+            mConnection = null;
+        }
+        mHandler.removeCallbacksAndMessages(null);
+        mService = null;
+    }
+
+    private boolean bindService() {
+        String dialerPackage = DefaultDialerManager
+                .getDefaultDialerApplication(mContext, UserHandle.USER_CURRENT);
+        if (TextUtils.isEmpty(dialerPackage)) {
+            return false;
+        }
+
+        Intent intent = new Intent(CallScreeningService.SERVICE_INTERFACE)
+            .setPackage(dialerPackage);
+        List<ResolveInfo> entries = mContext.getPackageManager().queryIntentServices(intent, 0);
+        if (entries.isEmpty()) {
+            return false;
+        }
+
+        ResolveInfo entry = entries.get(0);
+        if (entry.serviceInfo == null) {
+            return false;
+        }
+
+        if (entry.serviceInfo.permission == null || !entry.serviceInfo.permission.equals(
+                Manifest.permission.BIND_SCREENING_SERVICE)) {
+            Log.w(this, "CallScreeningService must require BIND_SCREENING_SERVICE permission: " +
+                    entry.serviceInfo.packageName);
+            return false;
+        }
+
+        ComponentName componentName =
+                new ComponentName(entry.serviceInfo.packageName, entry.serviceInfo.name);
+        Log.event(mCall, Log.Events.BIND_SCREENING, componentName);
+        intent.setComponent(componentName);
+        ServiceConnection connection = new CallScreeningServiceConnection();
+        if (mContext.bindServiceAsUser(
+                intent,
+                connection,
+                Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
+                UserHandle.CURRENT)) {
+            Log.d(this, "bindService, found service, waiting for it to connect");
+            mConnection = connection;
+            return true;
+        }
+
+        return false;
+    }
+
+    private void onServiceBound(ICallScreeningService service) {
+        mService = service;
+        try {
+            mService.screenCall(new CallScreeningAdapter(), ParcelableCallUtils.toParcelableCall(
+                    mCall, false /* includeVideoProvider */, mPhoneAccountRegistrar));
+        } catch (RemoteException e) {
+            Log.e(this, e, "Failed to set the call screening adapter.");
+            performCleanup();
+        }
+    }
+
+    private class CallScreeningServiceConnection implements ServiceConnection {
+        @Override
+        public void onServiceConnected(ComponentName componentName, IBinder service) {
+            Log.startSession("CSCR.oSC");
+            try {
+                synchronized (mLock) {
+                    Log.event(mCall, Log.Events.SCREENING_BOUND, componentName);
+                    if (mCall == null) {
+                        performCleanup();
+                    } else {
+                        onServiceBound(ICallScreeningService.Stub.asInterface(service));
+                    }
+                }
+            } finally {
+                Log.endSession();
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName componentName) {
+            Log.startSession("CSCR.oSD");
+            try {
+                synchronized (mLock) {
+                    performCleanup();
+                }
+            } finally {
+                Log.endSession();
+            }
+        }
+    }
+
+    private class CallScreeningAdapter extends ICallScreeningAdapter.Stub {
+        @Override
+        public void allowCall(String callId) {
+            try {
+                Log.startSession("CSCR.aC");
+                long token = Binder.clearCallingIdentity();
+                try {
+                    synchronized (mLock) {
+                        Log.d(this, "allowCall(%s)", callId);
+                        if (mCall != null && mCall.getId().equals(callId)) {
+                            mListener.onCallScreeningCompleted(
+                                    mCall,
+                                    true /* shouldAllowCall */,
+                                    false /* shouldReject */,
+                                    false /* shouldAddToCallLog */,
+                                    false /* shouldShowNotification */);
+                        } else {
+                            Log.w(this, "allowCall, unknown call id: %s", callId);
+                        }
+                        mCall = null;
+                        performCleanup();
+                    }
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+            } finally {
+                Log.endSession();
+            }
+        }
+
+        @Override
+        public void disallowCall(
+                String callId,
+                boolean shouldReject,
+                boolean shouldAddToCallLog,
+                boolean shouldShowNotification) {
+            try {
+                Log.startSession("CSCR.dC");
+                long token = Binder.clearCallingIdentity();
+                try {
+                    synchronized (mLock) {
+                        Log.i(this, "disallowCall(%s), shouldReject: %b, shouldAddToCallLog: %b, "
+                                + "shouldShowNotification: %b", callId, shouldReject,
+                                shouldAddToCallLog, shouldShowNotification);
+                        if (mCall != null && mCall.getId().equals(callId)) {
+                            mListener.onCallScreeningCompleted(
+                                    mCall,
+                                    false /* shouldAllowCall */,
+                                    shouldReject,
+                                    shouldAddToCallLog,
+                                    shouldShowNotification);
+                        } else {
+                            Log.w(this, "disallowCall, unknown call id: %s", callId);
+                        }
+                        mCall = null;
+                        performCleanup();
+                    }
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+            } finally {
+                Log.endSession();
+            }
+        }
+    }
+}
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 973e4c1..fbb35ba 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -17,13 +17,20 @@
 package com.android.server.telecom;
 
 import android.content.Context;
+import android.content.pm.UserInfo;
+import android.media.AudioManager;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.Process;
 import android.os.SystemProperties;
+import android.os.SystemVibrator;
 import android.os.Trace;
+import android.os.UserHandle;
+import android.os.UserManager;
 import android.provider.CallLog.Calls;
+import android.provider.Settings;
 import android.telecom.CallAudioState;
 import android.telecom.Conference;
 import android.telecom.Connection;
@@ -46,8 +53,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;
@@ -60,10 +69,12 @@
  * beyond the com.android.server.telecom package boundary.
  */
 @VisibleForTesting
-public class CallsManager extends Call.ListenerBase implements VideoProviderProxy.Listener {
+public class CallsManager extends Call.ListenerBase
+        implements VideoProviderProxy.Listener, CallScreening.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);
@@ -73,7 +84,6 @@
                 ConnectionServiceWrapper newService);
         void onIncomingCallAnswered(Call call);
         void onIncomingCallRejected(Call call, boolean rejectWithMessage, String textMessage);
-        void onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall);
         void onCallAudioStateChanged(CallAudioState oldAudioState, CallAudioState newAudioState);
         void onRingbackRequested(Call call, boolean ringback);
         void onIsConferencedChanged(Call call);
@@ -97,6 +107,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 +132,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 +152,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;
@@ -140,12 +171,6 @@
 
     private boolean mCanAddCall = true;
 
-    /**
-     * The call the user is currently interacting with. This is the call that should have audio
-     * focus and be visible in the in-call UI.
-     */
-    private Call mForegroundCall;
-
     private Runnable mStopTone;
 
     /**
@@ -160,7 +185,11 @@
             PhoneAccountRegistrar phoneAccountRegistrar,
             HeadsetMediaButtonFactory headsetMediaButtonFactory,
             ProximitySensorManagerFactory proximitySensorManagerFactory,
-            InCallWakeLockControllerFactory inCallWakeLockControllerFactory) {
+            InCallWakeLockControllerFactory inCallWakeLockControllerFactory,
+            CallAudioManager.AudioServiceFactory audioServiceFactory,
+            BluetoothManager bluetoothManager,
+            WiredHeadsetManager wiredHeadsetManager,
+            SystemStateProvider systemStateProvider) {
         mContext = context;
         mLock = lock;
         mContactsAsyncHelper = contactsAsyncHelper;
@@ -168,38 +197,70 @@
         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);
+
+        mDtmfLocalTonePlayer = new DtmfLocalTonePlayer();
+        CallAudioRouteStateMachine callAudioRouteStateMachine = new CallAudioRouteStateMachine(
+                context,
+                this,
+                bluetoothManager,
+                wiredHeadsetManager,
+                statusBarNotifier,
+                audioServiceFactory,
+                CallAudioRouteStateMachine.doesDeviceSupportEarpieceRoute()
+        );
+        callAudioRouteStateMachine.initialize();
+
+        CallAudioRoutePeripheralAdapter callAudioRoutePeripheralAdapter =
+                new CallAudioRoutePeripheralAdapter(
+                        callAudioRouteStateMachine,
+                        bluetoothManager,
+                        wiredHeadsetManager,
+                        mDockManager);
+
+        InCallTonePlayer.Factory playerFactory = new InCallTonePlayer.Factory(
+                callAudioRoutePeripheralAdapter, lock);
+
+        SystemSettingsUtil systemSettingsUtil = new SystemSettingsUtil();
+        RingtoneFactory ringtoneFactory = new RingtoneFactory(context);
+        SystemVibrator systemVibrator = new SystemVibrator(context);
+        AsyncRingtonePlayer asyncRingtonePlayer = new AsyncRingtonePlayer();
+        mRinger = new Ringer(playerFactory, context, systemSettingsUtil, asyncRingtonePlayer,
+                ringtoneFactory, systemVibrator);
+
+        mCallAudioManager = new CallAudioManager(callAudioRouteStateMachine,
+                this,new CallAudioModeStateMachine((AudioManager)
+                        mContext.getSystemService(Context.AUDIO_SERVICE)),
+                playerFactory, mRinger, new RingbackPlayer(playerFactory), mDtmfLocalTonePlayer);
+
         mHeadsetMediaButton = headsetMediaButtonFactory.create(context, this, mLock);
         mTtyManager = new TtyManager(context, mWiredHeadsetManager);
         mProximitySensorManager = proximitySensorManagerFactory.create(context, this);
         mPhoneStateBroadcaster = new PhoneStateBroadcaster(this);
-        mCallLogManager = new CallLogManager(context);
-        mInCallController = new InCallController(context, mLock, this);
-        mDtmfLocalTonePlayer = new DtmfLocalTonePlayer(context);
+        mCallLogManager = new CallLogManager(context, phoneAccountRegistrar);
+        mInCallController = new InCallController(context, mLock, this, systemStateProvider);
         mConnectionServiceRepository =
                 new ConnectionServiceRepository(mPhoneAccountRegistrar, mContext, mLock, this);
         mInCallWakeLockController = inCallWakeLockControllerFactory.create(context, this);
 
+        mListeners.add(mInCallWakeLockController);
         mListeners.add(statusBarNotifier);
         mListeners.add(mCallLogManager);
         mListeners.add(mPhoneStateBroadcaster);
         mListeners.add(mInCallController);
-        mListeners.add(mRinger);
-        mListeners.add(new RingbackPlayer(this, playerFactory));
-        mListeners.add(new InCallToneMonitor(playerFactory, this));
         mListeners.add(mCallAudioManager);
         mListeners.add(missedCallNotifier);
-        mListeners.add(mDtmfLocalTonePlayer);
         mListeners.add(mHeadsetMediaButton);
         mListeners.add(mProximitySensorManager);
 
-        mMissedCallNotifier.updateOnStartup(
-                mLock, this, mContactsAsyncHelper, mCallerInfoAsyncQueryFactory);
+        // There is no USER_SWITCHED broadcast for user 0, handle it here explicitly.
+        final UserManager userManager = UserManager.get(mContext);
+        // Don't load missed call if it is run in split user model.
+        if (userManager.isPrimaryUser()) {
+            onUserSwitch(Process.myUserHandle());
+        }
     }
 
     public void setRespondViaSmsManager(RespondViaSmsManager respondViaSmsManager) {
@@ -241,18 +302,63 @@
     }
 
     @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()) {
-            incomingCall.reject(false, null);
-            // since the call was not added to the list of calls, we have to call the missed
-            // call notifier and the call logger manually.
-            mMissedCallNotifier.showMissedCallNotification(incomingCall);
-            mCallLogManager.logCall(incomingCall, Calls.MISSED_TYPE);
+        // TODO: Parallelize Call screening, block check, and send to voicemail.
+        final String number = incomingCall.getHandle() == null ? null : incomingCall.getHandle()
+                .getSchemeSpecificPart();
+        Log.v(this, "Looking up information for: %s.", Log.piiHandle(number));
+
+        new AsyncBlockCheckTask(mContext, incomingCall,
+                new CallScreening(mContext, CallsManager.this, mLock,
+                        mPhoneAccountRegistrar, incomingCall), this, shouldSendToVoicemail)
+                .execute(number);
+    }
+
+    @Override
+    public void onCallScreeningCompleted(
+                Call incomingCall,
+                boolean shouldAllowCall,
+                boolean shouldReject,
+                boolean shouldAddToCallLog,
+                boolean shouldShowNotification) {
+        // 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,
+                    shouldAllowCall ? "blocking call" : "successful incoming call");
         } else {
-            addCall(incomingCall);
+            Log.i(this, "onCallScreeningCompleted: call already disconnected.");
+        }
+
+        if (shouldAllowCall) {
+            if (hasMaximumRingingCalls()) {
+                Log.i(this, "onCallScreeningCompleted: Call rejected! Exceeds maximum number of " +
+                        "ringing calls.");
+                rejectCallAndLog(incomingCall);
+            } else if (hasMaximumDialingCalls()) {
+                Log.i(this, "onCallScreeningCompleted: Call rejected! Exceeds maximum number of " +
+                        "dialing calls.");
+                rejectCallAndLog(incomingCall);
+            } else {
+                addCall(incomingCall);
+            }
+        } else {
+            if (shouldReject) {
+                Log.i(this, "onCallScreeningCompleted: blocked call, rejecting.");
+                incomingCall.reject(false, null);
+            }
+            if (shouldAddToCallLog) {
+                Log.i(this, "onCallScreeningCompleted: blocked call, adding to call log.");
+                mCallLogManager.logCall(incomingCall, Calls.MISSED_TYPE);
+            }
+            if (shouldShowNotification) {
+                Log.i(this, "onCallScreeningCompleted: blocked call, showing notification.");
+                mMissedCallNotifier.showMissedCallNotification(incomingCall);
+            }
         }
     }
 
@@ -294,30 +400,32 @@
             // Play tone if it is one of the dialpad digits, canceling out the previously queued
             // up stopTone runnable since playing a new tone automatically stops the previous tone.
             if (mStopTone != null) {
-                mHandler.removeCallbacks(mStopTone);
+                mHandler.removeCallbacks(mStopTone.getRunnableToCancel());
+                mStopTone.cancel();
             }
 
             mDtmfLocalTonePlayer.playTone(call, nextChar);
 
             // TODO: Create a LockedRunnable class that does the synchronization automatically.
-            mStopTone = new Runnable() {
+            mStopTone = new Runnable("CM.oPDC") {
                 @Override
-                public void run() {
+                public void loggedRun() {
                     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);
                     }
                 }
             };
-            mHandler.postDelayed(
-                    mStopTone,
+            mHandler.postDelayed(mStopTone.prepare(),
                     Timeouts.getDelayBetweenDtmfTonesMillis(mContext.getContentResolver()));
         } else if (nextChar == 0 || nextChar == TelecomManager.DTMF_CHARACTER_WAIT ||
                 nextChar == TelecomManager.DTMF_CHARACTER_PAUSE) {
             // Stop the tone if a tone is playing, removing any other stopTone callbacks since
             // the previous tone is being stopped anyway.
             if (mStopTone != null) {
-                mHandler.removeCallbacks(mStopTone);
+                mHandler.removeCallbacks(mStopTone.getRunnableToCancel());
+                mStopTone.cancel();
             }
             mDtmfLocalTonePlayer.stopTone(call);
         } else {
@@ -360,9 +468,9 @@
     @Override
     public boolean onCanceledViaNewOutgoingCallBroadcast(final Call call) {
         mPendingCallsToDisconnect.add(call);
-        mHandler.postDelayed(new Runnable() {
+        mHandler.postDelayed(new Runnable("CM.oCVNOCB") {
             @Override
-            public void run() {
+            public void loggedRun() {
                 synchronized (mLock) {
                     if (mPendingCallsToDisconnect.remove(call)) {
                         Log.i(this, "Delayed disconnection of call: %s", call);
@@ -370,7 +478,7 @@
                     }
                 }
             }
-        }, Timeouts.getNewOutgoingCallCancelMillis(mContext.getContentResolver()));
+        }.prepare(), Timeouts.getNewOutgoingCallCancelMillis(mContext.getContentResolver()));
 
         return true;
     }
@@ -414,16 +522,22 @@
         }
     }
 
-    Collection<Call> getCalls() {
+    @VisibleForTesting
+    public Collection<Call> getCalls() {
         return Collections.unmodifiableCollection(mCalls);
     }
 
-    Call getForegroundCall() {
-        return mForegroundCall;
+    @VisibleForTesting
+    public Call getForegroundCall() {
+        if (mCallAudioManager == null) {
+            // Happens when getForegroundCall is called before full initialization.
+            return null;
+        }
+        return mCallAudioManager.getForegroundCall();
     }
 
-    Ringer getRinger() {
-        return mRinger;
+    CallAudioManager getCallAudioManager() {
+        return mCallAudioManager;
     }
 
     InCallController getInCallController() {
@@ -469,7 +583,8 @@
         return mTtyManager.getCurrentTtyMode();
     }
 
-    void addListener(CallsManagerListener listener) {
+    @VisibleForTesting
+    public void addListener(CallsManagerListener listener) {
         mListeners.add(listener);
     }
 
@@ -492,6 +607,7 @@
             handle = extras.getParcelable(TelephonyManager.EXTRA_INCOMING_NUMBER);
         }
         Call call = new Call(
+                getNextCallId(),
                 mContext,
                 this,
                 mLock,
@@ -502,8 +618,16 @@
                 null /* gatewayInfo */,
                 null /* connectionManagerPhoneAccount */,
                 phoneAccountHandle,
-                true /* isIncoming */,
-                false /* isConference */);
+                Call.CALL_DIRECTION_INCOMING /* callDirection */,
+                false /* forceAttachToExistingConnection */,
+                false /* isConference */
+        );
+
+        call.initAnalytics();
+        if (getForegroundCall() != null) {
+            getForegroundCall().getAnalytics().setCallIsInterrupted(true);
+            call.getAnalytics().setCallIsAdditional(true);
+        }
 
         call.setIntentExtras(extras);
         // TODO: Move this to be a part of addCall()
@@ -515,6 +639,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 +650,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 +677,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 +691,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,17 +702,44 @@
      * @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) {
-            Call ongoingCall = mForegroundCall;
+        if (getForegroundCall() != null) {
+            Call ongoingCall = getForegroundCall();
             // If there is an ongoing call, use the same phone account to place this new call.
             // If the ongoing call is a conference call, we fetch the phone account from the
             // child calls because we don't have targetPhoneAccount set on Conference calls.
@@ -628,7 +766,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 +775,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 +812,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);
         }
 
@@ -686,8 +828,9 @@
      * @param speakerphoneOn Whether or not to turn the speakerphone on once the call connects.
      * @param videoState The desired video state for the outgoing call.
      */
-    void placeOutgoingCall(Call call, Uri handle, GatewayInfo gatewayInfo, boolean speakerphoneOn,
-            int videoState) {
+    @VisibleForTesting
+    public void placeOutgoingCall(Call call, Uri handle, GatewayInfo gatewayInfo,
+            boolean speakerphoneOn, int videoState) {
         if (call == null) {
             // don't do anything if the call no longer exists
             Log.i(this, "Canceling unknown call.");
@@ -705,23 +848,26 @@
 
         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));
+
+        final boolean useSpeakerWhenDocked = mContext.getResources().getBoolean(
+                R.bool.use_speaker_when_docked);
+        final boolean isDocked = mDockManager.isDocked();
+        final boolean useSpeakerForVideoCall = isSpeakerphoneAutoEnabled(videoState);
+
+        // Auto-enable speakerphone if the originating intent specified to do so, if the call
+        // is a video call, of if using speaker when docked
+        call.setStartWithSpeakerphoneOn(speakerphoneOn || useSpeakerForVideoCall
+                || (useSpeakerWhenDocked && isDocked));
         call.setVideoState(videoState);
 
         if (speakerphoneOn) {
             Log.i(this, "%s Starting with speakerphone as requested", call);
-        } else {
+        } else if (useSpeakerWhenDocked && useSpeakerWhenDocked) {
             Log.i(this, "%s Starting with speakerphone because car is docked.", call);
+        } else if (useSpeakerForVideoCall) {
+            Log.i(this, "%s Starting with speakerphone because its a video call.", call);
         }
 
-        final boolean useSpeakerWhenDocked = mContext.getResources().getBoolean(
-                R.bool.use_speaker_when_docked);
-
-        call.setStartWithSpeakerphoneOn(speakerphoneOn
-                || (useSpeakerWhenDocked && mDockManager.isDocked()));
-
         if (call.isEmergencyCall()) {
             // Emergency -- CreateConnectionProcessor will choose accounts automatically
             call.setTargetPhoneAccount(null);
@@ -735,8 +881,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"));
@@ -750,7 +896,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);
     }
 
@@ -762,22 +909,24 @@
      * @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 {
+            Call foregroundCall = getForegroundCall();
             // If the foreground call is not the ringing call and it is currently isActive() or
             // STATE_DIALING, put it on hold before answering the call.
-            if (mForegroundCall != null && mForegroundCall != call &&
-                    (mForegroundCall.isActive() ||
-                     mForegroundCall.getState() == CallState.DIALING)) {
-                if (0 == (mForegroundCall.getConnectionCapabilities()
+            if (foregroundCall != null && foregroundCall != call &&
+                    (foregroundCall.isActive() ||
+                     foregroundCall.getState() == CallState.DIALING)) {
+                if (0 == (foregroundCall.getConnectionCapabilities()
                         & Connection.CAPABILITY_HOLD)) {
                     // This call does not support hold.  If it is from a different connection
                     // service, then disconnect it, otherwise allow the connection service to
                     // figure out the right states.
-                    if (mForegroundCall.getConnectionService() != call.getConnectionService()) {
-                        mForegroundCall.disconnect();
+                    if (foregroundCall.getConnectionService() != call.getConnectionService()) {
+                        foregroundCall.disconnect();
                     }
                 } else {
                     Call heldCall = getHeldCall();
@@ -788,8 +937,8 @@
                     }
 
                     Log.v(this, "Holding active/dialing call %s before answering incoming call %s.",
-                            mForegroundCall, call);
-                    mForegroundCall.hold();
+                            foregroundCall, call);
+                    foregroundCall.hold();
                 }
                 // TODO: Wait until we get confirmation of the active call being
                 // on-hold before answering the new call.
@@ -820,7 +969,7 @@
     private boolean isSpeakerphoneAutoEnabled(int videoState) {
         return VideoProfile.isVideo(videoState) &&
             !mWiredHeadsetManager.isPluggedIn() &&
-            !mCallAudioManager.isBluetoothDeviceAvailable() &&
+            !mBluetoothManager.isBluetoothAvailable() &&
             isSpeakerEnabledForVideoCalls();
     }
 
@@ -840,7 +989,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 {
@@ -856,7 +1006,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 {
@@ -868,7 +1019,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 {
@@ -893,7 +1045,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)) {
@@ -921,7 +1074,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 {
@@ -935,7 +1089,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 {
@@ -950,6 +1105,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);
@@ -981,14 +1150,12 @@
         if (!mCalls.contains(call)) {
             Log.i(this, "Attempted to add account to unknown call %s", call);
         } else {
-            // TODO: There is an odd race condition here. Since NewOutgoingCallIntentBroadcaster and
-            // the SELECT_PHONE_ACCOUNT sequence run in parallel, if the user selects an account before the
-            // NEW_OUTGOING_CALL sequence finishes, we'll start the call immediately without
-            // respecting a rewritten number or a canceled number. This is unlikely since
-            // NEW_OUTGOING_CALL sequence, in practice, runs a lot faster than the user selecting
-            // a phone account from the in-call UI.
             call.setTargetPhoneAccount(account);
 
+            if (!call.isNewOutgoingCallIntentBroadcastDone()) {
+                return;
+            }
+
             // Note: emergency calls never go through account selection dialog so they never
             // arrive here.
             if (makeRoomForOutgoingCall(call, false /* isEmergencyCall */)) {
@@ -998,13 +1165,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);
@@ -1047,8 +1217,9 @@
         removeCall(call);
         if (mLocallyDisconnectingCalls.contains(call)) {
             mLocallyDisconnectingCalls.remove(call);
-            if (mForegroundCall != null && mForegroundCall.getState() == CallState.ON_HOLD) {
-                mForegroundCall.unhold();
+            Call foregroundCall = getForegroundCall();
+            if (foregroundCall != null && foregroundCall.getState() == CallState.ON_HOLD) {
+                foregroundCall.unhold();
             }
         }
     }
@@ -1112,6 +1283,13 @@
      * Returns true if telecom supports adding another top-level call.
      */
     boolean canAddCall() {
+        boolean isDeviceProvisioned = Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.DEVICE_PROVISIONED, 0) != 0;
+        if (!isDeviceProvisioned) {
+            Log.d(TAG, "Device not provisioned, canAddCall is false.");
+            return false;
+        }
+
         if (getFirstCallWithState(OUTGOING_CALL_STATES) != null) {
             return false;
         }
@@ -1150,7 +1328,8 @@
         return getFirstCallWithState(CallState.RINGING);
     }
 
-    Call getActiveCall() {
+    @VisibleForTesting
+    public Call getActiveCall() {
         return getFirstCallWithState(CallState.ACTIVE);
     }
 
@@ -1158,11 +1337,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) {
@@ -1172,11 +1353,13 @@
         return count;
     }
 
-    Call getOutgoingCall() {
+    @VisibleForTesting
+    public Call getOutgoingCall() {
         return getFirstCallWithState(OUTGOING_CALL_STATES);
     }
 
-    Call getFirstCallWithState(int... states) {
+    @VisibleForTesting
+    public Call getFirstCallWithState(int... states) {
         return getFirstCallWithState(null, states);
     }
 
@@ -1190,8 +1373,9 @@
     Call getFirstCallWithState(Call callToSkip, int... states) {
         for (int currentState : states) {
             // check the foreground first
-            if (mForegroundCall != null && mForegroundCall.getState() == currentState) {
-                return mForegroundCall;
+            Call foregroundCall = getForegroundCall();
+            if (foregroundCall != null && foregroundCall.getState() == currentState) {
+                return foregroundCall;
             }
 
             for (Call call : mCalls) {
@@ -1213,6 +1397,7 @@
     }
 
     Call createConferenceCall(
+            String callId,
             PhoneAccountHandle phoneAccount,
             ParcelableConference parcelableConference) {
 
@@ -1224,6 +1409,7 @@
                         parcelableConference.getConnectTimeMillis();
 
         Call call = new Call(
+                callId,
                 mContext,
                 this,
                 mLock,
@@ -1234,7 +1420,8 @@
                 null /* gatewayInfo */,
                 null /* connectionManagerPhoneAccount */,
                 phoneAccount,
-                false /* isIncoming */,
+                Call.CALL_DIRECTION_UNDEFINED /* callDirection */,
+                false /* forceAttachToExistingConnection */,
                 true /* isConference */,
                 connectTime);
 
@@ -1277,6 +1464,19 @@
     }
 
     /**
+     * Reject an incoming call and manually add it to the Call Log.
+     * @param incomingCall Incoming call that has been rejected
+     */
+    private void rejectCallAndLog(Call incomingCall) {
+        incomingCall.reject(false, null);
+        // 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);
+    }
+
+    /**
      * Adds the specified call to the main list of live calls.
      *
      * @param call The call to add.
@@ -1287,7 +1487,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) {
@@ -1298,7 +1498,6 @@
                 Trace.endSection();
             }
         }
-        updateCallsManagerState();
         Trace.endSection();
     }
 
@@ -1320,6 +1519,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");
@@ -1329,7 +1529,6 @@
                     Trace.endSection();
                 }
             }
-            updateCallsManagerState();
         }
         Trace.endSection();
     }
@@ -1360,6 +1559,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");
@@ -1369,59 +1569,11 @@
                         Trace.endSection();
                     }
                 }
-                updateCallsManagerState();
             }
             Trace.endSection();
         }
     }
 
-    /**
-     * Checks which call should be visible to the user and have audio focus.
-     */
-    private void updateForegroundCall() {
-        Trace.beginSection("updateForegroundCall");
-        Call newForegroundCall = null;
-        for (Call call : mCalls) {
-            // TODO: Foreground-ness needs to be explicitly set. No call, regardless
-            // of its state will be foreground by default and instead the connection service should
-            // be notified when its calls enter and exit foreground state. Foreground will mean that
-            // the call should play audio and listen to microphone if it wants.
-
-            // Only top-level calls can be in foreground
-            if (call.getParentCall() != null) {
-                continue;
-            }
-
-            // Active calls have priority.
-            if (call.isActive()) {
-                newForegroundCall = call;
-                break;
-            }
-
-            if (call.isAlive() || call.getState() == CallState.RINGING) {
-                newForegroundCall = call;
-                // Don't break in case there's an active call that has priority.
-            }
-        }
-
-        if (newForegroundCall != mForegroundCall) {
-            Log.v(this, "Updating foreground call, %s -> %s.", mForegroundCall, newForegroundCall);
-            Call oldForegroundCall = mForegroundCall;
-            mForegroundCall = newForegroundCall;
-
-            for (CallsManagerListener listener : mListeners) {
-                if (Log.SYSTRACE_DEBUG) {
-                    Trace.beginSection(listener.getClass().toString() + " updateForegroundCall");
-                }
-                listener.onForegroundCallChanged(oldForegroundCall, mForegroundCall);
-                if (Log.SYSTRACE_DEBUG) {
-                    Trace.endSection();
-                }
-            }
-        }
-        Trace.endSection();
-    }
-
     private void updateCanAddCall() {
         boolean newCanAddCall = canAddCall();
         if (newCanAddCall != mCanAddCall) {
@@ -1439,7 +1591,6 @@
     }
 
     private void updateCallsManagerState() {
-        updateForegroundCall();
         updateCanAddCall();
     }
 
@@ -1509,7 +1660,7 @@
         if (hasMaximumLiveCalls()) {
             // NOTE: If the amount of live calls changes beyond 1, this logic will probably
             // have to change.
-            Call liveCall = getFirstCallWithState(call, LIVE_CALL_STATES);
+            Call liveCall = getFirstCallWithState(LIVE_CALL_STATES);
             Log.i(this, "makeRoomForOutgoingCall call = " + call + " livecall = " +
                    liveCall);
 
@@ -1526,12 +1677,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;
                 }
@@ -1543,6 +1698,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;
                 }
@@ -1572,6 +1729,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.
@@ -1586,6 +1745,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;
             }
@@ -1631,6 +1792,7 @@
      */
     Call createCallForExistingConnection(String callId, ParcelableConnection connection) {
         Call call = new Call(
+                callId,
                 mContext,
                 this,
                 mLock,
@@ -1641,10 +1803,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());
@@ -1658,6 +1824,43 @@
     }
 
     /**
+     * @return A new unique telecom call Id.
+     */
+    private String getNextCallId() {
+        synchronized(mLock) {
+            return TELECOM_CALL_ID_PREFIX + (++mCallId);
+        }
+    }
+
+    /**
+     * Callback when foreground user is switched. We will reload missed call in all profiles
+     * including the user itself. There may be chances that profiles are not started yet.
+     */
+    void onUserSwitch(UserHandle userHandle) {
+        mMissedCallNotifier.setCurrentUserHandle(userHandle);
+        final UserManager userManager = UserManager.get(mContext);
+        List<UserInfo> profiles = userManager.getEnabledProfiles(userHandle.getIdentifier());
+        for (UserInfo profile : profiles) {
+            reloadMissedCallsOfUser(profile.getUserHandle());
+        }
+    }
+
+    /**
+     * Because there may be chances that profiles are not started yet though its parent user is
+     * switched, we reload missed calls of profile that are just started here.
+     */
+    void onUserStarting(UserHandle userHandle) {
+        if (UserUtil.isProfile(mContext, userHandle)) {
+            reloadMissedCallsOfUser(userHandle);
+        }
+    }
+
+    private void reloadMissedCallsOfUser(UserHandle userHandle) {
+        mMissedCallNotifier.reloadFromDatabase(
+                mLock, this, mContactsAsyncHelper, mCallerInfoAsyncQueryFactory, userHandle);
+    }
+
+    /**
      * Dumps the state of the {@link CallsManager}.
      *
      * @param pw The {@code IndentingPrintWriter} to write the state to.
@@ -1672,7 +1875,6 @@
             }
             pw.decreaseIndent();
         }
-        pw.println("mForegroundCall: " + (mForegroundCall == null ? "none" : mForegroundCall));
 
         if (mCallAudioManager != null) {
             pw.println("mCallAudioManager:");
diff --git a/src/com/android/server/telecom/CallsManagerListenerBase.java b/src/com/android/server/telecom/CallsManagerListenerBase.java
index 58085a0..50716d5 100644
--- a/src/com/android/server/telecom/CallsManagerListenerBase.java
+++ b/src/com/android/server/telecom/CallsManagerListenerBase.java
@@ -52,10 +52,6 @@
     }
 
     @Override
-    public void onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall) {
-    }
-
-    @Override
     public void onCallAudioStateChanged(CallAudioState oldAudioState,
             CallAudioState newAudioState) {
     }
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 b80134e..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());
@@ -709,6 +710,18 @@
         removeCall(call, new DisconnectCause(DisconnectCause.LOCAL));
     }
 
+    /** @see IConnectionService#silence(String) */
+    void silence(Call call) {
+        final String callId = mCallIdMapper.getCallId(call);
+        if (callId != null && isServiceValid("silence")) {
+            try {
+                logOutgoing("silence %s", callId);
+                mServiceInterface.silence(callId);
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
     /** @see IConnectionService#hold(String) */
     void hold(Call call) {
         final String callId = mCallIdMapper.getCallId(call);
@@ -733,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 {
@@ -791,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")) {
@@ -808,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) {
             }
@@ -968,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())) {
@@ -982,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/ContactsAsyncHelper.java b/src/com/android/server/telecom/ContactsAsyncHelper.java
index 44fa654..974fc51 100644
--- a/src/com/android/server/telecom/ContactsAsyncHelper.java
+++ b/src/com/android/server/telecom/ContactsAsyncHelper.java
@@ -29,6 +29,7 @@
 
 // TODO: Needed for move to system service: import com.android.internal.R;
 
+import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 
@@ -57,15 +58,22 @@
                 Object cookie);
     }
 
+    /**
+     * Interface to enable stubbing of the call to openInputStream
+     */
+    public interface ContentResolverAdapter {
+        InputStream openInputStream(Context context, Uri uri) throws FileNotFoundException;
+    }
+
     // constants
     private static final int EVENT_LOAD_IMAGE = 1;
 
     /** Handler run on a worker thread to load photo asynchronously. */
     private Handler mThreadHandler;
-    private final TelecomSystem.SyncRoot mLock;
+    private final ContentResolverAdapter mContentResolverAdapter;
 
-    public ContactsAsyncHelper(TelecomSystem.SyncRoot lock) {
-        mLock = lock;
+    public ContactsAsyncHelper(ContentResolverAdapter contentResolverAdapter) {
+        mContentResolverAdapter = contentResolverAdapter;
     }
 
     private static final class WorkerArgs {
@@ -95,8 +103,8 @@
                     InputStream inputStream = null;
                     try {
                         try {
-                            inputStream = args.context.getContentResolver()
-                                    .openInputStream(args.displayPhotoUri);
+                            inputStream = mContentResolverAdapter.openInputStream(
+                                    args.context, args.displayPhotoUri);
                         } catch (Exception e) {
                             Log.e(this, e, "Error opening photo input stream");
                         }
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..69cc129 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;
 
@@ -28,7 +29,7 @@
 /**
  * Registers a timeout for a call and disconnects the call when the timeout expires.
  */
-final class CreateConnectionTimeout implements Runnable {
+final class CreateConnectionTimeout extends Runnable {
     private final Context mContext;
     private final PhoneAccountRegistrar mPhoneAccountRegistrar;
     private final ConnectionServiceWrapper mConnectionService;
@@ -39,6 +40,7 @@
 
     CreateConnectionTimeout(Context context, PhoneAccountRegistrar phoneAccountRegistrar,
             ConnectionServiceWrapper service, Call call) {
+        super("CCT");
         mContext = context;
         mPhoneAccountRegistrar = phoneAccountRegistrar;
         mConnectionService = service;
@@ -54,7 +56,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;
         }
@@ -83,7 +86,7 @@
         if (timeoutLengthMillis <= 0) {
             Log.d(this, "registerTimeout, timeout set to %d, skipping", timeoutLengthMillis);
         } else {
-            mHandler.postDelayed(this, timeoutLengthMillis);
+            mHandler.postDelayed(prepare(), timeoutLengthMillis);
         }
     }
 
@@ -91,6 +94,7 @@
         Log.d(this, "unregisterTimeout");
         mIsRegistered = false;
         mHandler.removeCallbacksAndMessages(null);
+        cancel();
     }
 
     boolean isCallTimedOut() {
@@ -98,7 +102,7 @@
     }
 
     @Override
-    public void run() {
+    public void loggedRun() {
         if (mIsRegistered && isCallBeingPlaced(mCall)) {
             Log.i(this, "run, call timed out, calling disconnect");
             mIsCallTimedOut = true;
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..27ffd28 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);
     }
 
@@ -37,10 +40,15 @@
     private class DockBroadcastReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {
-            if (Intent.ACTION_DOCK_EVENT.equals(intent.getAction())) {
-                int dockState = intent.getIntExtra(
-                        Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_UNDOCKED);
-                onDockChanged(dockState);
+            Log.startSession("DM.oR");
+            try {
+                if (Intent.ACTION_DOCK_EVENT.equals(intent.getAction())) {
+                    int dockState = intent.getIntExtra(
+                            Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_UNDOCKED);
+                    onDockChanged(dockState);
+                }
+            } finally {
+                Log.endSession();
             }
         }
     }
@@ -65,7 +73,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/DtmfLocalTonePlayer.java b/src/com/android/server/telecom/DtmfLocalTonePlayer.java
index a0d2862..20c9dd8 100644
--- a/src/com/android/server/telecom/DtmfLocalTonePlayer.java
+++ b/src/com/android/server/telecom/DtmfLocalTonePlayer.java
@@ -34,16 +34,13 @@
  * class employs a concept of a call "session" that starts and stops when the foreground call
  * changes.
  */
-class DtmfLocalTonePlayer extends CallsManagerListenerBase {
+class DtmfLocalTonePlayer {
     /** Generator used to actually play the tone. */
     private ToneGenerator mToneGenerator;
 
     /** The current call associated with an existing dtmf session. */
     private Call mCall;
 
-    /** The context. */
-    private final Context mContext;
-
     /**
      * Message codes to be used for creating and deleting ToneGenerator object in the tonegenerator
      * thread.
@@ -54,13 +51,8 @@
     /** Handler running on the tonegenerator thread. */
     private Handler mHandler;
 
+    public DtmfLocalTonePlayer() { }
 
-    public DtmfLocalTonePlayer(Context context) {
-        mContext = context;
-    }
-
-    /** {@inheritDoc} */
-    @Override
     public void onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall) {
         endDtmfSession(oldForegroundCall);
         startDtmfSession(newForegroundCall);
@@ -219,7 +211,7 @@
         };
     }
 
-    private static final int getMappedTone(char digit) {
+    private static int getMappedTone(char digit) {
         if (digit >= '0' && digit <= '9') {
             return ToneGenerator.TONE_DTMF_0 + digit - '0';
         } else if (digit == '#') {
diff --git a/src/com/android/server/telecom/InCallAdapter.java b/src/com/android/server/telecom/InCallAdapter.java
index 9239288..bee03a2 100644
--- a/src/com/android/server/telecom/InCallAdapter.java
+++ b/src/com/android/server/telecom/InCallAdapter.java
@@ -30,22 +30,25 @@
     private final CallsManager mCallsManager;
     private final CallIdMapper mCallIdMapper;
     private final TelecomSystem.SyncRoot mLock;
+    private final String mOwnerComponentName;
 
     /** Persists the specified parameters. */
     public InCallAdapter(CallsManager callsManager, CallIdMapper callIdMapper,
-            TelecomSystem.SyncRoot lock) {
+            TelecomSystem.SyncRoot lock, String ownerComponentName) {
         mCallsManager = callsManager;
         mCallIdMapper = callIdMapper;
         mLock = lock;
+        mOwnerComponentName = ownerComponentName;
     }
 
     @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", mOwnerComponentName);
+            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 +56,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.rC", mOwnerComponentName);
+            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 +79,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", mOwnerComponentName);
+            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 +102,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", mOwnerComponentName);
+            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 +125,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", mOwnerComponentName);
+            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 +148,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", mOwnerComponentName);
+            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 +171,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", mOwnerComponentName);
+            long token = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
                     Call call = mCallIdMapper.getCall(callId);
                     if (call != null) {
                         mCallsManager.holdCall(call);
@@ -172,18 +193,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", mOwnerComponentName);
+            long token = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
                     Call call = mCallIdMapper.getCall(callId);
                     if (call != null) {
                         mCallsManager.unholdCall(call);
@@ -191,19 +215,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", mOwnerComponentName);
+            long token = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
                     Call call = mCallIdMapper.getCall(callId);
                     if (call != null) {
                         mCallsManager.phoneAccountSelected(call, accountHandle, setDefault);
@@ -211,43 +238,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", mOwnerComponentName);
+            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", mOwnerComponentName);
+            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", mOwnerComponentName);
+            long token = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
                     Call call = mCallIdMapper.getCall(callId);
                     Call otherCall = mCallIdMapper.getCall(otherCallId);
                     if (call != null && otherCall != null) {
@@ -255,20 +294,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", mOwnerComponentName);
+            long token = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
                     Call call = mCallIdMapper.getCall(callId);
                     if (call != null) {
                         call.splitFromConference();
@@ -276,18 +317,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", mOwnerComponentName);
+            long token = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
                     Call call = mCallIdMapper.getCall(callId);
                     if (call != null) {
                         call.mergeConference();
@@ -295,18 +339,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", mOwnerComponentName);
+            long token = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
                     Call call = mCallIdMapper.getCall(callId);
                     if (call != null) {
                         call.swapConference();
@@ -314,33 +361,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("ICA.tOnPS", mOwnerComponentName);
+            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.tOffPS", mOwnerComponentName);
+            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 ab9bf91..41c99db 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -25,7 +25,6 @@
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.content.res.Resources;
-import android.net.Uri;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
@@ -33,21 +32,22 @@
 import android.os.Trace;
 import android.os.UserHandle;
 import android.telecom.CallAudioState;
-import android.telecom.Connection;
 import android.telecom.DefaultDialerManager;
 import android.telecom.InCallService;
 import android.telecom.ParcelableCall;
 import android.telecom.TelecomManager;
-import android.telecom.VideoCallImpl;
+import android.text.TextUtils;
 import android.util.ArrayMap;
 
 // TODO: Needed for move to system service: import com.android.internal.R;
 import com.android.internal.telecom.IInCallService;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.telecom.SystemStateProvider.SystemStateListener;
 
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Iterator;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -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();
         }
     }
 
@@ -129,6 +133,19 @@
         }
     };
 
+    private final SystemStateListener mSystemStateListener = new SystemStateListener() {
+        @Override
+        public void onCarModeChanged(boolean isCarMode) {
+            // Do something when the car mode changes.
+        }
+    };
+
+    private static final int IN_CALL_SERVICE_TYPE_INVALID = 0;
+    private static final int IN_CALL_SERVICE_TYPE_DIALER_UI = 1;
+    private static final int IN_CALL_SERVICE_TYPE_SYSTEM_UI = 2;
+    private static final int IN_CALL_SERVICE_TYPE_CAR_MODE_UI = 3;
+    private static final int IN_CALL_SERVICE_TYPE_NON_UI = 4;
+
     /**
      * Maintains a binding connection to the in-call app(s).
      * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is
@@ -146,7 +163,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;
@@ -154,17 +171,21 @@
     private final Context mContext;
     private final TelecomSystem.SyncRoot mLock;
     private final CallsManager mCallsManager;
+    private final SystemStateProvider mSystemStateProvider;
 
-    public InCallController(
-            Context context, TelecomSystem.SyncRoot lock, CallsManager callsManager) {
+    public InCallController(Context context, TelecomSystem.SyncRoot lock, CallsManager callsManager,
+            SystemStateProvider systemStateProvider) {
         mContext = context;
         mLock = lock;
         mCallsManager = callsManager;
-        Resources resources = mContext.getResources();
+        mSystemStateProvider = systemStateProvider;
 
+        Resources resources = mContext.getResources();
         mSystemInCallComponentName = new ComponentName(
                 resources.getString(R.string.ui_default_package),
                 resources.getString(R.string.incall_default_class));
+
+        mSystemStateProvider.addListener(mSystemStateListener);
     }
 
     @Override
@@ -181,8 +202,8 @@
             for (Map.Entry<ComponentName, IInCallService> entry : mInCallServices.entrySet()) {
                 ComponentName componentName = entry.getKey();
                 IInCallService inCallService = entry.getValue();
-                ParcelableCall parcelableCall = toParcelableCall(call,
-                        true /* includeVideoProvider */);
+                ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(call,
+                        true /* includeVideoProvider */, mCallsManager.getPhoneAccountRegistrar());
                 try {
                     inCallService.addCall(parcelableCall);
                 } catch (RemoteException ignored) {
@@ -199,9 +220,9 @@
              *  give them enough time to process all the pending messages.
              */
             Handler handler = new Handler(Looper.getMainLooper());
-            final Runnable runnableUnbind = new Runnable() {
+            handler.postDelayed(new Runnable("ICC.oCR") {
                 @Override
-                public void run() {
+                public void loggedRun() {
                     synchronized (mLock) {
                         // Check again to make sure there are no active calls.
                         if (mCallsManager.getCalls().isEmpty()) {
@@ -209,10 +230,7 @@
                         }
                     }
                 }
-            };
-            handler.postDelayed(
-                    runnableUnbind,
-                    Timeouts.getCallRemoveUnbindInCallServicesDelay(
+            }.prepare(), Timeouts.getCallRemoveUnbindInCallServicesDelay(
                             mContext.getContentResolver()));
         }
         call.removeListener(mCallListener);
@@ -291,6 +309,17 @@
         }
     }
 
+    void silenceRinger() {
+        if (!mInCallServices.isEmpty()) {
+            for (IInCallService inCallService : mInCallServices.values()) {
+                try {
+                    inCallService.silenceRinger();
+                } catch (RemoteException ignored) {
+                }
+            }
+        }
+    }
+
     /**
      * Unbinds an existing bound connection to the in-call app.
      */
@@ -317,82 +346,80 @@
      * @param call The newly added call that triggered the binding to the in-call services.
      */
     private void bindToServices(Call call) {
+        ComponentName inCallUIService = null;
+        ComponentName carModeInCallUIService = null;
+        List<ComponentName> nonUIInCallServices = new LinkedList<>();
+
+        // Loop through all the InCallService implementations that exist in the devices;
         PackageManager packageManager = mContext.getPackageManager();
         Intent serviceIntent = new Intent(InCallService.SERVICE_INTERFACE);
-
-        List<ComponentName> inCallControlServices = new ArrayList<>();
-        ComponentName inCallUIService = null;
-
         for (ResolveInfo entry :
                 packageManager.queryIntentServices(serviceIntent, PackageManager.GET_META_DATA)) {
             ServiceInfo serviceInfo = entry.serviceInfo;
             if (serviceInfo != null) {
-                boolean hasServiceBindPermission = serviceInfo.permission != null &&
-                        serviceInfo.permission.equals(
-                                Manifest.permission.BIND_INCALL_SERVICE);
-                if (!hasServiceBindPermission) {
-                    Log.w(this, "InCallService does not have BIND_INCALL_SERVICE permission: " +
-                            serviceInfo.packageName);
-                    continue;
-                }
+                ComponentName componentName =
+                        new ComponentName(serviceInfo.packageName, serviceInfo.name);
 
-                boolean hasControlInCallPermission = packageManager.checkPermission(
-                        Manifest.permission.CONTROL_INCALL_EXPERIENCE,
-                        serviceInfo.packageName) == PackageManager.PERMISSION_GRANTED;
-                boolean isDefaultDialerPackage = Objects.equals(serviceInfo.packageName,
-                        DefaultDialerManager.getDefaultDialerApplication(mContext));
-                if (!hasControlInCallPermission && !isDefaultDialerPackage) {
-                    Log.w(this, "Service does not have CONTROL_INCALL_EXPERIENCE permission: %s"
-                            + " and is not system or default dialer.", serviceInfo.packageName);
-                    continue;
-                }
+                switch (getInCallServiceType(entry.serviceInfo, packageManager)) {
+                    case IN_CALL_SERVICE_TYPE_DIALER_UI:
+                        if (inCallUIService == null ||
+                                inCallUIService.compareTo(componentName) > 0) {
+                            inCallUIService = componentName;
+                        }
+                        break;
 
-                boolean isUIService = serviceInfo.metaData != null &&
-                        serviceInfo.metaData.getBoolean(
-                                TelecomManager.METADATA_IN_CALL_SERVICE_UI, false);
-                ComponentName componentName = new ComponentName(serviceInfo.packageName,
-                        serviceInfo.name);
-                if (isUIService) {
-                    // For the main UI service, we always prefer the default dialer.
-                    if (isDefaultDialerPackage) {
-                        inCallUIService = componentName;
-                        Log.i(this, "Found default-dialer's In-Call UI: %s", componentName);
-                    }
-                } else {
-                    // for non-UI services that have passed our checks, add them to the list of
-                    // service to bind to.
-                    inCallControlServices.add(componentName);
-                }
+                    case IN_CALL_SERVICE_TYPE_SYSTEM_UI:
+                        // skip, will be added manually
+                        break;
 
+                    case IN_CALL_SERVICE_TYPE_CAR_MODE_UI:
+                        if (carModeInCallUIService == null ||
+                                carModeInCallUIService.compareTo(componentName) > 0) {
+                            carModeInCallUIService = componentName;
+                        }
+                        break;
+
+                    case IN_CALL_SERVICE_TYPE_NON_UI:
+                        nonUIInCallServices.add(componentName);
+                        break;
+
+                    case IN_CALL_SERVICE_TYPE_INVALID:
+                        break;
+
+                    default:
+                        Log.w(this, "unexpected in-call service type");
+                        break;
+                }
             }
         }
 
-        // Attempt to bind to the default-dialer InCallService first.
-        if (inCallUIService != null) {
-            // skip default dialer if we have an emergency call or if it failed binding.
-            if (mCallsManager.hasEmergencyCall()) {
-                Log.i(this, "Skipping default-dialer because of emergency call");
-                inCallUIService = null;
-            } else if (!bindToInCallService(inCallUIService, call, "def-dialer")) {
-                Log.event(call, Log.Events.ERROR_LOG,
-                        "InCallService UI failed binding: " + inCallUIService);
-                inCallUIService = null;
-            }
-        }
+        Log.i(this, "Car mode InCallService: %s", carModeInCallUIService);
+        Log.i(this, "Dialer InCallService: %s", inCallUIService);
 
-        if (inCallUIService == null) {
-            // We failed to connect to the default-dialer service, or none was provided. Switch to
-            // the system built-in InCallService UI.
-            inCallUIService = mSystemInCallComponentName;
-            if (!bindToInCallService(inCallUIService, call, "system")) {
-                Log.event(call, Log.Events.ERROR_LOG,
-                        "InCallService system UI failed binding: " + inCallUIService);
-            }
+        // Adding the in-call services in order:
+        // (1) The carmode in-call if carmode is on.
+        // (2) The default-dialer in-call if not an emergency call
+        // (3) The system-provided in-call
+        List<ComponentName> orderedInCallUIServices = new LinkedList<>();
+        if (shouldUseCarModeUI() && carModeInCallUIService != null) {
+            orderedInCallUIServices.add(carModeInCallUIService);
         }
-        mInCallUIComponentName = inCallUIService;
+        if (!mCallsManager.hasEmergencyCall() && inCallUIService != null) {
+            orderedInCallUIServices.add(inCallUIService);
+        }
+        orderedInCallUIServices.add(mSystemInCallComponentName);
+
+        // TODO: Need to implement the fall-back logic in case the main UI in-call service rejects
+        // the binding request.
+        ComponentName inCallUIServiceToBind = orderedInCallUIServices.get(0);
+        if (!bindToInCallService(inCallUIServiceToBind, call, "ui")) {
+            Log.event(call, Log.Events.ERROR_LOG,
+                    "InCallService system UI failed binding: " + inCallUIService);
+        }
+        mInCallUIComponentName = inCallUIServiceToBind;
 
         // Bind to the control InCallServices
-        for (ComponentName componentName : inCallControlServices) {
+        for (ComponentName componentName : nonUIInCallServices) {
             bindToInCallService(componentName, call, "control");
         }
     }
@@ -432,6 +459,68 @@
         return false;
     }
 
+    private boolean shouldUseCarModeUI() {
+        return mSystemStateProvider.isCarMode();
+    }
+
+    /**
+     * Returns the type of InCallService described by the specified serviceInfo.
+     */
+    private int getInCallServiceType(ServiceInfo serviceInfo, PackageManager packageManager) {
+        // Verify that the InCallService requires the BIND_INCALL_SERVICE permission which
+        // enforces that only Telecom can bind to it.
+        boolean hasServiceBindPermission = serviceInfo.permission != null &&
+                serviceInfo.permission.equals(
+                        Manifest.permission.BIND_INCALL_SERVICE);
+        if (!hasServiceBindPermission) {
+            Log.w(this, "InCallService does not require BIND_INCALL_SERVICE permission: " +
+                    serviceInfo.packageName);
+            return IN_CALL_SERVICE_TYPE_INVALID;
+        }
+
+        if (mSystemInCallComponentName.getPackageName().equals(serviceInfo.packageName) &&
+                mSystemInCallComponentName.getClassName().equals(serviceInfo.name)) {
+            return IN_CALL_SERVICE_TYPE_SYSTEM_UI;
+        }
+
+        // Check to see if the service is a car-mode UI type by checking that it has the
+        // CONTROL_INCALL_EXPERIENCE (to verify it is a system app) and that it has the
+        // car-mode UI metadata.
+        boolean hasControlInCallPermission = packageManager.checkPermission(
+                Manifest.permission.CONTROL_INCALL_EXPERIENCE,
+                serviceInfo.packageName) == PackageManager.PERMISSION_GRANTED;
+        boolean isCarModeUIService = serviceInfo.metaData != null &&
+                serviceInfo.metaData.getBoolean(
+                        TelecomManager.METADATA_IN_CALL_SERVICE_CAR_MODE_UI, false) &&
+                hasControlInCallPermission;
+        if (isCarModeUIService) {
+            return IN_CALL_SERVICE_TYPE_CAR_MODE_UI;
+        }
+
+
+        // Check to see that it is the default dialer package
+        boolean isDefaultDialerPackage = Objects.equals(serviceInfo.packageName,
+                DefaultDialerManager.getDefaultDialerApplication(mContext));
+        boolean isUIService = serviceInfo.metaData != null &&
+                serviceInfo.metaData.getBoolean(
+                        TelecomManager.METADATA_IN_CALL_SERVICE_UI, false);
+        if (isDefaultDialerPackage && isUIService) {
+            return IN_CALL_SERVICE_TYPE_DIALER_UI;
+        }
+
+        // Also allow any in-call service that has the control-experience permission (to ensure
+        // that it is a system app) and doesn't claim to show any UI.
+        if (hasControlInCallPermission && !isUIService) {
+            return IN_CALL_SERVICE_TYPE_NON_UI;
+        }
+
+        // Anything else that remains, we will not bind to.
+        Log.i(this, "Skipping binding to %s:%s, control: %b, car-mode: %b, ui: %b",
+                serviceInfo.packageName, serviceInfo.name, hasControlInCallPermission,
+                isCarModeUIService, isUIService);
+        return IN_CALL_SERVICE_TYPE_INVALID;
+    }
+
     private void adjustServiceBindingsForEmergency() {
         if (!Objects.equals(mInCallUIComponentName, mSystemInCallComponentName)) {
             // The connected UI is not the system UI, so lets check if we should switch them
@@ -463,7 +552,8 @@
                     new InCallAdapter(
                             mCallsManager,
                             mCallIdMapper,
-                            mLock));
+                            mLock,
+                            componentName.getPackageName()));
         } catch (RemoteException e) {
             Log.e(this, e, "Failed to set the in-call adapter.");
             Trace.endSection();
@@ -472,7 +562,7 @@
         }
 
         // Upon successful connection, send the state of the world to the service.
-        Collection<Call> calls = mCallsManager.getCalls();
+        List<Call> calls = orderCallsWithChildrenFirst(mCallsManager.getCalls());
         if (!calls.isEmpty()) {
             Log.i(this, "Adding %s calls to InCallService after onConnected: %s", calls.size(),
                     componentName);
@@ -480,14 +570,18 @@
                 try {
                     // Track the call if we don't already know about it.
                     addCall(call);
-                    inCallService.addCall(toParcelableCall(call, true /* includeVideoProvider */));
+                    inCallService.addCall(ParcelableCallUtils.toParcelableCall(
+                            call,
+                            true /* includeVideoProvider */,
+                            mCallsManager.getPhoneAccountRegistrar()));
                 } catch (RemoteException ignored) {
                 }
             }
-            onCallAudioStateChanged(
-                    null,
-                    mCallsManager.getAudioState());
-            onCanAddCallChanged(mCallsManager.canAddCall());
+            try {
+                inCallService.onCallAudioStateChanged(mCallsManager.getAudioState());
+                inCallService.onCanAddCallChanged(mCallsManager.canAddCall());
+            } catch (RemoteException ignored) {
+            }
         } else {
             unbindFromServices();
         }
@@ -557,8 +651,10 @@
      */
     private void updateCall(Call call, boolean videoProviderChanged) {
         if (!mInCallServices.isEmpty()) {
-            ParcelableCall parcelableCall = toParcelableCall(call,
-                    videoProviderChanged /* includeVideoProvider */);
+            ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(
+                    call,
+                    videoProviderChanged /* includeVideoProvider */,
+                    mCallsManager.getPhoneAccountRegistrar());
             Log.i(this, "Sending updateCall %s ==> %s", call, parcelableCall);
             List<ComponentName> componentsUpdated = new ArrayList<>();
             for (Map.Entry<ComponentName, IInCallService> entry : mInCallServices.entrySet()) {
@@ -575,244 +671,6 @@
     }
 
     /**
-     * Parcels all information for a {@link Call} into a new {@link ParcelableCall} instance.
-     *
-     * @param call The {@link Call} to parcel.
-     * @param includeVideoProvider {@code true} if the video provider should be parcelled with the
-     *      {@link Call}, {@code false} otherwise.  Since the {@link ParcelableCall#getVideoCall()}
-     *      method creates a {@link VideoCallImpl} instance on access it is important for the
-     *      recipient of the {@link ParcelableCall} to know if the video provider changed.
-     * @return The {@link ParcelableCall} containing all call information from the {@link Call}.
-     */
-    private ParcelableCall toParcelableCall(Call call, boolean includeVideoProvider) {
-        String callId = mCallIdMapper.getCallId(call);
-
-        int state = getParcelableState(call);
-        int capabilities = convertConnectionToCallCapabilities(call.getConnectionCapabilities());
-        int properties = convertConnectionToCallProperties(call.getConnectionCapabilities());
-        if (call.isConference()) {
-            properties |= android.telecom.Call.Details.PROPERTY_CONFERENCE;
-        }
-
-        // If this is a single-SIM device, the "default SIM" will always be the only SIM.
-        boolean isDefaultSmsAccount =
-                mCallsManager.getPhoneAccountRegistrar()
-                        .isUserSelectedSmsPhoneAccount(call.getTargetPhoneAccount());
-        if (call.isRespondViaSmsCapable() && isDefaultSmsAccount) {
-            capabilities |= android.telecom.Call.Details.CAPABILITY_RESPOND_VIA_TEXT;
-        }
-
-        if (call.isEmergencyCall()) {
-            capabilities = removeCapability(
-                    capabilities, android.telecom.Call.Details.CAPABILITY_MUTE);
-        }
-
-        if (state == android.telecom.Call.STATE_DIALING) {
-            capabilities = removeCapability(capabilities,
-                    android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL);
-            capabilities = removeCapability(capabilities,
-                    android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL);
-        }
-
-        String parentCallId = null;
-        Call parentCall = call.getParentCall();
-        if (parentCall != null) {
-            parentCallId = mCallIdMapper.getCallId(parentCall);
-        }
-
-        long connectTimeMillis = call.getConnectTimeMillis();
-        List<Call> childCalls = call.getChildCalls();
-        List<String> childCallIds = new ArrayList<>();
-        if (!childCalls.isEmpty()) {
-            long childConnectTimeMillis = Long.MAX_VALUE;
-            for (Call child : childCalls) {
-                if (child.getConnectTimeMillis() > 0) {
-                    childConnectTimeMillis = Math.min(child.getConnectTimeMillis(),
-                            childConnectTimeMillis);
-                }
-                childCallIds.add(mCallIdMapper.getCallId(child));
-            }
-
-            if (childConnectTimeMillis != Long.MAX_VALUE) {
-                connectTimeMillis = childConnectTimeMillis;
-            }
-        }
-
-        Uri handle = call.getHandlePresentation() == TelecomManager.PRESENTATION_ALLOWED ?
-                call.getHandle() : null;
-        String callerDisplayName = call.getCallerDisplayNamePresentation() ==
-                TelecomManager.PRESENTATION_ALLOWED ?  call.getCallerDisplayName() : null;
-
-        List<Call> conferenceableCalls = call.getConferenceableCalls();
-        List<String> conferenceableCallIds = new ArrayList<String>(conferenceableCalls.size());
-        for (Call otherCall : conferenceableCalls) {
-            String otherId = mCallIdMapper.getCallId(otherCall);
-            if (otherId != null) {
-                conferenceableCallIds.add(otherId);
-            }
-        }
-
-        return new ParcelableCall(
-                callId,
-                state,
-                call.getDisconnectCause(),
-                call.getCannedSmsResponses(),
-                capabilities,
-                properties,
-                connectTimeMillis,
-                handle,
-                call.getHandlePresentation(),
-                callerDisplayName,
-                call.getCallerDisplayNamePresentation(),
-                call.getGatewayInfo(),
-                call.getTargetPhoneAccount(),
-                includeVideoProvider,
-                includeVideoProvider ? call.getVideoProvider() : null,
-                parentCallId,
-                childCallIds,
-                call.getStatusHints(),
-                call.getVideoState(),
-                conferenceableCallIds,
-                call.getIntentExtras(),
-                call.getExtras());
-    }
-
-    private static int getParcelableState(Call call) {
-        int state = CallState.NEW;
-        switch (call.getState()) {
-            case CallState.ABORTED:
-            case CallState.DISCONNECTED:
-                state = android.telecom.Call.STATE_DISCONNECTED;
-                break;
-            case CallState.ACTIVE:
-                state = android.telecom.Call.STATE_ACTIVE;
-                break;
-            case CallState.CONNECTING:
-                state = android.telecom.Call.STATE_CONNECTING;
-                break;
-            case CallState.DIALING:
-                state = android.telecom.Call.STATE_DIALING;
-                break;
-            case CallState.DISCONNECTING:
-                state = android.telecom.Call.STATE_DISCONNECTING;
-                break;
-            case CallState.NEW:
-                state = android.telecom.Call.STATE_NEW;
-                break;
-            case CallState.ON_HOLD:
-                state = android.telecom.Call.STATE_HOLDING;
-                break;
-            case CallState.RINGING:
-                state = android.telecom.Call.STATE_RINGING;
-                break;
-            case CallState.SELECT_PHONE_ACCOUNT:
-                state = android.telecom.Call.STATE_SELECT_PHONE_ACCOUNT;
-                break;
-        }
-
-        // If we are marked as 'locally disconnecting' then mark ourselves as disconnecting instead.
-        // Unless we're disconnect*ED*, in which case leave it at that.
-        if (call.isLocallyDisconnecting() &&
-                (state != android.telecom.Call.STATE_DISCONNECTED)) {
-            state = android.telecom.Call.STATE_DISCONNECTING;
-        }
-        return state;
-    }
-
-    private static final int[] CONNECTION_TO_CALL_CAPABILITY = new int[] {
-        Connection.CAPABILITY_HOLD,
-        android.telecom.Call.Details.CAPABILITY_HOLD,
-
-        Connection.CAPABILITY_SUPPORT_HOLD,
-        android.telecom.Call.Details.CAPABILITY_SUPPORT_HOLD,
-
-        Connection.CAPABILITY_MERGE_CONFERENCE,
-        android.telecom.Call.Details.CAPABILITY_MERGE_CONFERENCE,
-
-        Connection.CAPABILITY_SWAP_CONFERENCE,
-        android.telecom.Call.Details.CAPABILITY_SWAP_CONFERENCE,
-
-        Connection.CAPABILITY_RESPOND_VIA_TEXT,
-        android.telecom.Call.Details.CAPABILITY_RESPOND_VIA_TEXT,
-
-        Connection.CAPABILITY_MUTE,
-        android.telecom.Call.Details.CAPABILITY_MUTE,
-
-        Connection.CAPABILITY_MANAGE_CONFERENCE,
-        android.telecom.Call.Details.CAPABILITY_MANAGE_CONFERENCE,
-
-        Connection.CAPABILITY_SUPPORTS_VT_LOCAL_RX,
-        android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_LOCAL_RX,
-
-        Connection.CAPABILITY_SUPPORTS_VT_LOCAL_TX,
-        android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_LOCAL_TX,
-
-        Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL,
-        android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL,
-
-        Connection.CAPABILITY_SUPPORTS_VT_REMOTE_RX,
-        android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_REMOTE_RX,
-
-        Connection.CAPABILITY_SUPPORTS_VT_REMOTE_TX,
-        android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_REMOTE_TX,
-
-        Connection.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL,
-        android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL,
-
-        Connection.CAPABILITY_SEPARATE_FROM_CONFERENCE,
-        android.telecom.Call.Details.CAPABILITY_SEPARATE_FROM_CONFERENCE,
-
-        Connection.CAPABILITY_DISCONNECT_FROM_CONFERENCE,
-        android.telecom.Call.Details.CAPABILITY_DISCONNECT_FROM_CONFERENCE,
-
-        Connection.CAPABILITY_CAN_UPGRADE_TO_VIDEO,
-        android.telecom.Call.Details.CAPABILITY_CAN_UPGRADE_TO_VIDEO,
-
-        Connection.CAPABILITY_CAN_PAUSE_VIDEO,
-        android.telecom.Call.Details.CAPABILITY_CAN_PAUSE_VIDEO,
-
-        Connection.CAPABILITY_CAN_SEND_RESPONSE_VIA_CONNECTION,
-        android.telecom.Call.Details.CAPABILITY_CAN_SEND_RESPONSE_VIA_CONNECTION,
-
-        Connection.CAPABILITY_CANNOT_DOWNGRADE_VIDEO_TO_AUDIO,
-        android.telecom.Call.Details.CAPABILITY_CANNOT_DOWNGRADE_VIDEO_TO_AUDIO
-    };
-
-    private static int convertConnectionToCallCapabilities(int connectionCapabilities) {
-        int callCapabilities = 0;
-        for (int i = 0; i < CONNECTION_TO_CALL_CAPABILITY.length; i += 2) {
-            if ((CONNECTION_TO_CALL_CAPABILITY[i] & connectionCapabilities) != 0) {
-                callCapabilities |= CONNECTION_TO_CALL_CAPABILITY[i + 1];
-            }
-        }
-        return callCapabilities;
-    }
-
-    private static final int[] CONNECTION_TO_CALL_PROPERTIES = new int[] {
-        Connection.CAPABILITY_HIGH_DEF_AUDIO,
-        android.telecom.Call.Details.PROPERTY_HIGH_DEF_AUDIO,
-
-        Connection.CAPABILITY_WIFI,
-        android.telecom.Call.Details.PROPERTY_WIFI,
-
-        Connection.CAPABILITY_GENERIC_CONFERENCE,
-        android.telecom.Call.Details.PROPERTY_GENERIC_CONFERENCE,
-
-        Connection.CAPABILITY_SHOW_CALLBACK_NUMBER,
-        android.telecom.Call.Details.PROPERTY_EMERGENCY_CALLBACK_MODE
-    };
-
-    private static int convertConnectionToCallProperties(int connectionCapabilities) {
-        int callProperties = 0;
-        for (int i = 0; i < CONNECTION_TO_CALL_PROPERTIES.length; i += 2) {
-            if ((CONNECTION_TO_CALL_PROPERTIES[i] & connectionCapabilities) != 0) {
-                callProperties |= CONNECTION_TO_CALL_PROPERTIES[i + 1];
-            }
-        }
-        return callProperties;
-    }
-
-    /**
      * Adds the call to the list of calls tracked by the {@link InCallController}.
      * @param call The call to add.
      */
@@ -828,13 +686,6 @@
     }
 
     /**
-     * Removes the specified capability from the set of capabilities bits and returns the new set.
-     */
-    private static int removeCapability(int capabilities, int capability) {
-        return capabilities & ~capability;
-    }
-
-    /**
      * Dumps the state of the {@link InCallController}.
      *
      * @param pw The {@code IndentingPrintWriter} to write the state to.
@@ -854,4 +705,42 @@
         }
         pw.decreaseIndent();
     }
+
+    static boolean doesDefaultDialerSupportRinging(Context context) {
+        String dialerPackage = DefaultDialerManager
+                .getDefaultDialerApplication(context, UserHandle.USER_CURRENT);
+        if (TextUtils.isEmpty(dialerPackage)) {
+            return false;
+        }
+
+        Intent intent = new Intent(InCallService.SERVICE_INTERFACE)
+            .setPackage(dialerPackage);
+        List<ResolveInfo> entries = context.getPackageManager()
+                .queryIntentServices(intent, PackageManager.GET_META_DATA);
+        if (entries.isEmpty()) {
+            return false;
+        }
+
+        ResolveInfo info = entries.get(0);
+        if (info.serviceInfo == null || info.serviceInfo.metaData == null) {
+            return false;
+        }
+
+        return info.serviceInfo.metaData
+                .getBoolean(TelecomManager.METADATA_IN_CALL_SERVICE_RINGING, false);
+    }
+
+    private List<Call> orderCallsWithChildrenFirst(Collection<Call> calls) {
+        LinkedList<Call> parentCalls = new LinkedList<>();
+        LinkedList<Call> childCalls = new LinkedList<>();
+        for (Call call : calls) {
+            if (call.getChildCalls().size() > 0) {
+                parentCalls.add(call);
+            } else {
+                childCalls.add(call);
+            }
+        }
+        childCalls.addAll(parentCalls);
+        return childCalls;
+    }
 }
diff --git a/src/com/android/server/telecom/InCallToneMonitor.java b/src/com/android/server/telecom/InCallToneMonitor.java
deleted file mode 100644
index afe0f06..0000000
--- a/src/com/android/server/telecom/InCallToneMonitor.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright 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.media.ToneGenerator;
-import android.telecom.Connection;
-import android.telecom.VideoProfile;
-
-import java.util.Collection;
-
-/**
- * Monitors events from CallsManager and plays in-call tones for events which require them, such as
- * different type of call disconnections (busy tone, congestion tone, etc).
- */
-public final class InCallToneMonitor extends CallsManagerListenerBase {
-    private final InCallTonePlayer.Factory mPlayerFactory;
-
-    private final CallsManager mCallsManager;
-
-    InCallToneMonitor(InCallTonePlayer.Factory playerFactory, CallsManager callsManager) {
-        mPlayerFactory = playerFactory;
-        mCallsManager = callsManager;
-    }
-
-    @Override
-    public void onCallStateChanged(Call call, int oldState, int newState) {
-        if (mCallsManager.getForegroundCall() != call) {
-            // We only play tones for foreground calls.
-            return;
-        }
-
-        if (newState == CallState.DISCONNECTED && call.getDisconnectCause() != null) {
-            int toneToPlay = InCallTonePlayer.TONE_INVALID;
-
-            Log.v(this, "Disconnect cause: %s.", call.getDisconnectCause());
-
-            switch(call.getDisconnectCause().getTone()) {
-                case ToneGenerator.TONE_SUP_BUSY:
-                    toneToPlay = InCallTonePlayer.TONE_BUSY;
-                    break;
-                case ToneGenerator.TONE_SUP_CONGESTION:
-                    toneToPlay = InCallTonePlayer.TONE_CONGESTION;
-                    break;
-                case ToneGenerator.TONE_CDMA_REORDER:
-                    toneToPlay = InCallTonePlayer.TONE_REORDER;
-                    break;
-                case ToneGenerator.TONE_CDMA_ABBR_INTERCEPT:
-                    toneToPlay = InCallTonePlayer.TONE_INTERCEPT;
-                    break;
-                case ToneGenerator.TONE_CDMA_CALLDROP_LITE:
-                    toneToPlay = InCallTonePlayer.TONE_CDMA_DROP;
-                    break;
-                case ToneGenerator.TONE_SUP_ERROR:
-                    toneToPlay = InCallTonePlayer.TONE_UNOBTAINABLE_NUMBER;
-                    break;
-                case ToneGenerator.TONE_PROP_PROMPT:
-                    toneToPlay = InCallTonePlayer.TONE_CALL_ENDED;
-                    break;
-            }
-
-            Log.d(this, "Found a disconnected call with tone to play %d.", toneToPlay);
-
-            if (toneToPlay != InCallTonePlayer.TONE_INVALID) {
-                mPlayerFactory.createPlayer(toneToPlay).startTone();
-            }
-        }
-    }
-
-    /**
-     * Handles requests received via the {@link VideoProviderProxy} requesting a change in the video
-     * state of the call by the peer.  If the request involves the peer turning their camera on,
-     * the call waiting tone is played to inform the user of the incoming request.
-     *
-     * @param call The call.
-     * @param videoProfile The requested video profile.
-     */
-    @Override
-    public void onSessionModifyRequestReceived(Call call, VideoProfile videoProfile) {
-        if (videoProfile == null) {
-            return;
-        }
-
-        if (mCallsManager.getForegroundCall() != call) {
-            // We only play tones for foreground calls.
-            return;
-        }
-
-        int previousVideoState = call.getVideoState();
-        int newVideoState = videoProfile.getVideoState();
-        Log.v(this, "onSessionModifyRequestReceived : videoProfile = " + VideoProfile
-                .videoStateToString(newVideoState));
-
-        boolean isUpgradeRequest = !VideoProfile.isReceptionEnabled(previousVideoState) &&
-                VideoProfile.isReceptionEnabled(newVideoState);
-
-        if (isUpgradeRequest) {
-            mPlayerFactory.createPlayer(InCallTonePlayer.TONE_VIDEO_UPGRADE).startTone();
-        }
-    }
-}
diff --git a/src/com/android/server/telecom/InCallTonePlayer.java b/src/com/android/server/telecom/InCallTonePlayer.java
index 0d2e3c4..78c1395 100644
--- a/src/com/android/server/telecom/InCallTonePlayer.java
+++ b/src/com/android/server/telecom/InCallTonePlayer.java
@@ -21,27 +21,36 @@
 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 CallAudioManager mCallAudioManager;
+        private final CallAudioRoutePeripheralAdapter mCallAudioRoutePeripheralAdapter;
         private final TelecomSystem.SyncRoot mLock;
 
-        Factory(CallAudioManager callAudioManager, TelecomSystem.SyncRoot lock) {
-            mCallAudioManager = callAudioManager;
+        Factory(CallAudioRoutePeripheralAdapter callAudioRoutePeripheralAdapter,
+                TelecomSystem.SyncRoot lock) {
+            mCallAudioRoutePeripheralAdapter = callAudioRoutePeripheralAdapter;
             mLock = lock;
         }
 
-        InCallTonePlayer createPlayer(int tone) {
-            return new InCallTonePlayer(tone, mCallAudioManager, mLock);
+        public void setCallAudioManager(CallAudioManager callAudioManager) {
+            mCallAudioManager = callAudioManager;
+        }
+
+        public InCallTonePlayer createPlayer(int tone) {
+            return new InCallTonePlayer(tone, mCallAudioManager,
+                    mCallAudioRoutePeripheralAdapter, mLock);
         }
     }
 
@@ -83,6 +92,7 @@
     private static int sTonesPlaying = 0;
 
     private final CallAudioManager mCallAudioManager;
+    private final CallAudioRoutePeripheralAdapter mCallAudioRoutePeripheralAdapter;
 
     private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
 
@@ -103,10 +113,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 +207,7 @@
             }
 
             int stream = AudioManager.STREAM_VOICE_CALL;
-            if (mCallAudioManager.isBluetoothAudioOn()) {
+            if (mCallAudioRoutePeripheralAdapter.isBluetoothAudioOn()) {
                 stream = AudioManager.STREAM_BLUETOOTH_SCO;
             }
 
@@ -236,8 +248,9 @@
             cleanUpTonePlayer();
         }
     }
-
-    void startTone() {
+    
+    @VisibleForTesting
+    public void startTone() {
         sTonesPlaying++;
         if (sTonesPlaying == 1) {
             mCallAudioManager.setIsTonePlaying(true);
@@ -249,7 +262,8 @@
     /**
      * Stops the tone.
      */
-    void stopTone() {
+    @VisibleForTesting
+    public void stopTone() {
         synchronized (this) {
             if (mState == STATE_ON) {
                 Log.d(this, "Stopping the tone %d.", mToneId);
@@ -261,8 +275,9 @@
 
     private void cleanUpTonePlayer() {
         // Release focus on the main thread.
-        mMainThreadHandler.post(new Runnable() {
-            @Override public void run() {
+        mMainThreadHandler.post(new Runnable("ICTP.cUTP") {
+            @Override
+            public void loggedRun() {
                 synchronized (mLock) {
                     if (sTonesPlaying == 0) {
                         Log.wtf(this, "Over-releasing focus for tone player.");
@@ -271,6 +286,6 @@
                     }
                 }
             }
-        });
+        }.prepare());
     }
 }
diff --git a/src/com/android/server/telecom/InCallWakeLockController.java b/src/com/android/server/telecom/InCallWakeLockController.java
index a6c63c3..ffa6a3f 100644
--- a/src/com/android/server/telecom/InCallWakeLockController.java
+++ b/src/com/android/server/telecom/InCallWakeLockController.java
@@ -18,31 +18,21 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 
-import android.content.Context;
-import android.os.PowerManager;
-
 /**
  * Handles acquisition and release of wake locks relating to call state.
  */
 @VisibleForTesting
 public class InCallWakeLockController extends CallsManagerListenerBase {
 
-    private static final String TAG = "InCallWakeLockContoller";
-
-    private final Context mContext;
-    private final PowerManager.WakeLock mFullWakeLock;
+    private final TelecomWakeLock mTelecomWakeLock;
     private final CallsManager mCallsManager;
 
     @VisibleForTesting
-    public InCallWakeLockController(Context context, CallsManager callsManager) {
-        mContext = context;
+    public InCallWakeLockController(TelecomWakeLock telecomWakeLock, CallsManager callsManager) {
         mCallsManager = callsManager;
 
-        PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
-        mFullWakeLock = powerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, TAG);
-        mFullWakeLock.setReferenceCounted(false);
-
-        callsManager.addListener(this);
+        mTelecomWakeLock = telecomWakeLock;
+        mTelecomWakeLock.setReferenceCounted(false);
     }
 
     @Override
@@ -64,10 +54,10 @@
         // We grab a full lock as long as there exists a ringing call.
         Call ringingCall = mCallsManager.getRingingCall();
         if (ringingCall != null) {
-            mFullWakeLock.acquire();
+            mTelecomWakeLock.acquire();
             Log.i(this, "Acquiring full wake lock");
-        } else if (mFullWakeLock.isHeld()) {
-            mFullWakeLock.release();
+        } else {
+            mTelecomWakeLock.release(0);
             Log.i(this, "Releasing full wake lock");
         }
     }
diff --git a/src/com/android/server/telecom/Log.java b/src/com/android/server/telecom/Log.java
index f0ee3fe..5e2dc9d 100644
--- a/src/com/android/server/telecom/Log.java
+++ b/src/com/android/server/telecom/Log.java
@@ -16,25 +16,34 @@
 
 package com.android.server.telecom;
 
+import android.content.Context;
 import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.AsyncTask;
 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;
 
 /**
@@ -68,6 +77,7 @@
         public static final String STOP_DTMF = "STOP_DTMF";
         public static final String START_RINGER = "START_RINGER";
         public static final String STOP_RINGER = "STOP_RINGER";
+        public static final String SKIP_RINGING = "SKIP_RINGING";
         public static final String START_CALL_WAITING_TONE = "START_CALL_WAITING_TONE";
         public static final String STOP_CALL_WAITING_TONE = "STOP_CALL_WAITING_TONE";
         public static final String START_CONNECTION = "START_CONNECTION";
@@ -82,6 +92,15 @@
         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";
+        public static final String BIND_SCREENING = "BIND_SCREENING";
+        public static final String SCREENING_BOUND = "SCREENING_BOUND";
+        public static final String SCREENING_SENT = "SCREENING_SENT";
+        public static final String SCREENING_TIMED_OUT = "SCREENING_TIMED_OUT";
+        public static final String BLOCK_CHECK_INITIATED = "BLOCK_CHECK_INITIATED";
+        public static final String BLOCK_CHECK_TIMED_OUT = "BLOCK_CHECK_TIMED_OUT";
+        public static final String BLOCK_CHECK_FINISHED = "BLOCK_CHECK_FINISHED";
 
         /**
          * Maps from a request to a response.  The same event could be listed as the
@@ -98,16 +117,20 @@
                     put(REQUEST_UNHOLD, SET_ACTIVE);
                     put(START_CONNECTION, SET_DIALING);
                     put(BIND_CS, CS_BOUND);
+                    put(SCREENING_SENT, SCREENING_TIMED_OUT);
+                    put(BLOCK_CHECK_INITIATED, BLOCK_CHECK_TIMED_OUT);
                 }};
     }
 
     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;
         }
@@ -118,27 +141,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("]");
@@ -174,7 +195,7 @@
                         // ID instead.
                         CallEventRecord record = mCallEventRecordMap.get(data);
                         if (record != null) {
-                            data = "Call " + record.mId;
+                            data = "Call " + record.mCall.getId();
                         }
                     }
 
@@ -192,6 +213,8 @@
                     pw.print(event.time - requestEvent.time);
                     pw.print(" ms");
                 }
+                pw.print(":");
+                pw.print(event.sessionId);
                 pw.println();
             }
             pw.decreaseIndent();
@@ -199,10 +222,19 @@
     }
 
     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
+
+    // Don't check in with this true!
+    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 */
@@ -213,43 +245,405 @@
     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("L.cSS") {
+        @Override
+        public void loggedRun() {
+            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;
+        }
+    }
+
+    public static final long DEFAULT_SESSION_TIMEOUT_MS = 30000L; // 30 seconds
+    private static MessageDigest sMessageDigest;
+
+    public static void initMd5Sum() {
+        new AsyncTask<Void, Void, Void>() {
+            @Override
+            public Void doInBackground(Void... args) {
+                MessageDigest md;
+                try {
+                    md = MessageDigest.getInstance("SHA-1");
+                } catch (NoSuchAlgorithmException e) {
+                    md = null;
+                }
+                sMessageDigest = md;
+                return null;
+            }
+        }.execute();
+    }
 
     @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() {
+                    // mContext will be null if Log is called from another process
+                    // (UserCallActivity, for example). For these cases, use the default value.
+                    if(mContext == null) {
+                        return DEFAULT_SESSION_TIMEOUT_MS;
+                    }
+                    return Timeouts.getStaleSessionCleanupTimeoutMillis(
+                            mContext.getContentResolver());
+                }
+            };
+
+    private static long getSessionCleanupTimeoutMs() {
+        return sSessionCleanupTimeoutMs.get();
+    }
+
+    private static synchronized void resetStaleSessionTimer() {
+        sSessionCleanupHandler.removeCallbacksAndMessages(null);
+        // Will be null in Log Testing
+        if (sCleanStaleSessions != null) {
+            sSessionCleanupHandler.postDelayed(sCleanStaleSessions.prepare(),
+                    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) {
+        startSession(shortMethodName, null);
+    }
+    public static synchronized void startSession(String shortMethodName,
+            String callerIdentification) {
+        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) {
+            Session childSession = createSubsession(true);
+            continueSession(childSession, shortMethodName);
+            return;
+        }
+        Session newSession = new Session(getNextSessionID(), shortMethodName,
+                System.currentTimeMillis(), threadId, false, callerIdentification);
+        sSessionMapper.put(threadId, newSession);
+
+        Log.v(LOGGING_TAG, Session.START_SESSION);
+    }
+
+
+    /**
+     * 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 Session createSubsession() {
+        return createSubsession(false);
+    }
+
+    private static synchronized Session createSubsession(boolean isStartedFromActiveSession) {
+        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,
+                isStartedFromActiveSession, null);
+        threadSession.addChild(newSubsession);
+        newSubsession.setParentSession(threadSession);
+
+        if(!isStartedFromActiveSession) {
+            Log.v(LOGGING_TAG, Session.CREATE_SUBSESSION + " " + newSubsession.toString());
+        } else {
+            Log.v(LOGGING_TAG, Session.CREATE_SUBSESSION + " (Invisible subsession)");
+        }
+        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 parentSession = subsession.getParentSession();
+        if (parentSession == null) {
+            Log.d(LOGGING_TAG, "Log.continueSession was called with no session active for " +
+                    "method %s.", shortMethodName);
+            return;
+        }
+
+        sSessionMapper.put(getCallingThreadId(), subsession);
+        if(!subsession.isStartedFromActiveSession()) {
+            Log.v(LOGGING_TAG, Session.CONTINUE_SUBSESSION);
+        } else {
+            Log.v(LOGGING_TAG, Session.CONTINUE_SUBSESSION + " (Invisible Subsession) with " +
+                    "Method " + shortMethodName);
+        }
+    }
+
+    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());
+        if(!completedSession.isStartedFromActiveSession()) {
+            Log.v(LOGGING_TAG, Session.END_SUBSESSION + " (dur: " +
+                    completedSession.getLocalExecutionTime() + " mS)");
+        } else {
+            Log.v(LOGGING_TAG, Session.END_SUBSESSION + " (Invisible 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.isStartedFromActiveSession()) {
+            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.v(LOGGING_TAG, Session.END_SESSION + " (dur: " + fullSessionTimeMs + " ms): " +
+                    subsession.toString());
+        }
+    }
+
+    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);
+        } else {
+            Log.v(LOGGING_TAG, "No stale logging sessions needed to be cleaned...");
+        }
+    }
+
+    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;
         }
     }
 
@@ -258,83 +652,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) {
@@ -395,15 +801,14 @@
     }
 
     private static String secureHash(byte[] input) {
-        MessageDigest messageDigest;
-        try {
-            messageDigest = MessageDigest.getInstance("SHA-1");
-        } catch (NoSuchAlgorithmException e) {
-            return null;
+        if (sMessageDigest != null) {
+            sMessageDigest.reset();
+            sMessageDigest.update(input);
+            byte[] result = sMessageDigest.digest();
+            return encodeHex(result);
+        } else {
+            return "Uninitialized SHA1";
         }
-        messageDigest.update(input);
-        byte[] result = messageDigest.digest();
-        return encodeHex(result);
     }
 
     private static String encodeHex(byte[] bytes) {
@@ -425,6 +830,16 @@
     }
 
     private static String buildMessage(String prefix, String format, Object... args) {
+        if (LOG_DBG) {
+            checkIsThreadLogged();
+        }
+        // Incorporate thread ID and calling method into prefix
+        String sessionPostfix = "";
+        Session currentSession = sSessionMapper.get(getCallingThreadId());
+        if (currentSession != null) {
+            sessionPostfix = ": " + currentSession.toString();
+        }
+
         String msg;
         try {
             msg = (args == null || args.length == 0) ? format
@@ -434,6 +849,6 @@
                     args.length);
             msg = format + " (An error occurred while formatting the message.)";
         }
-        return String.format(Locale.US, "%s: %s", prefix, msg);
+        return String.format(Locale.US, "%s: %s%s", prefix, msg, sessionPostfix);
     }
 }
diff --git a/src/com/android/server/telecom/MissedCallNotifier.java b/src/com/android/server/telecom/MissedCallNotifier.java
index 5a88c34..1125a8e 100644
--- a/src/com/android/server/telecom/MissedCallNotifier.java
+++ b/src/com/android/server/telecom/MissedCallNotifier.java
@@ -16,18 +16,23 @@
 
 package com.android.server.telecom;
 
+import android.os.UserHandle;
+
 /**
  * Creates a notification for calls that the user missed (neither answered nor rejected).
  */
 public interface MissedCallNotifier extends CallsManager.CallsManagerListener {
 
-    void clearMissedCalls();
+    void clearMissedCalls(UserHandle userHandle);
 
     void showMissedCallNotification(Call call);
 
-    void updateOnStartup(
+    void reloadFromDatabase(
             TelecomSystem.SyncRoot lock,
             CallsManager callsManager,
             ContactsAsyncHelper contactsAsyncHelper,
-            CallerInfoAsyncQueryFactory callerInfoAsyncQueryFactory);
+            CallerInfoAsyncQueryFactory callerInfoAsyncQueryFactory,
+            UserHandle userHandle);
+
+    void setCurrentUserHandle(UserHandle userHandle);
 }
diff --git a/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java b/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
index 17ccdb1..6e8492e 100644
--- a/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
+++ b/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
@@ -31,9 +31,10 @@
 import android.telecom.TelecomManager;
 import android.telecom.VideoProfile;
 import android.telephony.DisconnectCause;
-import android.telephony.PhoneNumberUtils;
 import android.text.TextUtils;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 // TODO: Needed for move to system service: import com.android.internal.R;
 
 /**
@@ -52,7 +53,8 @@
  * Calls to emergency numbers are still broadcast for informative purposes. The call is placed
  * prior to sending ACTION_NEW_OUTGOING_CALL and cannot be redirected nor prevented.
  */
-class NewOutgoingCallIntentBroadcaster {
+@VisibleForTesting
+public class NewOutgoingCallIntentBroadcaster {
     private static final String EXTRA_ACTUAL_NUMBER_TO_DIAL =
             "android.telecom.extra.ACTUAL_NUMBER_TO_DIAL";
 
@@ -72,6 +74,7 @@
     private final Call mCall;
     private final Intent mIntent;
     private final Context mContext;
+    private final PhoneNumberUtilsAdapter mPhoneNumberUtilsAdapter;
 
     /*
      * Whether or not the outgoing call intent originated from the default phone application. If
@@ -79,12 +82,15 @@
      */
     private final boolean mIsDefaultOrSystemPhoneApp;
 
-    NewOutgoingCallIntentBroadcaster(Context context, CallsManager callsManager, Call call,
-            Intent intent, boolean isDefaultPhoneApp) {
+    @VisibleForTesting
+    public NewOutgoingCallIntentBroadcaster(Context context, CallsManager callsManager, Call call,
+            Intent intent, PhoneNumberUtilsAdapter phoneNumberUtilsAdapter,
+            boolean isDefaultPhoneApp) {
         mContext = context;
         mCallsManager = callsManager;
         mCall = call;
         mIntent = intent;
+        mPhoneNumberUtilsAdapter = phoneNumberUtilsAdapter;
         mIsDefaultOrSystemPhoneApp = isDefaultPhoneApp;
     }
 
@@ -92,57 +98,67 @@
      * Processes the result of the outgoing call broadcast intent, and performs callbacks to
      * the OutgoingCallIntentBroadcasterListener as necessary.
      */
-    private class NewOutgoingCallBroadcastIntentReceiver extends BroadcastReceiver {
+    public class NewOutgoingCallBroadcastIntentReceiver extends BroadcastReceiver {
 
         @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 (mPhoneNumberUtilsAdapter.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(
+                        mPhoneNumberUtilsAdapter.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);
+                mCall.setNewOutgoingCallIntentBroadcastIsDone();
+                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();
         }
     }
 
@@ -159,7 +175,8 @@
      * @return {@link DisconnectCause#NOT_DISCONNECTED} if the call succeeded, and an appropriate
      *         {@link DisconnectCause} if the call did not, describing why it failed.
      */
-    int processIntent() {
+    @VisibleForTesting
+    public int processIntent() {
         Log.v(this, "Processing call intent in OutgoingCallIntentBroadcaster.");
 
         Intent intent = mIntent;
@@ -191,16 +208,16 @@
             }
         }
 
-        String number = PhoneNumberUtils.getNumberFromIntent(intent, mContext);
+        String number = mPhoneNumberUtilsAdapter.getNumberFromIntent(intent, mContext);
         if (TextUtils.isEmpty(number)) {
             Log.w(this, "Empty number obtained from the call intent.");
             return DisconnectCause.NO_PHONE_NUMBER_SUPPLIED;
         }
 
-        boolean isUriNumber = PhoneNumberUtils.isUriNumber(number);
+        boolean isUriNumber = mPhoneNumberUtilsAdapter.isUriNumber(number);
         if (!isUriNumber) {
-            number = PhoneNumberUtils.convertKeypadLettersToDigits(number);
-            number = PhoneNumberUtils.stripSeparators(number);
+            number = mPhoneNumberUtilsAdapter.convertKeypadLettersToDigits(number);
+            number = mPhoneNumberUtilsAdapter.stripSeparators(number);
         }
 
         final boolean isPotentialEmergencyNumber = isPotentialEmergencyNumber(number);
@@ -253,8 +270,9 @@
             // initiate the call again because of the presence of the EXTRA_ALREADY_CALLED extra.
         }
 
-        Log.i(this, "Sending NewOutgoingCallBroadcast for %s", mCall);
-        broadcastIntent(intent, number, !callImmediately);
+        UserHandle targetUser = mCall.getInitiatingUser();
+        Log.i(this, "Sending NewOutgoingCallBroadcast for %s to %s", mCall, targetUser);
+        broadcastIntent(intent, number, !callImmediately, targetUser);
         return DisconnectCause.NOT_DISCONNECTED;
     }
 
@@ -265,12 +283,14 @@
      * @param originalCallIntent The original call intent.
      * @param number Call number that was stored in the original call intent.
      * @param receiverRequired Whether or not the result from the ordered broadcast should be
-     *     processed using a {@link NewOutgoingCallIntentBroadcaster}.
+     *                         processed using a {@link NewOutgoingCallIntentBroadcaster}.
+     * @param targetUser User that the broadcast sent to.
      */
     private void broadcastIntent(
             Intent originalCallIntent,
             String number,
-            boolean receiverRequired) {
+            boolean receiverRequired,
+            UserHandle targetUser) {
         Intent broadcastIntent = new Intent(Intent.ACTION_NEW_OUTGOING_CALL);
         if (number != null) {
             broadcastIntent.putExtra(Intent.EXTRA_PHONE_NUMBER, number);
@@ -285,7 +305,7 @@
 
         mContext.sendOrderedBroadcastAsUser(
                 broadcastIntent,
-                UserHandle.CURRENT,
+                targetUser,
                 android.Manifest.permission.PROCESS_OUTGOING_CALLS,
                 AppOpsManager.OP_PROCESS_OUTGOING_CALLS,
                 receiverRequired ? new NewOutgoingCallBroadcastIntentReceiver() : null,
@@ -387,8 +407,8 @@
      */
     private boolean isPotentialEmergencyNumber(String number) {
         Log.v(this, "Checking restrictions for number : %s", Log.pii(number));
-        return (number != null) && PhoneNumberUtils.isPotentialLocalEmergencyNumber(mContext,
-                number);
+        return (number != null)
+                && mPhoneNumberUtilsAdapter.isPotentialLocalEmergencyNumber(mContext, number);
     }
 
     /**
diff --git a/src/com/android/server/telecom/ParcelableCallUtils.java b/src/com/android/server/telecom/ParcelableCallUtils.java
new file mode 100644
index 0000000..ae8e425
--- /dev/null
+++ b/src/com/android/server/telecom/ParcelableCallUtils.java
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2016 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.net.Uri;
+import android.telecom.Connection;
+import android.telecom.ParcelableCall;
+import android.telecom.TelecomManager;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Utilities dealing with {@link ParcelableCall}.
+ */
+public class ParcelableCallUtils {
+    /**
+     * Parcels all information for a {@link Call} into a new {@link ParcelableCall} instance.
+     *
+     * @param call The {@link Call} to parcel.
+     * @param includeVideoProvider {@code true} if the video provider should be parcelled with the
+     *      {@link Call}, {@code false} otherwise.  Since the {@link ParcelableCall#getVideoCall()}
+     *      method creates a {@link VideoCallImpl} instance on access it is important for the
+     *      recipient of the {@link ParcelableCall} to know if the video provider changed.
+     * @param phoneAccountRegistrar The {@link PhoneAccountRegistrar}.
+     * @return The {@link ParcelableCall} containing all call information from the {@link Call}.
+     */
+    public static ParcelableCall toParcelableCall(
+            Call call,
+            boolean includeVideoProvider,
+            PhoneAccountRegistrar phoneAccountRegistrar) {
+        int state = getParcelableState(call);
+        int capabilities = convertConnectionToCallCapabilities(call.getConnectionCapabilities());
+        int properties = convertConnectionToCallProperties(call.getConnectionCapabilities());
+        if (call.isConference()) {
+            properties |= android.telecom.Call.Details.PROPERTY_CONFERENCE;
+        }
+
+        if (call.isWorkCall()) {
+            properties |= android.telecom.Call.Details.PROPERTY_WORK_CALL;
+        }
+
+        // If this is a single-SIM device, the "default SIM" will always be the only SIM.
+        boolean isDefaultSmsAccount =
+                phoneAccountRegistrar.isUserSelectedSmsPhoneAccount(call.getTargetPhoneAccount());
+        if (call.isRespondViaSmsCapable() && isDefaultSmsAccount) {
+            capabilities |= android.telecom.Call.Details.CAPABILITY_RESPOND_VIA_TEXT;
+        }
+
+        if (call.isEmergencyCall()) {
+            capabilities = removeCapability(
+                    capabilities, android.telecom.Call.Details.CAPABILITY_MUTE);
+        }
+
+        if (state == android.telecom.Call.STATE_DIALING) {
+            capabilities = removeCapability(capabilities,
+                    android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL);
+            capabilities = removeCapability(capabilities,
+                    android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL);
+        }
+
+        String parentCallId = null;
+        Call parentCall = call.getParentCall();
+        if (parentCall != null) {
+            parentCallId = parentCall.getId();
+        }
+
+        long connectTimeMillis = call.getConnectTimeMillis();
+        List<Call> childCalls = call.getChildCalls();
+        List<String> childCallIds = new ArrayList<>();
+        if (!childCalls.isEmpty()) {
+            long childConnectTimeMillis = Long.MAX_VALUE;
+            for (Call child : childCalls) {
+                if (child.getConnectTimeMillis() > 0) {
+                    childConnectTimeMillis = Math.min(child.getConnectTimeMillis(),
+                            childConnectTimeMillis);
+                }
+                childCallIds.add(child.getId());
+            }
+
+            if (childConnectTimeMillis != Long.MAX_VALUE) {
+                connectTimeMillis = childConnectTimeMillis;
+            }
+        }
+
+        Uri handle = call.getHandlePresentation() == TelecomManager.PRESENTATION_ALLOWED ?
+                call.getHandle() : null;
+        String callerDisplayName = call.getCallerDisplayNamePresentation() ==
+                TelecomManager.PRESENTATION_ALLOWED ?  call.getCallerDisplayName() : null;
+
+        List<Call> conferenceableCalls = call.getConferenceableCalls();
+        List<String> conferenceableCallIds = new ArrayList<String>(conferenceableCalls.size());
+        for (Call otherCall : conferenceableCalls) {
+            conferenceableCallIds.add(otherCall.getId());
+        }
+
+        return new ParcelableCall(
+                call.getId(),
+                state,
+                call.getDisconnectCause(),
+                call.getCannedSmsResponses(),
+                capabilities,
+                properties,
+                connectTimeMillis,
+                handle,
+                call.getHandlePresentation(),
+                callerDisplayName,
+                call.getCallerDisplayNamePresentation(),
+                call.getGatewayInfo(),
+                call.getTargetPhoneAccount(),
+                includeVideoProvider,
+                includeVideoProvider ? call.getVideoProvider() : null,
+                parentCallId,
+                childCallIds,
+                call.getStatusHints(),
+                call.getVideoState(),
+                conferenceableCallIds,
+                call.getIntentExtras(),
+                call.getExtras());
+    }
+
+    private static int getParcelableState(Call call) {
+        int state = CallState.NEW;
+        switch (call.getState()) {
+            case CallState.ABORTED:
+            case CallState.DISCONNECTED:
+                state = android.telecom.Call.STATE_DISCONNECTED;
+                break;
+            case CallState.ACTIVE:
+                state = android.telecom.Call.STATE_ACTIVE;
+                break;
+            case CallState.CONNECTING:
+                state = android.telecom.Call.STATE_CONNECTING;
+                break;
+            case CallState.DIALING:
+                state = android.telecom.Call.STATE_DIALING;
+                break;
+            case CallState.DISCONNECTING:
+                state = android.telecom.Call.STATE_DISCONNECTING;
+                break;
+            case CallState.NEW:
+                state = android.telecom.Call.STATE_NEW;
+                break;
+            case CallState.ON_HOLD:
+                state = android.telecom.Call.STATE_HOLDING;
+                break;
+            case CallState.RINGING:
+                state = android.telecom.Call.STATE_RINGING;
+                break;
+            case CallState.SELECT_PHONE_ACCOUNT:
+                state = android.telecom.Call.STATE_SELECT_PHONE_ACCOUNT;
+                break;
+        }
+
+        // If we are marked as 'locally disconnecting' then mark ourselves as disconnecting instead.
+        // Unless we're disconnect*ED*, in which case leave it at that.
+        if (call.isLocallyDisconnecting() &&
+                (state != android.telecom.Call.STATE_DISCONNECTED)) {
+            state = android.telecom.Call.STATE_DISCONNECTING;
+        }
+        return state;
+    }
+
+    private static final int[] CONNECTION_TO_CALL_CAPABILITY = new int[] {
+        Connection.CAPABILITY_HOLD,
+        android.telecom.Call.Details.CAPABILITY_HOLD,
+
+        Connection.CAPABILITY_SUPPORT_HOLD,
+        android.telecom.Call.Details.CAPABILITY_SUPPORT_HOLD,
+
+        Connection.CAPABILITY_MERGE_CONFERENCE,
+        android.telecom.Call.Details.CAPABILITY_MERGE_CONFERENCE,
+
+        Connection.CAPABILITY_SWAP_CONFERENCE,
+        android.telecom.Call.Details.CAPABILITY_SWAP_CONFERENCE,
+
+        Connection.CAPABILITY_RESPOND_VIA_TEXT,
+        android.telecom.Call.Details.CAPABILITY_RESPOND_VIA_TEXT,
+
+        Connection.CAPABILITY_MUTE,
+        android.telecom.Call.Details.CAPABILITY_MUTE,
+
+        Connection.CAPABILITY_MANAGE_CONFERENCE,
+        android.telecom.Call.Details.CAPABILITY_MANAGE_CONFERENCE,
+
+        Connection.CAPABILITY_SUPPORTS_VT_LOCAL_RX,
+        android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_LOCAL_RX,
+
+        Connection.CAPABILITY_SUPPORTS_VT_LOCAL_TX,
+        android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_LOCAL_TX,
+
+        Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL,
+        android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL,
+
+        Connection.CAPABILITY_SUPPORTS_VT_REMOTE_RX,
+        android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_REMOTE_RX,
+
+        Connection.CAPABILITY_SUPPORTS_VT_REMOTE_TX,
+        android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_REMOTE_TX,
+
+        Connection.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL,
+        android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL,
+
+        Connection.CAPABILITY_SEPARATE_FROM_CONFERENCE,
+        android.telecom.Call.Details.CAPABILITY_SEPARATE_FROM_CONFERENCE,
+
+        Connection.CAPABILITY_DISCONNECT_FROM_CONFERENCE,
+        android.telecom.Call.Details.CAPABILITY_DISCONNECT_FROM_CONFERENCE,
+
+        Connection.CAPABILITY_CAN_UPGRADE_TO_VIDEO,
+        android.telecom.Call.Details.CAPABILITY_CAN_UPGRADE_TO_VIDEO,
+
+        Connection.CAPABILITY_CAN_PAUSE_VIDEO,
+        android.telecom.Call.Details.CAPABILITY_CAN_PAUSE_VIDEO,
+
+        Connection.CAPABILITY_CAN_SEND_RESPONSE_VIA_CONNECTION,
+        android.telecom.Call.Details.CAPABILITY_CAN_SEND_RESPONSE_VIA_CONNECTION,
+
+        Connection.CAPABILITY_CANNOT_DOWNGRADE_VIDEO_TO_AUDIO,
+        android.telecom.Call.Details.CAPABILITY_CANNOT_DOWNGRADE_VIDEO_TO_AUDIO
+    };
+
+    private static int convertConnectionToCallCapabilities(int connectionCapabilities) {
+        int callCapabilities = 0;
+        for (int i = 0; i < CONNECTION_TO_CALL_CAPABILITY.length; i += 2) {
+            if ((CONNECTION_TO_CALL_CAPABILITY[i] & connectionCapabilities) ==
+                    CONNECTION_TO_CALL_CAPABILITY[i]) {
+
+                callCapabilities |= CONNECTION_TO_CALL_CAPABILITY[i + 1];
+            }
+        }
+        return callCapabilities;
+    }
+
+    private static final int[] CONNECTION_TO_CALL_PROPERTIES = new int[] {
+        Connection.CAPABILITY_HIGH_DEF_AUDIO,
+        android.telecom.Call.Details.PROPERTY_HIGH_DEF_AUDIO,
+
+        Connection.CAPABILITY_WIFI,
+        android.telecom.Call.Details.PROPERTY_WIFI,
+
+        Connection.CAPABILITY_GENERIC_CONFERENCE,
+        android.telecom.Call.Details.PROPERTY_GENERIC_CONFERENCE,
+
+        Connection.CAPABILITY_SHOW_CALLBACK_NUMBER,
+        android.telecom.Call.Details.PROPERTY_EMERGENCY_CALLBACK_MODE
+    };
+
+    private static int convertConnectionToCallProperties(int connectionCapabilities) {
+        int callProperties = 0;
+        for (int i = 0; i < CONNECTION_TO_CALL_PROPERTIES.length; i += 2) {
+            if ((CONNECTION_TO_CALL_PROPERTIES[i] & connectionCapabilities) ==
+                    CONNECTION_TO_CALL_PROPERTIES[i]) {
+
+                callProperties |= CONNECTION_TO_CALL_PROPERTIES[i + 1];
+            }
+        }
+        return callProperties;
+    }
+
+    /**
+     * Removes the specified capability from the set of capabilities bits and returns the new set.
+     */
+    private static int removeCapability(int capabilities, int capability) {
+        return capabilities & ~capability;
+    }
+
+    private ParcelableCallUtils() {}
+}
diff --git a/src/com/android/server/telecom/PhoneAccountRegistrar.java b/src/com/android/server/telecom/PhoneAccountRegistrar.java
index 28ede77..19c1a01 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,8 @@
 import android.graphics.BitmapFactory;
 import android.graphics.drawable.Icon;
 import android.net.Uri;
-import android.os.Binder;
+import android.os.Bundle;
+import android.os.AsyncTask;
 import android.os.PersistableBundle;
 import android.os.Process;
 import android.os.UserHandle;
@@ -74,7 +74,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 +97,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 +119,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";
@@ -133,6 +131,9 @@
     private final SubscriptionManager mSubscriptionManager;
     private State mState;
     private UserHandle mCurrentUserHandle;
+    private interface PhoneAccountRegistrarWriteLock {}
+    private final PhoneAccountRegistrarWriteLock mWriteLock =
+            new PhoneAccountRegistrarWriteLock() {};
 
     @VisibleForTesting
     public PhoneAccountRegistrar(Context context) {
@@ -168,7 +169,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 +187,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 +215,30 @@
         }
     }
 
+    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);
+    @VisibleForTesting
+    public 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 +247,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 +276,8 @@
                 mSubscriptionManager.setDefaultVoiceSubId(subId);
             }
 
-            mState.defaultOutgoing = accountHandle;
+            mState.defaultOutgoingAccountHandles
+                    .put(userHandle, new DefaultPhoneAccountHandle(userHandle, accountHandle));
         }
 
         write();
@@ -266,26 +289,6 @@
                 SubscriptionManager.getDefaultSmsSubscriptionId();
     }
 
-    /**
-     * 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);
-    }
-
     public ComponentName getSystemSimCallManagerComponent() {
         String defaultSimCallManager = null;
         CarrierConfigManager configManager = (CarrierConfigManager) mContext.getSystemService(
@@ -299,6 +302,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 +315,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 +330,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 +358,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 +398,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;
@@ -385,17 +410,38 @@
 
         if (account.isEnabled() != isEnabled) {
             account.setIsEnabled(isEnabled);
+            if (!isEnabled) {
+                // If the disabled account is the default, remove it.
+                removeDefaultPhoneAccountHandle(accountHandle);
+            }
             write();
             fireAccountsChanged();
         }
         return true;
     }
 
-    private boolean isVisibleForUser(PhoneAccount account) {
+    private void removeDefaultPhoneAccountHandle(PhoneAccountHandle phoneAccountHandle) {
+        Iterator<Map.Entry<UserHandle, DefaultPhoneAccountHandle>> iterator =
+                mState.defaultOutgoingAccountHandles.entrySet().iterator();
+        while (iterator.hasNext()) {
+            Map.Entry<UserHandle, DefaultPhoneAccountHandle> entry = iterator.next();
+            if (phoneAccountHandle.equals(entry.getValue().phoneAccountHandle)) {
+                iterator.remove();
+            }
+        }
+    }
+
+    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 +454,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 +495,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 +515,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 +581,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 +601,7 @@
     }
 
     public void unregisterPhoneAccount(PhoneAccountHandle accountHandle) {
-        PhoneAccount account = getPhoneAccount(accountHandle);
+        PhoneAccount account = getPhoneAccountUnchecked(accountHandle);
         if (account != null) {
             if (mState.accounts.remove(account)) {
                 write();
@@ -615,12 +663,6 @@
         }
     }
 
-    private void fireSimCallManagerChanged() {
-        for (Listener l : mListeners) {
-            l.onSimCallManagerChanged(this);
-        }
-    }
-
     private String getAccountDiffString(PhoneAccount account1, PhoneAccount account2) {
         if (account1 == null || account2 == null) {
             return "Diff: " + account1 + ", " + account2;
@@ -696,7 +738,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 +752,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 +788,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 +804,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 +826,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 +859,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 +878,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 +896,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 +919,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) {
@@ -864,26 +938,35 @@
     // State management
     //
 
-    private void write() {
-        final FileOutputStream os;
-        try {
-            os = mAtomicFile.startWrite();
-            boolean success = false;
+    private class AsyncXmlWriter extends AsyncTask<ByteArrayOutputStream, Void, Void> {
+        @Override
+        public Void doInBackground(ByteArrayOutputStream... args) {
+            final ByteArrayOutputStream buffer = args[0];
+            FileOutputStream fileOutput = null;
             try {
-                XmlSerializer serializer = new FastXmlSerializer();
-                serializer.setOutput(new BufferedOutputStream(os), "utf-8");
-                writeToXml(mState, serializer, mContext);
-                serializer.flush();
-                success = true;
-            } finally {
-                if (success) {
-                    mAtomicFile.finishWrite(os);
-                } else {
-                    mAtomicFile.failWrite(os);
+                synchronized (mWriteLock) {
+                    fileOutput = mAtomicFile.startWrite();
+                    buffer.writeTo(fileOutput);
+                    mAtomicFile.finishWrite(fileOutput);
                 }
+            } catch (IOException e) {
+                Log.e(this, e, "Writing state to XML file");
+                mAtomicFile.failWrite(fileOutput);
             }
+            return null;
+        }
+    }
+
+    private void write() {
+        try {
+            ByteArrayOutputStream os = new ByteArrayOutputStream();
+            XmlSerializer serializer = new FastXmlSerializer();
+            serializer.setOutput(os, "utf-8");
+            writeToXml(mState, serializer, mContext);
+            serializer.flush();
+            new AsyncXmlWriter().execute(os);
         } catch (IOException e) {
-            Log.e(this, e, "Writing state to XML file");
+            Log.e(this, e, "Writing state to XML buffer");
         }
     }
 
@@ -954,8 +1037,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 +1084,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 +1165,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 +1175,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 +1185,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 +1261,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 +1286,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 +1338,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 +1418,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 +1442,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 +1467,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 +1513,8 @@
                     } else if (parser.getName().equals(ENABLED)) {
                         parser.next();
                         enabled = "true".equalsIgnoreCase(parser.getText());
+                    } else if (parser.getName().equals(EXTRAS)) {
+                        extras = readBundle(parser);
                     }
                 }
 
@@ -1313,6 +1580,7 @@
                         .setShortDescription(shortDescription)
                         .setSupportedUriSchemes(supportedUriSchemes)
                         .setHighlightColor(highlightColor)
+                        .setExtras(extras)
                         .setIsEnabled(enabled);
 
                 if (icon != null) {
diff --git a/src/com/android/server/telecom/PhoneNumberUtilsAdapter.java b/src/com/android/server/telecom/PhoneNumberUtilsAdapter.java
new file mode 100644
index 0000000..41284cb
--- /dev/null
+++ b/src/com/android/server/telecom/PhoneNumberUtilsAdapter.java
@@ -0,0 +1,32 @@
+/*
+ * 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.content.Intent;
+
+/**
+ * Interface to avoid static calls to PhoneNumberUtils. Add methods to this interface as needed for
+ * refactoring.
+ */
+public interface PhoneNumberUtilsAdapter {
+    boolean isPotentialLocalEmergencyNumber(Context context, String number);
+    boolean isUriNumber(String number);
+    String getNumberFromIntent(Intent intent, Context context);
+    String convertKeypadLettersToDigits(String number);
+    String stripSeparators(String number);
+}
diff --git a/src/com/android/server/telecom/PhoneNumberUtilsAdapterImpl.java b/src/com/android/server/telecom/PhoneNumberUtilsAdapterImpl.java
new file mode 100644
index 0000000..640d814
--- /dev/null
+++ b/src/com/android/server/telecom/PhoneNumberUtilsAdapterImpl.java
@@ -0,0 +1,48 @@
+/*
+ * 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.content.Intent;
+import android.telephony.PhoneNumberUtils;
+
+public class PhoneNumberUtilsAdapterImpl implements PhoneNumberUtilsAdapter {
+    @Override
+    public boolean isPotentialLocalEmergencyNumber(Context context, String number) {
+        return PhoneNumberUtils.isPotentialLocalEmergencyNumber(context, number);
+    }
+
+    @Override
+    public boolean isUriNumber(String number) {
+        return PhoneNumberUtils.isUriNumber(number);
+    }
+
+    @Override
+    public String getNumberFromIntent(Intent intent, Context context) {
+        return PhoneNumberUtils.getNumberFromIntent(intent, context);
+    }
+
+    @Override
+    public String convertKeypadLettersToDigits(String number) {
+        return PhoneNumberUtils.convertKeypadLettersToDigits(number);
+    }
+
+    @Override
+    public String stripSeparators(String number) {
+        return PhoneNumberUtils.stripSeparators(number);
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/server/telecom/PhoneStateBroadcaster.java b/src/com/android/server/telecom/PhoneStateBroadcaster.java
index 57ae24b..54329c8 100644
--- a/src/com/android/server/telecom/PhoneStateBroadcaster.java
+++ b/src/com/android/server/telecom/PhoneStateBroadcaster.java
@@ -43,26 +43,20 @@
 
     @Override
     public void onCallStateChanged(Call call, int oldState, int newState) {
-        if ((newState == CallState.DIALING || newState == CallState.ACTIVE
-                || newState == CallState.ON_HOLD) &&
-                !mCallsManager.hasRingingCall()) {
-            /*
-             * EXTRA_STATE_RINGING takes precedence over EXTRA_STATE_OFFHOOK, so if there is
-             * already a ringing call, don't broadcast EXTRA_STATE_OFFHOOK.
-             */
-            sendPhoneStateChangedBroadcast(call, TelephonyManager.CALL_STATE_OFFHOOK);
-        }
+        updateStates(call);
     }
 
     @Override
     public void onCallAdded(Call call) {
-        if (call.getState() == CallState.RINGING) {
-            sendPhoneStateChangedBroadcast(call, TelephonyManager.CALL_STATE_RINGING);
-        }
-    };
+        updateStates(call);
+    }
 
     @Override
     public void onCallRemoved(Call call) {
+        updateStates(call);
+    }
+
+    private void updateStates(Call call) {
         // Recalculate the current phone state based on the consolidated state of the remaining
         // calls in the call list.
         int callState = TelephonyManager.CALL_STATE_IDLE;
diff --git a/src/com/android/server/telecom/ProximitySensorManager.java b/src/com/android/server/telecom/ProximitySensorManager.java
index 5fddb89..dd336c4 100644
--- a/src/com/android/server/telecom/ProximitySensorManager.java
+++ b/src/com/android/server/telecom/ProximitySensorManager.java
@@ -19,27 +19,21 @@
 import android.content.Context;
 import android.os.PowerManager;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 /**
  * This class manages the proximity sensor and allows callers to turn it on and off.
  */
 public class ProximitySensorManager extends CallsManagerListenerBase {
-    private static final String TAG = ProximitySensorManager.class.getSimpleName();
 
-    private final PowerManager.WakeLock mProximityWakeLock;
     private final CallsManager mCallsManager;
+    private final TelecomWakeLock mTelecomWakeLock;
 
-    public ProximitySensorManager(Context context, CallsManager callsManager) {
-        PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+    public ProximitySensorManager(TelecomWakeLock telecomWakeLock, CallsManager callsManager) {
 
-        if (pm.isWakeLockLevelSupported(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK)) {
-            mProximityWakeLock = pm.newWakeLock(
-                    PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, TAG);
-        } else {
-            mProximityWakeLock = null;
-        }
-
+        mTelecomWakeLock = telecomWakeLock;
         mCallsManager = callsManager;
-        Log.d(this, "onCreate: mProximityWakeLock: ", mProximityWakeLock);
+        Log.d(this, "onCreate: mProximityWakeLock: ", mTelecomWakeLock);
     }
 
     @Override
@@ -54,38 +48,23 @@
     /**
      * Turn the proximity sensor on.
      */
-    void turnOn() {
+    @VisibleForTesting
+    public void turnOn() {
         if (mCallsManager.getCalls().isEmpty()) {
             Log.w(this, "Asking to turn on prox sensor without a call? I don't think so.");
             return;
         }
 
-        if (mProximityWakeLock == null) {
-            return;
-        }
-        if (!mProximityWakeLock.isHeld()) {
-            Log.i(this, "Acquiring proximity wake lock");
-            mProximityWakeLock.acquire();
-        } else {
-            Log.i(this, "Proximity wake lock already acquired");
-        }
+        mTelecomWakeLock.acquire();
     }
 
     /**
      * Turn the proximity sensor off.
      * @param screenOnImmediately
      */
-    void turnOff(boolean screenOnImmediately) {
-        if (mProximityWakeLock == null) {
-            return;
-        }
-        if (mProximityWakeLock.isHeld()) {
-            Log.i(this, "Releasing proximity wake lock");
-            int flags =
-                (screenOnImmediately ? 0 : PowerManager.RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY);
-            mProximityWakeLock.release(flags);
-        } else {
-            Log.i(this, "Proximity wake lock already released");
-        }
+    @VisibleForTesting
+    public void turnOff(boolean screenOnImmediately) {
+        int flags = (screenOnImmediately ? 0 : PowerManager.RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY);
+        mTelecomWakeLock.release(flags);
     }
 }
diff --git a/src/com/android/server/telecom/RingbackPlayer.java b/src/com/android/server/telecom/RingbackPlayer.java
index bb2055f..af60b68 100644
--- a/src/com/android/server/telecom/RingbackPlayer.java
+++ b/src/com/android/server/telecom/RingbackPlayer.java
@@ -1,17 +1,17 @@
 /*
- * Copyright 2014, The Android Open Source Project
+ * 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
+ *      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.
+ * limitations under the License
  */
 
 package com.android.server.telecom;
@@ -24,9 +24,7 @@
  * able to turn off and on as the user switches between calls. This is why it is implemented as its
  * own class.
  */
-class RingbackPlayer extends CallsManagerListenerBase {
-
-    private final CallsManager mCallsManager;
+class RingbackPlayer {
 
     private final InCallTonePlayer.Factory mPlayerFactory;
 
@@ -40,52 +38,16 @@
      */
     private InCallTonePlayer mTonePlayer;
 
-    RingbackPlayer(CallsManager callsManager, InCallTonePlayer.Factory playerFactory) {
-        mCallsManager = callsManager;
+    RingbackPlayer(InCallTonePlayer.Factory playerFactory) {
         mPlayerFactory = playerFactory;
     }
 
-    /** {@inheritDoc} */
-    @Override
-    public void onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall) {
-        if (oldForegroundCall != null) {
-            stopRingbackForCall(oldForegroundCall);
-        }
-
-        if (shouldStartRinging(newForegroundCall)) {
-            startRingbackForCall(newForegroundCall);
-        }
-    }
-
-    @Override
-    public void onConnectionServiceChanged(
-            Call call,
-            ConnectionServiceWrapper oldService,
-            ConnectionServiceWrapper newService) {
-
-        // Treat as ending or begining dialing based on the state transition.
-        if (shouldStartRinging(call)) {
-            startRingbackForCall(call);
-        } else if (newService == null) {
-            stopRingbackForCall(call);
-        }
-    }
-
-    @Override
-    public void onRingbackRequested(Call call, boolean ignored) {
-        if (shouldStartRinging(call)) {
-            startRingbackForCall(call);
-        } else {
-            stopRingbackForCall(call);
-        }
-    }
-
     /**
      * Starts ringback for the specified dialing call as needed.
      *
      * @param call The call for which to ringback.
      */
-    private void startRingbackForCall(Call call) {
+    public void startRingbackForCall(Call call) {
         Preconditions.checkState(call.getState() == CallState.DIALING);
 
         if (mCall == call) {
@@ -112,7 +74,7 @@
      *
      * @param call The call for which to stop ringback.
      */
-    private void stopRingbackForCall(Call call) {
+    public void stopRingbackForCall(Call call) {
         if (mCall == call) {
             // The foreground call is no longer dialing or is no longer the foreground call. In
             // either case, stop the ringback tone.
@@ -127,11 +89,4 @@
             }
         }
     }
-
-    private boolean shouldStartRinging(Call call) {
-        return call != null
-                && mCallsManager.getForegroundCall() == call
-                && call.getState() == CallState.DIALING
-                && call.isRingbackRequested();
-    }
-}
+}
\ No newline at end of file
diff --git a/src/com/android/server/telecom/Ringer.java b/src/com/android/server/telecom/Ringer.java
index 0245859..a3766ec 100644
--- a/src/com/android/server/telecom/Ringer.java
+++ b/src/com/android/server/telecom/Ringer.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2014 The Android Open Source Project
+ * 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.
@@ -11,7 +11,7 @@
  * 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.
+ * limitations under the License
  */
 
 package com.android.server.telecom;
@@ -23,28 +23,21 @@
 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 java.util.LinkedList;
-import java.util.List;
+import com.android.internal.annotations.VisibleForTesting;
 
 /**
  * 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 {
     private static final long[] VIBRATION_PATTERN = new long[] {
         0, // No delay before starting
         1000, // How long to vibrate
         1000, // How long to wait before vibrating again
     };
 
-    private static final int STATE_RINGING = 1;
-    private static final int STATE_CALL_WAITING = 2;
-    private static final int STATE_STOPPED = 3;
-
     private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder()
             .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
             .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
@@ -53,22 +46,25 @@
     /** 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 CallAudioManager mCallAudioManager;
-    private final CallsManager mCallsManager;
+    private final SystemSettingsUtil mSystemSettingsUtil;
     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;
+
+    /**
+     * Call objects that are ringing or call-waiting. These are used only for logging purposes.
+     */
+    private Call mRingingCall;
+    private Call mCallWaitingCall;
 
     /**
      * Used to track the status of {@link #mVibrator} in the case of simultaneous incoming calls.
@@ -76,168 +72,115 @@
     private boolean mIsVibrating = false;
 
     /** Initializes the Ringer. */
-    Ringer(
-            CallAudioManager callAudioManager,
-            CallsManager callsManager,
+    @VisibleForTesting
+    public Ringer(
             InCallTonePlayer.Factory playerFactory,
-            Context context) {
+            Context context,
+            SystemSettingsUtil systemSettingsUtil,
+            AsyncRingtonePlayer asyncRingtonePlayer,
+            RingtoneFactory ringtoneFactory,
+            Vibrator vibrator) {
 
-        mCallAudioManager = callAudioManager;
-        mCallsManager = callsManager;
+        mSystemSettingsUtil = systemSettingsUtil;
         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
-    public void onCallAdded(final Call call) {
-        if (call.isIncoming() && call.getState() == CallState.RINGING) {
-            if (mRingingCalls.contains(call)) {
-                Log.wtf(this, "New ringing call is already in list of unanswered calls");
-            }
-            mRingingCalls.add(call);
-            updateRinging(call);
-        }
-    }
-
-    @Override
-    public void onCallRemoved(Call call) {
-        removeFromUnansweredCall(call);
-    }
-
-    @Override
-    public void onCallStateChanged(Call call, int oldState, int newState) {
-        if (newState != CallState.RINGING) {
-            removeFromUnansweredCall(call);
-        }
-    }
-
-    @Override
-    public void onIncomingCallAnswered(Call call) {
-        onRespondedToIncomingCall(call);
-    }
-
-    @Override
-    public void onIncomingCallRejected(Call call, boolean rejectWithMessage, String textMessage) {
-        onRespondedToIncomingCall(call);
-    }
-
-    @Override
-    public void onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall) {
-        Call ringingCall = null;
-        if (mRingingCalls.contains(newForegroundCall)) {
-            ringingCall = newForegroundCall;
-        } else if (mRingingCalls.contains(oldForegroundCall)) {
-            ringingCall = oldForegroundCall;
-        }
-        if (ringingCall != null) {
-            updateRinging(ringingCall);
-        }
-    }
-
-    /**
-     * Silences the ringer for any actively ringing calls.
-     */
-    void silence() {
-        // Remove all calls from the "ringing" set and then update the ringer.
-        mRingingCalls.clear();
-        updateRinging(null);
-    }
-
-    private void onRespondedToIncomingCall(Call call) {
-        // Only stop the ringer if this call is the top-most incoming call.
-        if (getTopMostUnansweredCall() == call) {
-            removeFromUnansweredCall(call);
-        }
-    }
-
-    private Call getTopMostUnansweredCall() {
-        return mRingingCalls.isEmpty() ? null : mRingingCalls.get(0);
-    }
-
-    /**
-     * Removes the specified call from the list of unanswered incoming calls and updates the ringer
-     * based on the new state of {@link #mRingingCalls}. Safe to call with a call that is not
-     * present in the list of incoming calls.
-     */
-    private void removeFromUnansweredCall(Call call) {
-        mRingingCalls.remove(call);
-        updateRinging(call);
-    }
-
-    private void updateRinging(Call call) {
-        if (mRingingCalls.isEmpty()) {
-            stopRinging(call, "No more ringing calls found");
-            stopCallWaiting(call);
-        } else {
-            startRingingOrCallWaiting(call);
-        }
-    }
-
-    private void startRingingOrCallWaiting(Call call) {
-        Call foregroundCall = mCallsManager.getForegroundCall();
-        Log.v(this, "startRingingOrCallWaiting, foregroundCall: %s.", foregroundCall);
-
-        if (Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.THEATER_MODE_ON,
-                0) == 1) {
+    public void startRinging(Call foregroundCall) {
+        if (mSystemSettingsUtil.isTheaterModeOn(mContext)) {
             return;
         }
 
-        if (mRingingCalls.contains(foregroundCall)) {
-            // The foreground call is one of incoming calls so play the ringer out loud.
-            stopCallWaiting(call);
+        if (foregroundCall == null) {
+            Log.wtf(this, "startRinging called with null foreground call.");
+            return;
+        }
 
-            if (!shouldRingForContact(foregroundCall.getContactUri())) {
-                return;
+        if (InCallController.doesDefaultDialerSupportRinging(mContext)) {
+            Log.event(foregroundCall, Log.Events.SKIP_RINGING);
+            return;
+        }
+
+        stopCallWaiting();
+
+        if (!shouldRingForContact(foregroundCall.getContactUri())) {
+            return;
+        }
+
+        AudioManager audioManager =
+                (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+        if (audioManager.getStreamVolume(AudioManager.STREAM_RING) > 0) {
+            mRingingCall = foregroundCall;
+            Log.event(foregroundCall, Log.Events.START_RINGER);
+            // Because we wait until a contact info query to complete before processing a
+            // 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(
+                    mRingtoneFactory.getRingtone(foregroundCall.getRingtone()));
+        } else {
+            Log.v(this, "startRingingOrCallWaiting, skipping because volume is 0");
+        }
+
+        if (shouldVibrate(mContext) && !mIsVibrating) {
+            mVibrator.vibrate(VIBRATION_PATTERN, VIBRATION_PATTERN_REPEAT,
+                    VIBRATION_ATTRIBUTES);
+            mIsVibrating = true;
+        }
+    }
+
+    public void startCallWaiting(Call call) {
+        if (mSystemSettingsUtil.isTheaterModeOn(mContext)) {
+            return;
+        }
+
+        if (InCallController.doesDefaultDialerSupportRinging(mContext)) {
+            Log.event(call, Log.Events.SKIP_RINGING);
+            return;
+        }
+
+        Log.v(this, "Playing call-waiting tone.");
+
+        stopRinging();
+
+        if (mCallWaitingPlayer == null) {
+            Log.event(call, Log.Events.START_CALL_WAITING_TONE);
+            mCallWaitingCall = call;
+            mCallWaitingPlayer =
+                    mPlayerFactory.createPlayer(InCallTonePlayer.TONE_CALL_WAITING);
+            mCallWaitingPlayer.startTone();
+        }
+    }
+
+    public void stopRinging() {
+        if (mRingingCall != null) {
+            Log.event(mRingingCall, Log.Events.STOP_RINGER);
+            mRingingCall = null;
+        }
+
+        mRingtonePlayer.stop();
+
+        if (mIsVibrating) {
+            mVibrator.cancel();
+            mIsVibrating = false;
+        }
+    }
+
+    public void stopCallWaiting() {
+        Log.v(this, "stop call waiting.");
+        if (mCallWaitingPlayer != null) {
+            if (mCallWaitingCall != null) {
+                Log.event(mCallWaitingCall, Log.Events.STOP_CALL_WAITING_TONE);
+                mCallWaitingCall = null;
             }
 
-            AudioManager audioManager =
-                    (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
-            if (audioManager.getStreamVolume(AudioManager.STREAM_RING) > 0) {
-                if (mState != STATE_RINGING) {
-                    Log.event(call, Log.Events.START_RINGER);
-                    mState = STATE_RINGING;
-                }
-                mCallAudioManager.setIsRinging(call, true);
-
-                // Because we wait until a contact info query to complete before processing a
-                // 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());
-            } else {
-                Log.v(this, "startRingingOrCallWaiting, skipping because volume is 0");
-            }
-
-            if (shouldVibrate(mContext) && !mIsVibrating) {
-                mVibrator.vibrate(VIBRATION_PATTERN, VIBRATION_PATTERN_REPEAT,
-                        VIBRATION_ATTRIBUTES);
-                mIsVibrating = true;
-            }
-        } else if (foregroundCall != null) {
-            // The first incoming call added to Telecom is not a foreground call at this point
-            // in time. If the current foreground call is null at point, don't play call-waiting
-            // as the call will eventually be promoted to the foreground call and play the
-            // ring tone.
-            Log.v(this, "Playing call-waiting tone.");
-
-            // All incoming calls are in background so play call waiting.
-            stopRinging(call, "Stop for call-waiting");
-
-
-            if (mState != STATE_CALL_WAITING) {
-                Log.event(call, Log.Events.START_CALL_WAITING_TONE);
-                mState = STATE_CALL_WAITING;
-            }
-
-            if (mCallWaitingPlayer == null) {
-                mCallWaitingPlayer =
-                        mPlayerFactory.createPlayer(InCallTonePlayer.TONE_CALL_WAITING);
-                mCallWaitingPlayer.startTone();
-            }
+            mCallWaitingPlayer.stopTone();
+            mCallWaitingPlayer = null;
         }
     }
 
@@ -251,37 +194,6 @@
         return manager.matchesCallFilter(extras);
     }
 
-    private void stopRinging(Call call, String reasonTag) {
-        if (mState == STATE_RINGING) {
-            Log.event(call, Log.Events.STOP_RINGER, reasonTag);
-            mState = STATE_STOPPED;
-        }
-
-        mRingtonePlayer.stop();
-
-        if (mIsVibrating) {
-            mVibrator.cancel();
-            mIsVibrating = false;
-        }
-
-        // Even though stop is asynchronous it's ok to update the audio manager. Things like audio
-        // focus are voluntary so releasing focus too early is not detrimental.
-        mCallAudioManager.setIsRinging(call, false);
-    }
-
-    private void stopCallWaiting(Call call) {
-        Log.v(this, "stop call waiting.");
-        if (mCallWaitingPlayer != null) {
-            mCallWaitingPlayer.stopTone();
-            mCallWaitingPlayer = null;
-        }
-
-        if (mState == STATE_CALL_WAITING) {
-            Log.event(call, Log.Events.STOP_CALL_WAITING_TONE);
-            mState = STATE_STOPPED;
-        }
-    }
-
     private boolean shouldVibrate(Context context) {
         AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
         int ringerMode = audioManager.getRingerModeInternal();
@@ -296,7 +208,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/Runnable.java b/src/com/android/server/telecom/Runnable.java
new file mode 100644
index 0000000..ce5b6d2
--- /dev/null
+++ b/src/com/android/server/telecom/Runnable.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2016 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;
+
+/**
+ * Encapsulates session logging in a Runnable to reduce code duplication when continuing subsessions
+ * in a handler/thread.
+ */
+public abstract class Runnable {
+
+    private Session mSubsession;
+    private final String mSubsessionName;
+    private Object mLock = new Object();
+    private final java.lang.Runnable mRunnable = new java.lang.Runnable() {
+            @Override
+            public void run() {
+                synchronized (mLock) {
+                    try {
+                        Log.continueSession(mSubsession, mSubsessionName);
+                        loggedRun();
+                    } finally {
+                        Log.endSession();
+                        mSubsession = null;
+                    }
+                }
+            }
+        };
+
+    public Runnable(String subsessionName) {
+        mSubsessionName = subsessionName;
+    }
+
+    /**
+     * Return the runnable that will be canceled in the handler queue.
+     * @return Runnable object to cancel.
+     */
+    public final java.lang.Runnable getRunnableToCancel() {
+        return mRunnable;
+    }
+
+    /**
+     * Creates a Runnable and a logging subsession that can be used in a handler/thread. Be sure to
+     * call cancel() if this session is never going to be run (removed from a handler queue, for
+     * for example).
+     * @return A Java Runnable that can be used in a handler queue or thread.
+     */
+    public java.lang.Runnable prepare() {
+        cancel();
+        mSubsession = Log.createSubsession();
+        return mRunnable;
+    }
+
+    /**
+     * This method is used to clean up the active session if the Runnable gets removed from a
+     * handler and is never run.
+     */
+    public void cancel() {
+        synchronized (mLock) {
+            Log.cancelSubsession(mSubsession);
+            mSubsession = null;
+        }
+    }
+
+    /**
+     * The method that will be run in the handler/thread.
+     */
+    abstract public void loggedRun();
+
+}
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..51ef0fa
--- /dev/null
+++ b/src/com/android/server/telecom/Session.java
@@ -0,0 +1,222 @@
+/*
+ * 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;
+    // True if this is a subsession that has been started from the same thread as the parent
+    // session. This can happen if Log.startSession(...) is called multiple times on the same
+    // thread in the case of one Telecom entry point method calling another entry point method.
+    // In this case, we can just make this subsession "invisible," but still keep track of it so
+    // that the Log.endSession() calls match up.
+    private boolean mIsStartedFromActiveSession = false;
+    // Optionally provided info about the method/class/component that started the session in order
+    // to make Logging easier. This info will be provided in parentheses along with the session.
+    private String mOwnerInfo;
+
+    public Session(String sessionId, String shortMethodName, long startTimeMs, long threadID,
+            boolean isStartedFromActiveSession, String ownerInfo) {
+        setSessionId(sessionId);
+        setShortMethodName(shortMethodName);
+        mExecutionStartTimeMs = startTimeMs;
+        mParentSession = null;
+        mChildSessions = new ArrayList<>(5);
+        mIsStartedFromActiveSession = isStartedFromActiveSession;
+        mOwnerInfo = ownerInfo;
+    }
+
+    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;
+    }
+
+    public boolean isStartedFromActiveSession() {
+        return mIsStartedFromActiveSession;
+    }
+
+    // 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++);
+    }
+
+    @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 &&
+                mIsStartedFromActiveSession == otherSession.mIsStartedFromActiveSession &&
+                mOwnerInfo == otherSession.mOwnerInfo;
+    }
+
+    // Builds full session id recursively
+    private String getFullSessionId() {
+        // Cache mParentSession locally to prevent a concurrency problem where
+        // Log.endParentSessions() is called while a logging statement is running (Log.i, for
+        // example) and setting mParentSession to null in a different thread after the null check
+        // occurred.
+        Session parentSession = mParentSession;
+        if(parentSession == null) {
+            return mSessionId;
+        } else {
+            return parentSession.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() {
+        if(mParentSession != null && mIsStartedFromActiveSession) {
+            // Log.startSession was called from within another active session. Use the parent's
+            // Id instead of the child to reduce confusion.
+            return mParentSession.toString();
+        } else {
+            StringBuilder methodName = new StringBuilder();
+            methodName.append(mShortMethodName);
+            if(mOwnerInfo != null && !mOwnerInfo.isEmpty()) {
+                methodName.append("(InCall package: ");
+                methodName.append(mOwnerInfo);
+                methodName.append(")");
+            }
+            return methodName.toString() + "@" + 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/SystemStateProvider.java b/src/com/android/server/telecom/SystemStateProvider.java
new file mode 100644
index 0000000..0b636cf
--- /dev/null
+++ b/src/com/android/server/telecom/SystemStateProvider.java
@@ -0,0 +1,123 @@
+/*
+ * 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.UiModeManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Configuration;
+
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+/**
+ * Provides various system states to the rest of the telecom codebase. So far, that's only car-mode.
+ */
+public class SystemStateProvider {
+
+    public static interface SystemStateListener {
+        public void onCarModeChanged(boolean isCarMode);
+    }
+
+    private final Context mContext;
+    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            Log.startSession("SSP.oR");
+            try {
+                String action = intent.getAction();
+                if (UiModeManager.ACTION_ENTER_CAR_MODE.equals(action)) {
+                    onEnterCarMode();
+                } else if (UiModeManager.ACTION_EXIT_CAR_MODE.equals(action)) {
+                    onExitCarMode();
+                } else {
+                    Log.w(this, "Unexpected intent received: %s", intent.getAction());
+                }
+            } finally {
+                Log.endSession();
+            }
+        }
+    };
+
+    private Set<SystemStateListener> mListeners = new CopyOnWriteArraySet<>();
+    private boolean mIsCarMode;
+
+    public SystemStateProvider(Context context) {
+        mContext = context;
+
+        IntentFilter intentFilter = new IntentFilter(UiModeManager.ACTION_ENTER_CAR_MODE);
+        intentFilter.addAction(UiModeManager.ACTION_EXIT_CAR_MODE);
+        mContext.registerReceiver(mBroadcastReceiver, intentFilter);
+        Log.i(this, "Registering car mode receiver: %s", intentFilter);
+
+        mIsCarMode = getSystemCarMode();
+    }
+
+    public void addListener(SystemStateListener listener) {
+        if (listener != null) {
+            mListeners.add(listener);
+        }
+    }
+
+    public boolean removeListener(SystemStateListener listener) {
+        return mListeners.remove(listener);
+    }
+
+    public boolean isCarMode() {
+        return mIsCarMode;
+    }
+
+    private void onEnterCarMode() {
+        if (!mIsCarMode) {
+            Log.i(this, "Entering carmode");
+            mIsCarMode = true;
+            notifyCarMode();
+        }
+    }
+
+    private void onExitCarMode() {
+        if (mIsCarMode) {
+            Log.i(this, "Exiting carmode");
+            mIsCarMode = false;
+            notifyCarMode();
+        }
+    }
+
+    private void notifyCarMode() {
+        for (SystemStateListener listener : mListeners) {
+            listener.onCarModeChanged(mIsCarMode);
+        }
+    }
+
+    /**
+     * Checks the system for the current car mode.
+     *
+     * @return True if in car mode, false otherwise.
+     */
+    private boolean getSystemCarMode() {
+        UiModeManager uiModeManager =
+                (UiModeManager) mContext.getSystemService(Context.UI_MODE_SERVICE);
+
+        if (uiModeManager != null) {
+            return uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_CAR;
+        }
+
+        return false;
+    }
+}
diff --git a/src/com/android/server/telecom/TelecomBroadcastIntentProcessor.java b/src/com/android/server/telecom/TelecomBroadcastIntentProcessor.java
index 89aa2aa..c5db6de 100644
--- a/src/com/android/server/telecom/TelecomBroadcastIntentProcessor.java
+++ b/src/com/android/server/telecom/TelecomBroadcastIntentProcessor.java
@@ -33,6 +33,8 @@
     public static final String ACTION_CLEAR_MISSED_CALLS =
             "com.android.server.telecom.ACTION_CLEAR_MISSED_CALLS";
 
+    public static final String EXTRA_USERHANDLE = "userhandle";
+
     private final Context mContext;
     private final CallsManager mCallsManager;
 
@@ -45,6 +47,11 @@
         String action = intent.getAction();
 
         Log.v(this, "Action received: %s.", action);
+        UserHandle userHandle = intent.getParcelableExtra(EXTRA_USERHANDLE);
+        if (userHandle == null) {
+            Log.d(this, "user handle can't be null, not processing the broadcast");
+            return;
+        }
 
         MissedCallNotifier missedCallNotifier = mCallsManager.getMissedCallNotifier();
 
@@ -52,26 +59,26 @@
         if (ACTION_SEND_SMS_FROM_NOTIFICATION.equals(action)) {
             // Close the notification shade and the notification itself.
             closeSystemDialogs(mContext);
-            missedCallNotifier.clearMissedCalls();
+            missedCallNotifier.clearMissedCalls(userHandle);
 
             Intent callIntent = new Intent(Intent.ACTION_SENDTO, intent.getData());
             callIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-            mContext.startActivityAsUser(callIntent, UserHandle.CURRENT);
+            mContext.startActivityAsUser(callIntent, userHandle);
 
         // Call back recent caller from the missed call notification.
         } else if (ACTION_CALL_BACK_FROM_NOTIFICATION.equals(action)) {
             // Close the notification shade and the notification itself.
             closeSystemDialogs(mContext);
-            missedCallNotifier.clearMissedCalls();
+            missedCallNotifier.clearMissedCalls(userHandle);
 
-            Intent callIntent = new Intent(Intent.ACTION_CALL_PRIVILEGED, intent.getData());
+            Intent callIntent = new Intent(Intent.ACTION_CALL, intent.getData());
             callIntent.setFlags(
                     Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
-            mContext.startActivityAsUser(callIntent, UserHandle.CURRENT);
+            mContext.startActivityAsUser(callIntent, userHandle);
 
         // Clear the missed call notification and call log entries.
         } else if (ACTION_CLEAR_MISSED_CALLS.equals(action)) {
-            missedCallNotifier.clearMissedCalls();
+            missedCallNotifier.clearMissedCalls(userHandle);
         }
     }
 
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index 9b0c7c7..a269085 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -17,6 +17,7 @@
 package com.android.server.telecom;
 
 import static android.Manifest.permission.CALL_PHONE;
+import static android.Manifest.permission.DUMP;
 import static android.Manifest.permission.MODIFY_PHONE_STATE;
 import static android.Manifest.permission.READ_PHONE_STATE;
 import static android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE;
@@ -34,26 +35,27 @@
 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.ParcelableCallAnalytics;
 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;
 import com.android.internal.util.IndentingPrintWriter;
-import com.android.server.telecom.components.UserCallIntentProcessor;
+import com.android.server.telecom.components.UserCallIntentProcessorFactory;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 
@@ -61,6 +63,40 @@
  * Implementation of the ITelecom interface.
  */
 public class TelecomServiceImpl {
+    public interface DefaultDialerManagerAdapter {
+        String getDefaultDialerApplication(Context context);
+        boolean setDefaultDialerApplication(Context context, String packageName);
+        boolean isDefaultOrSystemDialer(Context context, String packageName);
+    }
+
+    static class DefaultDialerManagerAdapterImpl implements DefaultDialerManagerAdapter {
+        @Override
+        public String getDefaultDialerApplication(Context context) {
+            return DefaultDialerManager.getDefaultDialerApplication(context);
+        }
+
+        @Override
+        public boolean setDefaultDialerApplication(Context context, String packageName) {
+            return DefaultDialerManager.setDefaultDialerApplication(context, packageName);
+        }
+
+        @Override
+        public boolean isDefaultOrSystemDialer(Context context, String packageName) {
+            return DefaultDialerManager.isDefaultOrSystemDialer(context, packageName);
+        }
+    }
+
+    public interface SubscriptionManagerAdapter {
+        int getDefaultVoiceSubId();
+    }
+
+    static class SubscriptionManagerAdapterImpl implements SubscriptionManagerAdapter {
+        @Override
+        public int getDefaultVoiceSubId() {
+            return SubscriptionManager.getDefaultVoiceSubscriptionId();
+        }
+    }
+
     private static final String PERMISSION_PROCESS_PHONE_ACCOUNT_REGISTRATION =
             "android.permission.PROCESS_PHONE_ACCOUNT_REGISTRATION";
     private static final int DEFAULT_VIDEO_STATE = -1;
@@ -69,30 +105,27 @@
         @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();
             }
         }
 
@@ -100,94 +133,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();
                 }
             }
         }
@@ -195,16 +243,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();
                 }
             }
         }
@@ -213,11 +268,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();
                 }
             }
         }
@@ -225,20 +283,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();
                 }
             }
         }
@@ -246,92 +301,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();
             }
         }
 
@@ -339,6 +406,7 @@
         public void unregisterPhoneAccount(PhoneAccountHandle accountHandle) {
             synchronized (mLock) {
                 try {
+                    Log.startSession("TSI.uPA");
                     enforcePhoneAccountModificationForPackage(
                             accountHandle.getComponentName().getPackageName());
                     enforceUserHandleMatchesCaller(accountHandle);
@@ -349,7 +417,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);
@@ -359,6 +428,8 @@
                 } catch (Exception e) {
                     Log.e(this, e, "unregisterPhoneAccount %s", accountHandle);
                     throw e;
+                } finally {
+                    Log.endSession();
                 }
             }
         }
@@ -367,12 +438,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();
                 }
             }
         }
@@ -383,25 +457,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();
             }
         }
 
@@ -410,27 +489,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.getDefaultVoiceSubscriptionId();
-                    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 = mSubscriptionManagerAdapter.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();
             }
         }
 
@@ -439,27 +524,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().getLine1Number(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().getLine1Number(subId);
+                    } catch (Exception e) {
+                        Log.e(this, e, "getSubscriptionIdForPhoneAccount");
+                        throw e;
+                    } finally {
+                        Binder.restoreCallingIdentity(token);
+                    }
                 }
+            } finally {
+                Log.endSession();
             }
         }
 
@@ -468,16 +560,22 @@
          */
         @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.getCallAudioManager().silenceRingers();
+                        mCallsManager.getInCallController().silenceRinger();
+                    } finally {
+                        Binder.restoreCallingIdentity(token);
+                    }
                 }
+            } finally {
+                Log.endSession();
             }
         }
 
@@ -488,11 +586,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();
+            }
         }
 
         /**
@@ -503,11 +606,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 mDefaultDialerManagerAdapter.getDefaultDialerApplication(mContext);
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
             } finally {
-                Binder.restoreCallingIdentity(token);
+                Log.endSession();
             }
         }
 
@@ -516,7 +624,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();
+            }
         }
 
         /**
@@ -524,14 +637,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();
             }
         }
 
@@ -540,12 +658,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();
             }
         }
 
@@ -554,8 +682,13 @@
          */
         @Override
         public int getCallState() {
-            synchronized (mLock) {
-                return mCallsManager.getCallState();
+            try {
+                Log.startSession("TSI.getCallState");
+                synchronized (mLock) {
+                    return mCallsManager.getCallState();
+                }
+            } finally {
+                Log.endSession();
             }
         }
 
@@ -564,15 +697,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();
             }
         }
 
@@ -581,15 +719,20 @@
          */
         @Override
         public void acceptRingingCall() {
-            synchronized (mLock) {
-                enforceModifyPermission();
+            try {
+                Log.startSession("TSI.aRC");
+                synchronized (mLock) {
+                    enforceModifyPermission();
 
-                long token = Binder.clearCallingIdentity();
-                try {
-                    acceptRingingCallInternal(DEFAULT_VIDEO_STATE);
-                } finally {
-                    Binder.restoreCallingIdentity(token);
+                    long token = Binder.clearCallingIdentity();
+                    try {
+                        acceptRingingCallInternal(DEFAULT_VIDEO_STATE);
+                    } finally {
+                        Binder.restoreCallingIdentity(token);
+                    }
                 }
+            } finally {
+                Log.endSession();
             }
         }
 
@@ -599,15 +742,20 @@
          */
         @Override
         public void acceptRingingCallWithVideoState(int videoState) {
-            synchronized (mLock) {
-                enforceModifyPermission();
+            try {
+                Log.startSession("TSI.aRCWVS");
+                synchronized (mLock) {
+                    enforceModifyPermission();
 
-                long token = Binder.clearCallingIdentity();
-                try {
-                    acceptRingingCallInternal(videoState);
-                } finally {
-                    Binder.restoreCallingIdentity(token);
+                    long token = Binder.clearCallingIdentity();
+                    try {
+                        acceptRingingCallInternal(videoState);
+                    } finally {
+                        Binder.restoreCallingIdentity(token);
+                    }
                 }
+            } finally {
+                Log.endSession();
             }
         }
 
@@ -616,18 +764,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();
             }
         }
 
@@ -636,35 +789,46 @@
          */
         @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);
+                    UserHandle userHandle = Binder.getCallingUserHandle();
+                    long token = Binder.clearCallingIdentity();
+                    try {
+                        mCallsManager.getMissedCallNotifier().clearMissedCalls(userHandle);
+                    } 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();
             }
         }
 
@@ -672,30 +836,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();
             }
         }
 
@@ -705,26 +874,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();
             }
         }
 
@@ -733,12 +908,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();
             }
         }
 
@@ -747,12 +927,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();
             }
         }
 
@@ -761,39 +946,48 @@
          */
         @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);
+                            }
+                            mCallIntentProcessorAdapter.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();
             }
         }
 
@@ -802,31 +996,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);
+                            mCallIntentProcessorAdapter.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();
             }
         }
 
@@ -835,36 +1035,42 @@
          */
         @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);
+                        mUserCallIntentProcessorFactory.create(mContext, userHandle)
+                                .processIntent(
+                                        intent, callingPackage, hasCallAppOp && hasCallPermission);
+                    } finally {
+                        Binder.restoreCallingIdentity(token);
+                    }
+                }
+            } finally {
+                Log.endSession();
             }
         }
 
@@ -873,39 +1079,61 @@
          */
         @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 =
+                                mDefaultDialerManagerAdapter
+                                        .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();
+            }
+        }
+
+        @Override
+        public List<ParcelableCallAnalytics> dumpCallAnalytics() {
+            try {
+                Log.startSession("TSI.dCA");
+                enforcePermission(DUMP);
+                return Arrays.asList(Analytics.dumpToParcelableAnalytics());
+            } finally {
+                Log.endSession();
             }
         }
 
@@ -938,10 +1166,21 @@
                 pw.increaseIndent();
                 mPhoneAccountRegistrar.dump(pw);
                 pw.decreaseIndent();
+
+                pw.println("Analytics:");
+                pw.increaseIndent();
+                Analytics.dump(pw);
+                pw.decreaseIndent();
             }
 
             Log.dumpCallEvents(pw);
         }
+
+        // TODO: Add javadoc which points to TelecomManager
+        @Override
+        public void launchManageBlockedNumbersActivity(String callingPackageName) {
+            // TODO: Add implementation
+        }
     };
 
     private Context mContext;
@@ -950,12 +1189,20 @@
     private PackageManager mPackageManager;
     private CallsManager mCallsManager;
     private final PhoneAccountRegistrar mPhoneAccountRegistrar;
+    private final CallIntentProcessor.Adapter mCallIntentProcessorAdapter;
+    private final UserCallIntentProcessorFactory mUserCallIntentProcessorFactory;
+    private final DefaultDialerManagerAdapter mDefaultDialerManagerAdapter;
+    private final SubscriptionManagerAdapter mSubscriptionManagerAdapter;
     private final TelecomSystem.SyncRoot mLock;
 
     public TelecomServiceImpl(
             Context context,
             CallsManager callsManager,
             PhoneAccountRegistrar phoneAccountRegistrar,
+            CallIntentProcessor.Adapter callIntentProcessorAdapter,
+            UserCallIntentProcessorFactory userCallIntentProcessorFactory,
+            DefaultDialerManagerAdapter defaultDialerManagerAdapter,
+            SubscriptionManagerAdapter subscriptionManagerAdapter,
             TelecomSystem.SyncRoot lock) {
         mContext = context;
         mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
@@ -966,6 +1213,10 @@
         mCallsManager = callsManager;
         mLock = lock;
         mPhoneAccountRegistrar = phoneAccountRegistrar;
+        mUserCallIntentProcessorFactory = userCallIntentProcessorFactory;
+        mDefaultDialerManagerAdapter = defaultDialerManagerAdapter;
+        mCallIntentProcessorAdapter = callIntentProcessorAdapter;
+        mSubscriptionManagerAdapter = subscriptionManagerAdapter;
     }
 
     public ITelecomService.Stub getBinder() {
@@ -976,63 +1227,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() {
@@ -1154,6 +1351,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)) {
@@ -1200,10 +1405,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);
         }
@@ -1221,11 +1426,11 @@
 
     private boolean isPrivilegedDialerCalling(String callingPackage) {
         mAppOpsManager.checkPackage(Binder.getCallingUid(), callingPackage);
-        return DefaultDialerManager.isDefaultOrSystemDialer(mContext, callingPackage);
+        return mDefaultDialerManagerAdapter.isDefaultOrSystemDialer(mContext, callingPackage);
     }
 
     private TelephonyManager getTelephonyManager() {
-        return (TelephonyManager)mContext.getSystemService(Context.TELEPHONY_SERVICE);
+        return (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
     }
 
     /**
diff --git a/src/com/android/server/telecom/TelecomSystem.java b/src/com/android/server/telecom/TelecomSystem.java
index c3ab0dc..f484d6e 100644
--- a/src/com/android/server/telecom/TelecomSystem.java
+++ b/src/com/android/server/telecom/TelecomSystem.java
@@ -17,13 +17,23 @@
 package com.android.server.telecom;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.telecom.components.UserCallIntentProcessor;
+import com.android.server.telecom.components.UserCallIntentProcessorFactory;
+import com.android.server.telecom.ui.MissedCallNotifierImpl.MissedCallNotifierImplFactory;
+import com.android.server.telecom.BluetoothPhoneServiceImpl.BluetoothPhoneServiceImplFactory;
+import com.android.server.telecom.CallAudioManager.AudioServiceFactory;
 
+import android.Manifest;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.net.Uri;
 import android.os.UserHandle;
 
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+
 /**
  * Top-level Application class for Telecom.
  */
@@ -54,6 +64,28 @@
     private static final IntentFilter USER_SWITCHED_FILTER =
             new IntentFilter(Intent.ACTION_USER_SWITCHED);
 
+    private static final IntentFilter USER_STARTING_FILTER =
+            new IntentFilter(Intent.ACTION_USER_STARTING);
+
+    /** 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,13 +99,34 @@
     private final TelecomBroadcastIntentProcessor mTelecomBroadcastIntentProcessor;
     private final TelecomServiceImpl mTelecomServiceImpl;
     private final ContactsAsyncHelper mContactsAsyncHelper;
+    private final DialerCodeReceiver mDialerCodeReceiver;
 
     private final BroadcastReceiver mUserSwitchedReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            int userHandleId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
-            UserHandle currentUserHandle = new UserHandle(userHandleId);
-            mPhoneAccountRegistrar.setCurrentUserHandle(currentUserHandle);
+            Log.startSession("TSSwR.oR");
+            try {
+                int userHandleId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
+                UserHandle currentUserHandle = new UserHandle(userHandleId);
+                mPhoneAccountRegistrar.setCurrentUserHandle(currentUserHandle);
+                mCallsManager.onUserSwitch(currentUserHandle);
+            } finally {
+                Log.endSession();
+            }
+        }
+    };
+
+    private final BroadcastReceiver mUserStartingReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            Log.startSession("TSStR.oR");
+            try {
+                int userHandleId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
+                UserHandle addingUserHandle = new UserHandle(userHandleId);
+                mCallsManager.onUserStarting(addingUserHandle);
+            } finally {
+                Log.endSession();
+            }
         }
     };
 
@@ -91,16 +144,33 @@
 
     public TelecomSystem(
             Context context,
-            MissedCallNotifier missedCallNotifier,
+            MissedCallNotifierImplFactory missedCallNotifierImplFactory,
             CallerInfoAsyncQueryFactory callerInfoAsyncQueryFactory,
             HeadsetMediaButtonFactory headsetMediaButtonFactory,
             ProximitySensorManagerFactory proximitySensorManagerFactory,
-            InCallWakeLockControllerFactory inCallWakeLockControllerFactory) {
+            InCallWakeLockControllerFactory inCallWakeLockControllerFactory,
+            AudioServiceFactory audioServiceFactory,
+            BluetoothPhoneServiceImplFactory
+                    bluetoothPhoneServiceImplFactory) {
         mContext = context.getApplicationContext();
+        Log.setContext(mContext);
+        Log.initMd5Sum();
 
-        mMissedCallNotifier = missedCallNotifier;
         mPhoneAccountRegistrar = new PhoneAccountRegistrar(mContext);
-        mContactsAsyncHelper = new ContactsAsyncHelper(mLock);
+        mContactsAsyncHelper = new ContactsAsyncHelper(
+                new ContactsAsyncHelper.ContentResolverAdapter() {
+                    @Override
+                    public InputStream openInputStream(Context context, Uri uri)
+                            throws FileNotFoundException {
+                        return context.getContentResolver().openInputStream(uri);
+                    }
+                });
+        BluetoothManager bluetoothManager = new BluetoothManager(mContext);
+        WiredHeadsetManager wiredHeadsetManager = new WiredHeadsetManager(mContext);
+        SystemStateProvider systemStateProvider = new SystemStateProvider(mContext);
+
+        mMissedCallNotifier = missedCallNotifierImplFactory
+                .makeMissedCallNotifierImpl(mContext, mPhoneAccountRegistrar);
 
         mCallsManager = new CallsManager(
                 mContext,
@@ -111,19 +181,41 @@
                 mPhoneAccountRegistrar,
                 headsetMediaButtonFactory,
                 proximitySensorManagerFactory,
-                inCallWakeLockControllerFactory);
+                inCallWakeLockControllerFactory,
+                audioServiceFactory,
+                bluetoothManager,
+                wiredHeadsetManager,
+                systemStateProvider);
 
         mRespondViaSmsManager = new RespondViaSmsManager(mCallsManager, mLock);
         mCallsManager.setRespondViaSmsManager(mRespondViaSmsManager);
 
         mContext.registerReceiver(mUserSwitchedReceiver, USER_SWITCHED_FILTER);
-        mBluetoothPhoneServiceImpl = new BluetoothPhoneServiceImpl(
+        mContext.registerReceiver(mUserStartingReceiver, USER_STARTING_FILTER);
+
+        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);
+                mContext, mCallsManager, mPhoneAccountRegistrar,
+                new CallIntentProcessor.AdapterImpl(),
+                new UserCallIntentProcessorFactory() {
+                    @Override
+                    public UserCallIntentProcessor create(Context context, UserHandle userHandle) {
+                        return new UserCallIntentProcessor(context, userHandle);
+                    }
+                },
+                new TelecomServiceImpl.DefaultDialerManagerAdapterImpl(),
+                new TelecomServiceImpl.SubscriptionManagerAdapterImpl(),
+                mLock);
     }
 
     @VisibleForTesting
diff --git a/src/com/android/server/telecom/TelecomWakeLock.java b/src/com/android/server/telecom/TelecomWakeLock.java
new file mode 100644
index 0000000..9fca19e
--- /dev/null
+++ b/src/com/android/server/telecom/TelecomWakeLock.java
@@ -0,0 +1,133 @@
+/*
+ * 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.os.PowerManager;
+
+/**
+ * Container for PowerManager / PowerManager.WakeLock access in telecom to facilitate unit testing.
+ */
+public class TelecomWakeLock {
+
+    public class WakeLockAdapter {
+
+        private PowerManager.WakeLock mWakeLock;
+
+        public WakeLockAdapter(PowerManager.WakeLock wakeLock) {
+            mWakeLock = wakeLock;
+        }
+
+        public void acquire() {
+            mWakeLock.acquire();
+        }
+
+        public boolean isHeld() {
+            return mWakeLock.isHeld();
+        }
+
+        public void release(int flags) {
+            mWakeLock.release(flags);
+        }
+
+        public void setReferenceCounted(boolean isReferencedCounted){
+            mWakeLock.setReferenceCounted(isReferencedCounted);
+        }
+
+    }
+
+    private static final String TAG = "TelecomWakeLock";
+
+    private Context mContext;
+    private int mWakeLockLevel;
+    private String mWakeLockTag;
+    private WakeLockAdapter mWakeLock;
+
+    public TelecomWakeLock(Context context, int wakeLockLevel, String wakeLockTag) {
+        mContext = context;
+        mWakeLockLevel = wakeLockLevel;
+        mWakeLockTag = wakeLockTag;
+        mWakeLock = getWakeLockFromPowerManager();
+    }
+
+    // Used For Testing
+    public TelecomWakeLock(Context context, WakeLockAdapter wakeLockAdapter, int wakeLockLevel,
+            String wakeLockTag) {
+        mContext = context;
+        mWakeLockLevel = wakeLockLevel;
+        mWakeLockTag = wakeLockTag;
+        mWakeLock = wakeLockAdapter;
+    }
+
+    private WakeLockAdapter getWakeLockFromPowerManager() {
+        PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+        WakeLockAdapter adapter = null;
+        if(powerManager.isWakeLockLevelSupported(mWakeLockLevel)) {
+            PowerManager.WakeLock wakeLock = powerManager.newWakeLock(mWakeLockLevel, mWakeLockTag);
+            adapter = new WakeLockAdapter(wakeLock);
+        }
+        return adapter;
+    }
+
+    public boolean isHeld() {
+        return mWakeLock != null && mWakeLock.isHeld();
+    }
+
+    public void acquire() {
+        if(mWakeLock == null) {
+            Log.i(TAG, "Can not acquire WakeLock (not supported) with level: " + mWakeLockLevel);
+            return;
+        }
+
+        if (!isHeld()) {
+            mWakeLock.acquire();
+            Log.i(TAG, "Acquiring WakeLock with id: " + mWakeLockLevel);
+        } else {
+            Log.i(TAG, "WakeLock already acquired for id: " + mWakeLockLevel);
+        }
+    }
+
+    public void release(int flags) {
+        if (mWakeLock == null) {
+            Log.i(TAG, "Can not release WakeLock (not supported) with id: " + mWakeLockLevel);
+            return;
+        }
+
+        if (isHeld()) {
+            mWakeLock.release(flags);
+            Log.i(TAG, "Releasing WakeLock with id: " + mWakeLockLevel);
+        } else {
+            Log.i(TAG, "WakeLock already released with id: " + mWakeLockLevel);
+        }
+    }
+
+    public void setReferenceCounted(boolean isReferencedCounted) {
+        if (mWakeLock == null) {
+            return;
+        }
+        mWakeLock.setReferenceCounted(isReferencedCounted);
+    }
+
+    @Override
+    public String toString() {
+        if(mWakeLock != null) {
+            return mWakeLock.toString();
+        } else {
+            return "null";
+        }
+    }
+}
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..9824f88 100644
--- a/src/com/android/server/telecom/Timeouts.java
+++ b/src/com/android/server/telecom/Timeouts.java
@@ -98,4 +98,37 @@
         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",
+                Log.DEFAULT_SESSION_TIMEOUT_MS);
+    }
+
+    /**
+     * Returns the amount of time to wait for the call screening service to allow or disallow a
+     * call.
+     */
+    public static long getCallScreeningTimeoutMillis(ContentResolver contentResolver) {
+        return get(contentResolver, "call_screening_timeout", 5000L /* 5 seconds */);
+    }
+
+    /**
+     * Returns the amount of time to wait for the block checker to allow or disallow a call.
+     */
+    public static long getBlockCheckTimeoutMillis(ContentResolver contentResolver) {
+        return get(contentResolver, "block_check_timeout_millis", 500L);
+    }
 }
diff --git a/src/com/android/server/telecom/TtyManager.java b/src/com/android/server/telecom/TtyManager.java
index e0a9e9c..25284e4 100644
--- a/src/com/android/server/telecom/TtyManager.java
+++ b/src/com/android/server/telecom/TtyManager.java
@@ -112,15 +112,20 @@
     private final class TtyBroadcastReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {
-            String action = intent.getAction();
-            Log.v(TtyManager.this, "onReceive, action: %s", action);
-            if (action.equals(TelecomManager.ACTION_TTY_PREFERRED_MODE_CHANGED)) {
-                int newPreferredTtyMode = intent.getIntExtra(
-                        TelecomManager.EXTRA_TTY_PREFERRED_MODE, TelecomManager.TTY_MODE_OFF);
-                if (mPreferredTtyMode != newPreferredTtyMode) {
-                    mPreferredTtyMode = newPreferredTtyMode;
-                    updateCurrentTtyMode();
+            Log.startSession("TBR.oR");
+            try {
+                String action = intent.getAction();
+                Log.v(TtyManager.this, "onReceive, action: %s", action);
+                if (action.equals(TelecomManager.ACTION_TTY_PREFERRED_MODE_CHANGED)) {
+                    int newPreferredTtyMode = intent.getIntExtra(
+                            TelecomManager.EXTRA_TTY_PREFERRED_MODE, TelecomManager.TTY_MODE_OFF);
+                    if (mPreferredTtyMode != newPreferredTtyMode) {
+                        mPreferredTtyMode = newPreferredTtyMode;
+                        updateCurrentTtyMode();
+                    }
                 }
+            } finally {
+                Log.endSession();
             }
         }
     }
diff --git a/src/com/android/server/telecom/UserUtil.java b/src/com/android/server/telecom/UserUtil.java
new file mode 100644
index 0000000..a304401
--- /dev/null
+++ b/src/com/android/server/telecom/UserUtil.java
@@ -0,0 +1,43 @@
+/*
+ * 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();
+    }
+
+    public static boolean isProfile(Context context, UserHandle userHandle) {
+        UserInfo userInfo = getUserInfoFromUserHandle(context, userHandle);
+        return userInfo != null && userInfo.profileGroupId != userInfo.id;
+    }
+}
diff --git a/src/com/android/server/telecom/VideoProviderProxy.java b/src/com/android/server/telecom/VideoProviderProxy.java
index b531242..9d17139 100644
--- a/src/com/android/server/telecom/VideoProviderProxy.java
+++ b/src/com/android/server/telecom/VideoProviderProxy.java
@@ -126,15 +126,20 @@
          */
         @Override
         public void receiveSessionModifyRequest(VideoProfile videoProfile) {
-            synchronized (mLock) {
-                logFromVideoProvider("receiveSessionModifyRequest: " + videoProfile);
+            try {
+                Log.startSession("VPP.rSMR");
+                synchronized (mLock) {
+                    logFromVideoProvider("receiveSessionModifyRequest: " + videoProfile);
 
-                // Inform other Telecom components of the session modification request.
-                for (Listener listener : mListeners) {
-                    listener.onSessionModifyRequestReceived(mCall, videoProfile);
+                    // Inform other Telecom components of the session modification request.
+                    for (Listener listener : mListeners) {
+                        listener.onSessionModifyRequestReceived(mCall, videoProfile);
+                    }
+
+                    VideoProviderProxy.this.receiveSessionModifyRequest(videoProfile);
                 }
-
-                VideoProviderProxy.this.receiveSessionModifyRequest(videoProfile);
+            } finally {
+                Log.endSession();
             }
         }
 
diff --git a/src/com/android/server/telecom/WiredHeadsetManager.java b/src/com/android/server/telecom/WiredHeadsetManager.java
index ef5f38c..253dfca 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);
     }
 
@@ -38,11 +41,16 @@
     private class WiredHeadsetBroadcastReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {
-            if (intent.getAction().equals(Intent.ACTION_HEADSET_PLUG)) {
-                boolean isPluggedIn = mAudioManager.isWiredHeadsetOn();
-                Log.v(WiredHeadsetManager.this, "ACTION_HEADSET_PLUG event, plugged in: %b",
-                        isPluggedIn);
-                onHeadsetPluggedInChanged(isPluggedIn);
+            Log.startSession("WHBR.oR");
+            try {
+                if (intent.getAction().equals(Intent.ACTION_HEADSET_PLUG)) {
+                    boolean isPluggedIn = mAudioManager.isWiredHeadsetOn();
+                    Log.v(WiredHeadsetManager.this, "ACTION_HEADSET_PLUG event, plugged in: %b",
+                            isPluggedIn);
+                    onHeadsetPluggedInChanged(isPluggedIn);
+                }
+            } finally {
+                Log.endSession();
             }
         }
     }
@@ -58,7 +66,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 +77,8 @@
         context.registerReceiver(mReceiver, intentFilter);
     }
 
-    void addListener(Listener listener) {
+    @VisibleForTesting
+    public void addListener(Listener listener) {
         mListeners.add(listener);
     }
 
@@ -79,7 +88,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..8e4ae57 100644
--- a/src/com/android/server/telecom/components/TelecomService.java
+++ b/src/com/android/server/telecom/components/TelecomService.java
@@ -20,19 +20,26 @@
 import android.bluetooth.BluetoothAdapter;
 import android.content.Context;
 import android.content.Intent;
+import android.media.IAudioService;
 import android.os.IBinder;
+import android.os.PowerManager;
+import android.os.ServiceManager;
 
 import com.android.internal.telephony.CallerInfoAsyncQuery;
+import com.android.server.telecom.BluetoothPhoneServiceImpl;
 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.CallAudioManager;
+import com.android.server.telecom.PhoneAccountRegistrar;
 import com.android.server.telecom.ProximitySensorManagerFactory;
 import com.android.server.telecom.InCallWakeLockController;
 import com.android.server.telecom.Log;
 import com.android.server.telecom.ProximitySensorManager;
 import com.android.server.telecom.TelecomSystem;
+import com.android.server.telecom.TelecomWakeLock;
 import com.android.server.telecom.ui.MissedCallNotifierImpl;
 
 /**
@@ -64,7 +71,15 @@
             TelecomSystem.setInstance(
                     new TelecomSystem(
                             context,
-                            new MissedCallNotifierImpl(context.getApplicationContext()),
+                            new MissedCallNotifierImpl.MissedCallNotifierImplFactory() {
+                                @Override
+                                public MissedCallNotifierImpl makeMissedCallNotifierImpl(
+                                        Context context,
+                                        PhoneAccountRegistrar phoneAccountRegistrar) {
+                                    return new MissedCallNotifierImpl(context,
+                                            phoneAccountRegistrar);
+                                }
+                            },
                             new CallerInfoAsyncQueryFactory() {
                                 @Override
                                 public CallerInfoAsyncQuery startQuery(int token, Context context,
@@ -92,16 +107,43 @@
                                 public ProximitySensorManager create(
                                         Context context,
                                         CallsManager callsManager) {
-                                    return new ProximitySensorManager(context, callsManager);
+                                    return new ProximitySensorManager(
+                                            new TelecomWakeLock(
+                                                    context,
+                                                    PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK,
+                                                    ProximitySensorManager.class.getSimpleName()),
+                                            callsManager);
                                 }
                             },
                             new InCallWakeLockControllerFactory() {
                                 @Override
                                 public InCallWakeLockController create(Context context,
                                         CallsManager callsManager) {
-                                    return new InCallWakeLockController(context, callsManager);
+                                    return new InCallWakeLockController(
+                                            new TelecomWakeLock(context,
+                                                    PowerManager.FULL_WAKE_LOCK,
+                                                    InCallWakeLockController.class.getSimpleName()),
+                                            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 f6a0027..65258d9 100644
--- a/src/com/android/server/telecom/components/UserCallIntentProcessor.java
+++ b/src/com/android/server/telecom/components/UserCallIntentProcessor.java
@@ -125,6 +125,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);
     }
 
@@ -162,7 +166,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/components/UserCallIntentProcessorFactory.java b/src/com/android/server/telecom/components/UserCallIntentProcessorFactory.java
new file mode 100644
index 0000000..d37cd06
--- /dev/null
+++ b/src/com/android/server/telecom/components/UserCallIntentProcessorFactory.java
@@ -0,0 +1,24 @@
+/*
+ * 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.components;
+
+import android.content.Context;
+import android.os.UserHandle;
+
+public interface UserCallIntentProcessorFactory {
+    UserCallIntentProcessor create(Context context, UserHandle userHandle);
+}
diff --git a/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java b/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java
index a864a83..67ca641 100644
--- a/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java
+++ b/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java
@@ -16,8 +16,14 @@
 
 package com.android.server.telecom.ui;
 
+import static android.Manifest.permission.READ_PHONE_STATE;
+
 import android.content.ComponentName;
+import android.content.ContentProvider;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
+
 import com.android.server.telecom.Call;
 import com.android.server.telecom.CallState;
 import com.android.server.telecom.CallerInfoAsyncQueryFactory;
@@ -27,7 +33,9 @@
 import com.android.server.telecom.ContactsAsyncHelper;
 import com.android.server.telecom.Log;
 import com.android.server.telecom.MissedCallNotifier;
+import com.android.server.telecom.PhoneAccountRegistrar;
 import com.android.server.telecom.R;
+import com.android.server.telecom.Runnable;
 import com.android.server.telecom.TelecomBroadcastIntentProcessor;
 import com.android.server.telecom.TelecomSystem;
 import com.android.server.telecom.components.TelecomBroadcastReceiver;
@@ -40,6 +48,7 @@
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.ResolveInfo;
 import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.graphics.drawable.BitmapDrawable;
@@ -49,6 +58,7 @@
 import android.os.Binder;
 import android.os.UserHandle;
 import android.provider.CallLog.Calls;
+import android.telecom.DefaultDialerManager;
 import android.telecom.DisconnectCause;
 import android.telecom.PhoneAccount;
 import android.telephony.PhoneNumberUtils;
@@ -57,9 +67,15 @@
 import android.text.TextDirectionHeuristics;
 import android.text.TextUtils;
 
+import com.android.internal.telephony.CallerInfo;
+
 import java.lang.Override;
 import java.lang.String;
+import java.util.List;
 import java.util.Locale;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicInteger;
 
 // TODO: Needed for move to system service: import com.android.internal.R;
 
@@ -75,6 +91,24 @@
  */
 public class MissedCallNotifierImpl extends CallsManagerListenerBase implements MissedCallNotifier {
 
+    public interface MissedCallNotifierImplFactory {
+        MissedCallNotifier makeMissedCallNotifierImpl(Context context,
+                PhoneAccountRegistrar phoneAccountRegistrar);
+    }
+
+    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,
@@ -94,21 +128,32 @@
     private static final int MISSED_CALL_NOTIFICATION_ID = 1;
 
     private final Context mContext;
+    private final PhoneAccountRegistrar mPhoneAccountRegistrar;
     private final NotificationManager mNotificationManager;
-
+    private final NotificationBuilderFactory mNotificationBuilderFactory;
     private final ComponentName mNotificationComponent;
+    private UserHandle mCurrentUserHandle;
 
     // Used to track the number of missed calls.
-    private int mMissedCallCount = 0;
+    private ConcurrentMap<UserHandle, AtomicInteger> mMissedCallCounts;
 
-    public MissedCallNotifierImpl(Context context) {
+    public MissedCallNotifierImpl(Context context, PhoneAccountRegistrar phoneAccountRegistrar) {
+        this(context, phoneAccountRegistrar, new DefaultNotificationBuilderFactory());
+    }
+
+    public MissedCallNotifierImpl(Context context,
+            PhoneAccountRegistrar phoneAccountRegistrar,
+            NotificationBuilderFactory notificationBuilderFactory) {
         mContext = context;
+        mPhoneAccountRegistrar = phoneAccountRegistrar;
         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;
+        mMissedCallCounts = new ConcurrentHashMap<>();
     }
 
     /** {@inheritDoc} */
@@ -122,10 +167,19 @@
 
     /** Clears missed call notification and marks the call log's missed calls as read. */
     @Override
-    public void clearMissedCalls() {
-        AsyncTask.execute(new Runnable() {
+    public void clearMissedCalls(UserHandle userHandle) {
+        // If the default dialer is showing the missed call notification then it will modify the
+        // call log and we don't have to do anything here.
+        if (!shouldManageNotificationThroughDefaultDialer(userHandle)) {
+            markMissedCallsAsRead(userHandle);
+        }
+        cancelMissedCallNotification(userHandle);
+    }
+
+    private void markMissedCallsAsRead(final UserHandle userHandle) {
+        AsyncTask.execute(new Runnable("MCNI.mMCAR") {
             @Override
-            public void run() {
+            public void loggedRun() {
                 // Clear the list of new missed calls from the call log.
                 ContentValues values = new ContentValues();
                 values.put(Calls.NEW, 0);
@@ -136,26 +190,27 @@
                 where.append(Calls.TYPE);
                 where.append(" = ?");
                 try {
-                    mContext.getContentResolver().update(Calls.CONTENT_URI, values,
+                    Uri callsUri = ContentProvider
+                            .maybeAddUserId(Calls.CONTENT_URI, userHandle.getIdentifier());
+                    mContext.getContentResolver().update(callsUri, values,
                             where.toString(), new String[]{ Integer.toString(Calls.
                             MISSED_TYPE) });
                 } catch (IllegalArgumentException e) {
                     Log.w(this, "ContactsProvider update command failed", e);
                 }
             }
-        });
-        cancelMissedCallNotification();
+        }.prepare());
     }
 
     /**
      * 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.
+     * Currently the component is set in phone capable android wear device.
+     * @param userHandle The user that has the missed call(s).
      * @return {@code true} if the broadcast was sent. {@code false} otherwise.
      */
-    private boolean sendNotificationCustomComponent(Call call, int count) {
+    private boolean sendNotificationCustomComponent(Call call, UserHandle userHandle) {
         if (mNotificationComponent != null) {
+            int count = mMissedCallCounts.get(userHandle).get();
             Intent intent = new Intent();
             intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
             intent.setComponent(mNotificationComponent);
@@ -164,7 +219,7 @@
             intent.putExtra(TelecomManager.EXTRA_NOTIFICATION_PHONE_NUMBER,
                     call != null ? call.getPhoneNumber() : null);
             intent.putExtra(TelecomManager.EXTRA_CLEAR_MISSED_CALLS_INTENT,
-                    createClearMissedCallsPendingIntent());
+                    createClearMissedCallsPendingIntent(userHandle));
 
 
             if (count == 1 && call != null) {
@@ -174,7 +229,7 @@
                 if (!TextUtils.isEmpty(handle) && !TextUtils.equals(handle,
                         mContext.getString(R.string.handle_restricted))) {
                     intent.putExtra(TelecomManager.EXTRA_CALL_BACK_INTENT,
-                            createCallBackPendingIntent(handleUri));
+                            createCallBackPendingIntent(handleUri, userHandle));
                 }
             }
 
@@ -186,15 +241,77 @@
     }
 
     /**
+     * Returns the missed-call notificatino intent to send to the default dialer for the given user.     * Note, the passed in userHandle is always the non-managed user for SIM calls (multi-user
+     * calls). In this case we return the default dialer for the logged in user. This is never the
+     * managed (work profile) dialer.
+     *
+     * For non-multi-user calls (3rd party phone accounts), the passed in userHandle is the user
+     * handle of the phone account. This could be a managed user. In that case we return the default
+     * dialer for the given user which could be a managed (work profile) dialer.
+     */
+    private Intent getShowMissedCallIntentForDefaultDialer(UserHandle userHandle) {
+        String dialerPackage = DefaultDialerManager
+                .getDefaultDialerApplication(mContext, userHandle.getIdentifier());
+        if (TextUtils.isEmpty(dialerPackage)) {
+            return null;
+        }
+        return new Intent(TelecomManager.ACTION_SHOW_MISSED_CALLS_NOTIFICATION)
+            .setPackage(dialerPackage);
+    }
+
+    private boolean shouldManageNotificationThroughDefaultDialer(UserHandle userHandle) {
+        Intent intent = getShowMissedCallIntentForDefaultDialer(userHandle);
+        if (intent == null) {
+            return false;
+        }
+
+        List<ResolveInfo> receivers = mContext.getPackageManager()
+                .queryBroadcastReceiversAsUser(intent, 0, userHandle.getIdentifier());
+        return receivers.size() > 0;
+    }
+
+    private void sendNotificationThroughDefaultDialer(Call call, UserHandle userHandle) {
+        int count = mMissedCallCounts.get(userHandle).get();
+        Intent intent = getShowMissedCallIntentForDefaultDialer(userHandle)
+            .setFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+            .putExtra(TelecomManager.EXTRA_NOTIFICATION_COUNT, count)
+            .putExtra(TelecomManager.EXTRA_NOTIFICATION_PHONE_NUMBER,
+                    call != null ? call.getPhoneNumber() : null);
+
+        Log.w(this, "Showing missed calls through default dialer.");
+        mContext.sendBroadcastAsUser(intent, userHandle, READ_PHONE_STATE);
+    }
+
+    /**
      * Create a system notification for the missed call.
      *
      * @param call The missed call.
      */
     @Override
     public void showMissedCallNotification(Call call) {
-        mMissedCallCount++;
+        final PhoneAccountHandle phoneAccountHandle = call.getTargetPhoneAccount();
+        final PhoneAccount phoneAccount =
+                mPhoneAccountRegistrar.getPhoneAccountUnchecked(phoneAccountHandle);
+        UserHandle userHandle;
+        if (phoneAccount != null &&
+                phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_MULTI_USER)) {
+            userHandle = mCurrentUserHandle;
+        } else {
+            userHandle = phoneAccountHandle.getUserHandle();
+        }
+        showMissedCallNotification(call, userHandle);
+    }
 
-        if (sendNotificationCustomComponent(call, mMissedCallCount)) {
+    private void showMissedCallNotification(Call call, UserHandle userHandle) {
+        mMissedCallCounts.putIfAbsent(userHandle, new AtomicInteger(0));
+        int missCallCounts = mMissedCallCounts.get(userHandle).incrementAndGet();
+
+        if (sendNotificationCustomComponent(call, userHandle)) {
+            return;
+        }
+
+        if (shouldManageNotificationThroughDefaultDialer(userHandle)) {
+            sendNotificationThroughDefaultDialer(call, userHandle);
             return;
         }
 
@@ -204,18 +321,26 @@
         // Display the first line of the notification:
         // 1 missed call: <caller name || handle>
         // More than 1 missed call: <number of calls> + "missed calls"
-        if (mMissedCallCount == 1) {
-            titleResId = R.string.notification_missedCallTitle;
+        if (missCallCounts == 1) {
             expandedText = getNameForCall(call);
+
+            CallerInfo ci = call.getCallerInfo();
+            if (ci != null && ci.userType == CallerInfo.USER_TYPE_WORK) {
+                titleResId = R.string.notification_missedWorkCallTitle;
+            } else {
+                titleResId = R.string.notification_missedCallTitle;
+            }
         } else {
             titleResId = R.string.notification_missedCallsTitle;
             expandedText =
-                    mContext.getString(R.string.notification_missedCallsMsg, mMissedCallCount);
+                    mContext.getString(R.string.notification_missedCallsMsg, missCallCounts);
         }
 
         // Create a public viewable version of the notification, suitable for display when sensitive
         // notification content is hidden.
-        Notification.Builder publicBuilder = new Notification.Builder(mContext);
+        // We use user's context here to make sure notification is badged if it is a managed user.
+        Context contextForUser = getContextForUser(userHandle);
+        Notification.Builder publicBuilder = mNotificationBuilderFactory.getBuilder(contextForUser);
         publicBuilder.setSmallIcon(android.R.drawable.stat_notify_missed_call)
                 .setColor(mContext.getResources().getColor(R.color.theme_color))
                 .setWhen(call.getCreationTimeMillis())
@@ -224,20 +349,20 @@
                 // Notification details shows that there are missed call(s), but does not reveal
                 // the missed caller information.
                 .setContentText(mContext.getText(titleResId))
-                .setContentIntent(createCallLogPendingIntent())
+                .setContentIntent(createCallLogPendingIntent(userHandle))
                 .setAutoCancel(true)
-                .setDeleteIntent(createClearMissedCallsPendingIntent());
+                .setDeleteIntent(createClearMissedCallsPendingIntent(userHandle));
 
         // Create the notification suitable for display when sensitive information is showing.
-        Notification.Builder builder = new Notification.Builder(mContext);
+        Notification.Builder builder = mNotificationBuilderFactory.getBuilder(contextForUser);
         builder.setSmallIcon(android.R.drawable.stat_notify_missed_call)
                 .setColor(mContext.getResources().getColor(R.color.theme_color))
                 .setWhen(call.getCreationTimeMillis())
                 .setContentTitle(mContext.getText(titleResId))
                 .setContentText(expandedText)
-                .setContentIntent(createCallLogPendingIntent())
+                .setContentIntent(createCallLogPendingIntent(userHandle))
                 .setAutoCancel(true)
-                .setDeleteIntent(createClearMissedCallsPendingIntent())
+                .setDeleteIntent(createClearMissedCallsPendingIntent(userHandle))
                 // Include a public version of the notification to be shown when the missed call
                 // notification is shown on the user's lock screen and they have chosen to hide
                 // sensitive notification information.
@@ -247,19 +372,19 @@
         String handle = handleUri == null ? null : handleUri.getSchemeSpecificPart();
 
         // Add additional actions when there is only 1 missed call, like call-back and SMS.
-        if (mMissedCallCount == 1) {
+        if (missCallCounts == 1) {
             Log.d(this, "Add actions with number %s.", Log.piiHandle(handle));
 
             if (!TextUtils.isEmpty(handle)
                     && !TextUtils.equals(handle, mContext.getString(R.string.handle_restricted))) {
                 builder.addAction(R.drawable.ic_phone_24dp,
                         mContext.getString(R.string.notification_missedCall_call_back),
-                        createCallBackPendingIntent(handleUri));
+                        createCallBackPendingIntent(handleUri, userHandle));
 
                 if (canRespondViaSms(call)) {
                     builder.addAction(R.drawable.ic_message_24dp,
                             mContext.getString(R.string.notification_missedCall_message),
-                            createSendSmsFromNotificationPendingIntent(handleUri));
+                            createSendSmsFromNotificationPendingIntent(handleUri, userHandle));
                 }
             }
 
@@ -274,7 +399,7 @@
             }
         } else {
             Log.d(this, "Suppress actions. handle: %s, missedCalls: %d.", Log.piiHandle(handle),
-                    mMissedCallCount);
+                    missCallCounts);
         }
 
         Notification notification = builder.build();
@@ -284,26 +409,31 @@
         long token = Binder.clearCallingIdentity();
         try {
             mNotificationManager.notifyAsUser(
-                    null /* tag */, MISSED_CALL_NOTIFICATION_ID, notification, UserHandle.CURRENT);
+                    null /* tag */, MISSED_CALL_NOTIFICATION_ID, notification, userHandle);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
     }
 
+
     /** Cancels the "missed call" notification. */
-    private void cancelMissedCallNotification() {
+    private void cancelMissedCallNotification(UserHandle userHandle) {
         // Reset the number of missed calls to 0.
-        mMissedCallCount = 0;
+        mMissedCallCounts.putIfAbsent(userHandle, new AtomicInteger(0));
+        mMissedCallCounts.get(userHandle).set(0);
 
+        if (sendNotificationCustomComponent(null, userHandle)) {
+            return;
+        }
 
-        if (sendNotificationCustomComponent(null, mMissedCallCount)) {
+        if (shouldManageNotificationThroughDefaultDialer(userHandle)) {
+            sendNotificationThroughDefaultDialer(null, userHandle);
             return;
         }
 
         long token = Binder.clearCallingIdentity();
         try {
-            mNotificationManager.cancelAsUser(null, MISSED_CALL_NOTIFICATION_ID,
-                    UserHandle.CURRENT);
+            mNotificationManager.cancelAsUser(null, MISSED_CALL_NOTIFICATION_ID, userHandle);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
@@ -367,22 +497,22 @@
      *
      * @return The pending intent.
      */
-    private PendingIntent createCallLogPendingIntent() {
+    private PendingIntent createCallLogPendingIntent(UserHandle userHandle) {
         Intent intent = new Intent(Intent.ACTION_VIEW, null);
         intent.setType(Calls.CONTENT_TYPE);
 
         TaskStackBuilder taskStackBuilder = TaskStackBuilder.create(mContext);
         taskStackBuilder.addNextIntent(intent);
 
-        return taskStackBuilder.getPendingIntent(0, 0, null, UserHandle.CURRENT);
+        return taskStackBuilder.getPendingIntent(0, 0, null, userHandle);
     }
 
     /**
      * Creates an intent to be invoked when the missed call notification is cleared.
      */
-    private PendingIntent createClearMissedCallsPendingIntent() {
+    private PendingIntent createClearMissedCallsPendingIntent(UserHandle userHandle) {
         return createTelecomPendingIntent(
-                TelecomBroadcastIntentProcessor.ACTION_CLEAR_MISSED_CALLS, null);
+                TelecomBroadcastIntentProcessor.ACTION_CLEAR_MISSED_CALLS, null, userHandle);
     }
 
     /**
@@ -391,19 +521,22 @@
      *
      * @param handle The handle to call back.
      */
-    private PendingIntent createCallBackPendingIntent(Uri handle) {
+    private PendingIntent createCallBackPendingIntent(Uri handle, UserHandle userHandle) {
         return createTelecomPendingIntent(
-                TelecomBroadcastIntentProcessor.ACTION_CALL_BACK_FROM_NOTIFICATION, handle);
+                TelecomBroadcastIntentProcessor.ACTION_CALL_BACK_FROM_NOTIFICATION, handle,
+                userHandle);
     }
 
     /**
      * Creates an intent to be invoked when the user opts to "send sms" from the missed call
      * notification.
      */
-    private PendingIntent createSendSmsFromNotificationPendingIntent(Uri handle) {
+    private PendingIntent createSendSmsFromNotificationPendingIntent(Uri handle,
+            UserHandle userHandle) {
         return createTelecomPendingIntent(
                 TelecomBroadcastIntentProcessor.ACTION_SEND_SMS_FROM_NOTIFICATION,
-                Uri.fromParts(Constants.SCHEME_SMSTO, handle.getSchemeSpecificPart(), null));
+                Uri.fromParts(Constants.SCHEME_SMSTO, handle.getSchemeSpecificPart(), null),
+                userHandle);
     }
 
     /**
@@ -413,8 +546,10 @@
      * @param action The intent action.
      * @param data The intent data.
      */
-    private PendingIntent createTelecomPendingIntent(String action, Uri data) {
+    private PendingIntent createTelecomPendingIntent(String action, Uri data,
+            UserHandle userHandle) {
         Intent intent = new Intent(action, data, mContext, TelecomBroadcastReceiver.class);
+        intent.putExtra(TelecomBroadcastIntentProcessor.EXTRA_USERHANDLE, userHandle);
         return PendingIntent.getBroadcast(mContext, 0, intent, 0);
     }
 
@@ -436,12 +571,13 @@
      * Adds the missed call notification on startup if there are unread missed calls.
      */
     @Override
-    public void updateOnStartup(
+    public void reloadFromDatabase(
             final TelecomSystem.SyncRoot lock,
             final CallsManager callsManager,
             final ContactsAsyncHelper contactsAsyncHelper,
-            final CallerInfoAsyncQueryFactory callerInfoAsyncQueryFactory) {
-        Log.d(this, "updateOnStartup()...");
+            final CallerInfoAsyncQueryFactory callerInfoAsyncQueryFactory,
+            final UserHandle userHandle) {
+        Log.d(this, "reloadFromDatabase()...");
 
         // instantiate query handler
         AsyncQueryHandler queryHandler = new AsyncQueryHandler(mContext.getContentResolver()) {
@@ -450,6 +586,7 @@
                 Log.d(MissedCallNotifierImpl.this, "onQueryComplete()...");
                 if (cursor != null) {
                     try {
+                        mMissedCallCounts.remove(userHandle);
                         while (cursor.moveToNext()) {
                             // Get data about the missed call from the cursor
                             final String handleString = cursor.getString(CALL_LOG_COLUMN_NUMBER);
@@ -470,9 +607,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");
@@ -487,7 +625,7 @@
                                                 this);  // No longer need to listen to call
                                         // changes after the contact info
                                         // is retrieved.
-                                        showMissedCallNotification(call);
+                                        showMissedCallNotification(call, userHandle);
                                     }
                                 });
                                 // Set the handle here because that is what triggers the contact
@@ -508,8 +646,24 @@
         where.append(" AND new=1");
         where.append(" AND is_read=0");
 
+        Uri callsUri =
+                ContentProvider.maybeAddUserId(Calls.CONTENT_URI, userHandle.getIdentifier());
         // start the query
-        queryHandler.startQuery(0, null, Calls.CONTENT_URI, CALL_LOG_PROJECTION,
+        queryHandler.startQuery(0, null, callsUri, CALL_LOG_PROJECTION,
                 where.toString(), null, Calls.DEFAULT_SORT_ORDER);
     }
+
+    @Override
+    public void setCurrentUserHandle(UserHandle currentUserHandle) {
+        mCurrentUserHandle = currentUserHandle;
+    }
+
+    private Context getContextForUser(UserHandle user) {
+        try {
+            return mContext.createPackageContextAsUser(mContext.getPackageName(), 0, user);
+        } catch (NameNotFoundException e) {
+            // Default to mContext, not finding the package system is running as is unlikely.
+            return mContext;
+        }
+    }
 }
diff --git a/testapps/AndroidManifest.xml b/testapps/AndroidManifest.xml
index df333a5..e645123 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" />
@@ -50,6 +54,7 @@
         <service android:name="com.android.server.telecom.testapps.TestInCallServiceImpl"
                  android:process="com.android.server.telecom.testapps.TestInCallService"
                  android:permission="android.permission.BIND_INCALL_SERVICE" >
+            <meta-data android:name="android.telecom.IN_CALL_SERVICE_CAR_MODE_UI" android:value="true" />
             <intent-filter>
                 <action android:name="android.telecom.InCallService"/>
             </intent-filter>
@@ -64,6 +69,17 @@
             </intent-filter>
         </receiver>
 
+        <activity android:name="com.android.server.telecom.testapps.TestInCallUI"
+                android:process="com.android.server.telecom.testapps.TestInCallService"
+                android:label="@string/inCallUiAppLabel"
+                android:launchMode="singleInstance">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
         <activity android:name="com.android.server.telecom.testapps.TestCallActivity"
                   android:theme="@android:style/Theme.NoDisplay"
                   android:label="@string/testCallActivityLabel">
diff --git a/testapps/res/layout/call_list_item.xml b/testapps/res/layout/call_list_item.xml
new file mode 100644
index 0000000..c9f2ff7
--- /dev/null
+++ b/testapps/res/layout/call_list_item.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical" >
+    <TextView
+            android:id="@+id/phoneNumber"
+            android:layout_gravity="left"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textSize="25dp"
+            android:text="TextView" />
+    <TextView
+            android:id="@+id/callState"
+            android:layout_gravity="left"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textSize="25dp"
+            android:text="TextView" />
+    <TextView
+            android:id="@+id/duration"
+            android:layout_gravity="right"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textSize="25dp"
+            android:text="TextView" />
+</LinearLayout>
diff --git a/testapps/res/layout/incall_screen.xml b/testapps/res/layout/incall_screen.xml
new file mode 100644
index 0000000..6a891e7
--- /dev/null
+++ b/testapps/res/layout/incall_screen.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical" >
+    <ListView
+            android:id="@+id/callListView"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:divider="#FFCC00"
+            android:dividerHeight="4px">
+    </ListView>
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="horizontal">
+        <Button
+            android:id="@+id/end_call_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/endCallButton" />
+        <Button
+            android:id="@+id/mute_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/muteButton" />
+        <Button
+            android:id="@+id/hold_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/holdButton" >
+        </Button>
+    </LinearLayout>
+</LinearLayout>
diff --git a/testapps/res/values/donottranslate_strings.xml b/testapps/res/values/donottranslate_strings.xml
index 43c302d..599d5cc 100644
--- a/testapps/res/values/donottranslate_strings.xml
+++ b/testapps/res/values/donottranslate_strings.xml
@@ -37,4 +37,12 @@
     <!-- String for button in TestDialerActivity that tries to exercise the
             TelecomManager.cancelMissedCallNotifications() functionality -->
     <string name="cancelMissedButton">Cancel missed calls</string>
+
+    <string name="endCallButton">End Call</string>
+
+    <string name="muteButton">Mute</string>
+
+    <string name="holdButton">Hold</string>
+
+    <string name="inCallUiAppLabel">Test InCall UI</string>
 </resources>
diff --git a/testapps/src/com/android/server/telecom/testapps/CallListAdapter.java b/testapps/src/com/android/server/telecom/testapps/CallListAdapter.java
new file mode 100644
index 0000000..bea0e63
--- /dev/null
+++ b/testapps/src/com/android/server/telecom/testapps/CallListAdapter.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2015 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.testapps;
+
+import android.content.Context;
+import android.net.Uri;
+import android.os.Handler;
+import android.telecom.Call;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.TextView;
+
+public class CallListAdapter extends BaseAdapter {
+    private static final String TAG = "CallListAdapter";
+
+    private final TestCallList.Listener mListener = new TestCallList.Listener() {
+        @Override
+        public void onCallAdded(Call call) {
+            notifyDataSetChanged();
+        }
+
+        @Override
+        public void onCallRemoved(Call call) {
+            notifyDataSetChanged();
+            if (mCallList.size() == 0) {
+                mCallList.removeListener(this);
+            }
+        }
+    };
+
+    private final LayoutInflater mLayoutInflater;
+    private final TestCallList mCallList;
+    private final Handler mHandler = new Handler();
+    private final Runnable mSecondsRunnable = new Runnable() {
+        @Override
+        public void run() {
+            notifyDataSetChanged();
+            if (mCallList.size() > 0) {
+                mHandler.postDelayed(this, 1000);
+            }
+        }
+    };
+
+    public CallListAdapter(Context context) {
+        mLayoutInflater =
+                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        mCallList = TestCallList.getInstance();
+        mCallList.addListener(mListener);
+        mHandler.postDelayed(mSecondsRunnable, 1000);
+    }
+
+
+    @Override
+    public int getCount() {
+        Log.i(TAG, "size reporting: " + mCallList.size());
+        return mCallList.size();
+    }
+
+    @Override
+    public Object getItem(int position) {
+        return position;
+    }
+
+    @Override
+    public long getItemId(int position) {
+        return position;
+    }
+
+    @Override
+    public View getView(final int position, View convertView, ViewGroup parent) {
+        Log.i(TAG, "getView: " + position);
+        if (convertView == null) {
+            convertView = mLayoutInflater.inflate(R.layout.call_list_item, parent, false);
+        }
+
+        TextView phoneNumber = (TextView) convertView.findViewById(R.id.phoneNumber);
+        TextView duration = (TextView) convertView.findViewById(R.id.duration);
+        TextView state = (TextView) convertView.findViewById(R.id.callState);
+
+        Call call = mCallList.getCall(position);
+        Uri handle = call.getDetails().getHandle();
+        phoneNumber.setText(handle == null ? "No number" : handle.getSchemeSpecificPart());
+
+        long durationMs = System.currentTimeMillis() - call.getDetails().getConnectTimeMillis();
+        duration.setText((durationMs / 1000) + " secs");
+
+        state.setText(getStateString(call));
+
+        Log.i(TAG, "Call found: " + handle.getSchemeSpecificPart() + ", " + durationMs);
+
+        return convertView;
+    }
+
+    private static String getStateString(Call call) {
+        switch (call.getState()) {
+            case Call.STATE_ACTIVE:
+                return "active";
+            case Call.STATE_CONNECTING:
+                return "connecting";
+            case Call.STATE_DIALING:
+                return "dialing";
+            case Call.STATE_DISCONNECTED:
+                return "disconnected";
+            case Call.STATE_DISCONNECTING:
+                return "disconnecting";
+            case Call.STATE_HOLDING:
+                return "on hold";
+            case Call.STATE_NEW:
+                return "new";
+            case Call.STATE_RINGING:
+                return "ringing";
+            case Call.STATE_SELECT_PHONE_ACCOUNT:
+                return "select phone account";
+            default:
+                return "unknown";
+        }
+    }
+}
diff --git a/testapps/src/com/android/server/telecom/testapps/CallNotificationReceiver.java b/testapps/src/com/android/server/telecom/testapps/CallNotificationReceiver.java
index 20a0475..aee5514 100644
--- a/testapps/src/com/android/server/telecom/testapps/CallNotificationReceiver.java
+++ b/testapps/src/com/android/server/telecom/testapps/CallNotificationReceiver.java
@@ -25,6 +25,7 @@
 import android.support.v4.content.LocalBroadcastManager;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
+import android.telecom.VideoProfile;
 import android.util.Log;
 
 /**
@@ -44,8 +45,10 @@
             "com.android.server.telecom.testapps.ACTION_REGISTER_PHONE_ACCOUNT";
     static final String ACTION_SHOW_ALL_PHONE_ACCOUNTS =
             "com.android.server.telecom.testapps.ACTION_SHOW_ALL_PHONE_ACCOUNTS";
-    static final String ACTION_VIDEO_CALL =
-            "com.android.server.telecom.testapps.ACTION_VIDEO_CALL";
+    static final String ACTION_ONE_WAY_VIDEO_CALL =
+            "com.android.server.telecom.testapps.ACTION_ONE_WAY_VIDEO_CALL";
+    static final String ACTION_TWO_WAY_VIDEO_CALL =
+            "com.android.server.telecom.testapps.ACTION_TWO_WAY_VIDEO_CALL";
     static final String ACTION_AUDIO_CALL =
             "com.android.server.telecom.testapps.ACTION_AUDIO_CALL";
 
@@ -59,10 +62,12 @@
             CallServiceNotifier.getInstance().registerPhoneAccount(context);
         } else if (ACTION_SHOW_ALL_PHONE_ACCOUNTS.equals(action)) {
             CallServiceNotifier.getInstance().showAllPhoneAccounts(context);
-        } else if (ACTION_VIDEO_CALL.equals(action)) {
-            sendIncomingCallIntent(context, null, true);
+        } else if (ACTION_ONE_WAY_VIDEO_CALL.equals(action)) {
+            sendIncomingCallIntent(context, null, VideoProfile.STATE_RX_ENABLED);
+        } else if (ACTION_TWO_WAY_VIDEO_CALL.equals(action)) {
+            sendIncomingCallIntent(context, null, VideoProfile.STATE_BIDIRECTIONAL);
         } else if (ACTION_AUDIO_CALL.equals(action)) {
-            sendIncomingCallIntent(context, null, false);
+            sendIncomingCallIntent(context, null, VideoProfile.STATE_AUDIO_ONLY);
         }
     }
 
@@ -70,9 +75,9 @@
      * Creates and sends the intent to add an incoming call through Telecom.
      *
      * @param context The current context.
-     * @param isVideoCall {@code True} if this is a video call.
+     * @param videoState The video state requested for the incoming call.
      */
-    public static void sendIncomingCallIntent(Context context, Uri handle, boolean isVideoCall) {
+    public static void sendIncomingCallIntent(Context context, Uri handle, int videoState) {
         PhoneAccountHandle phoneAccount = new PhoneAccountHandle(
                 new ComponentName(context, TestConnectionService.class),
                 CallServiceNotifier.SIM_SUBSCRIPTION_ID);
@@ -80,7 +85,7 @@
         // For the purposes of testing, indicate whether the incoming call is a video call by
         // stashing an indicator in the EXTRA_INCOMING_CALL_EXTRAS.
         Bundle extras = new Bundle();
-        extras.putBoolean(TestConnectionService.EXTRA_IS_VIDEO_CALL, isVideoCall);
+        extras.putInt(TestConnectionService.EXTRA_START_VIDEO_STATE, videoState);
         if (handle != null) {
             extras.putParcelable(TestConnectionService.EXTRA_HANDLE, handle);
         }
diff --git a/testapps/src/com/android/server/telecom/testapps/CallServiceNotifier.java b/testapps/src/com/android/server/telecom/testapps/CallServiceNotifier.java
index c1ced80..9292273 100644
--- a/testapps/src/com/android/server/telecom/testapps/CallServiceNotifier.java
+++ b/testapps/src/com/android/server/telecom/testapps/CallServiceNotifier.java
@@ -24,11 +24,10 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
 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 +102,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),
@@ -112,13 +119,14 @@
                 .setSubscriptionAddress(Uri.parse("tel:555-TEST"))
                 .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER |
                         PhoneAccount.CAPABILITY_VIDEO_CALLING |
-                        PhoneAccount.CAPABILITY_CALL_SUBJECT)
+                        PhoneAccount.CAPABILITY_VIDEO_CALLING_RELIES_ON_PRESENCE)
                 .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(
@@ -131,11 +139,11 @@
                 .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER |
                         PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION |
                         PhoneAccount.CAPABILITY_VIDEO_CALLING |
-                        PhoneAccount.CAPABILITY_CALL_SUBJECT)
+                        PhoneAccount.CAPABILITY_VIDEO_CALLING_RELIES_ON_PRESENCE)
                 .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());
 
@@ -208,7 +216,8 @@
         builder.setContentText("Test calls via CallService API");
         builder.setContentTitle("Test Connection Service");
 
-        addAddVideoCallAction(builder, context);
+        addAddOneWayVideoCallAction(builder, context);
+        addAddTwoWayVideoCallAction(builder, context);
         addAddCallAction(builder, context);
         addExitAction(builder, context);
 
@@ -244,10 +253,19 @@
     }
 
     /**
-     * Creates the intent to start an incoming video call
+     * Creates the intent to start an incoming 1-way video call (receive-only)
      */
-    private PendingIntent createIncomingVideoCall(Context context) {
-        final Intent intent = new Intent(CallNotificationReceiver.ACTION_VIDEO_CALL,
+    private PendingIntent createOneWayVideoCallIntent(Context context) {
+        final Intent intent = new Intent(CallNotificationReceiver.ACTION_ONE_WAY_VIDEO_CALL,
+                null, context, CallNotificationReceiver.class);
+        return PendingIntent.getBroadcast(context, 0, intent, 0);
+    }
+
+    /**
+     * Creates the intent to start an incoming 2-way video call
+     */
+    private PendingIntent createTwoWayVideoCallIntent(Context context) {
+        final Intent intent = new Intent(CallNotificationReceiver.ACTION_TWO_WAY_VIDEO_CALL,
                 null, context, CallNotificationReceiver.class);
         return PendingIntent.getBroadcast(context, 0, intent, 0);
     }
@@ -270,10 +288,19 @@
     }
 
     /**
-     * Adds an action to the Notification Builder to add an incoming video call through Telecom.
+     * Adds an action to the Notification Builder to add an incoming one-way video call through
+     * Telecom.
      */
-    private void addAddVideoCallAction(Notification.Builder builder, Context context) {
-        builder.addAction(0, "Add Video", createIncomingVideoCall(context));
+    private void addAddOneWayVideoCallAction(Notification.Builder builder, Context context) {
+        builder.addAction(0, "1-way Video", createOneWayVideoCallIntent(context));
+    }
+
+    /**
+     * Adds an action to the Notification Builder to add an incoming 2-way video call through
+     * Telecom.
+     */
+    private void addAddTwoWayVideoCallAction(Notification.Builder builder, Context context) {
+        builder.addAction(0, "2-way Video", createTwoWayVideoCallIntent(context));
     }
 
     /**
diff --git a/testapps/src/com/android/server/telecom/testapps/TestCallActivity.java b/testapps/src/com/android/server/telecom/testapps/TestCallActivity.java
index 4ac151f..862ccf7 100644
--- a/testapps/src/com/android/server/telecom/testapps/TestCallActivity.java
+++ b/testapps/src/com/android/server/telecom/testapps/TestCallActivity.java
@@ -20,6 +20,7 @@
 import android.content.Intent;
 import android.net.Uri;
 import android.os.Bundle;
+import android.telecom.VideoProfile;
 
 /**
  * This activity exists in order to add an icon to the launcher. This activity has no UI of its own
@@ -58,7 +59,8 @@
         final String action = intent != null ? intent.getAction() : null;
         final Uri data = intent != null ? intent.getData() : null;
         if (ACTION_NEW_INCOMING_CALL.equals(action) && data != null) {
-            CallNotificationReceiver.sendIncomingCallIntent(this, data, false);
+            CallNotificationReceiver.sendIncomingCallIntent(this, data,
+                    VideoProfile.STATE_AUDIO_ONLY);
         } else if (ACTION_NEW_UNKNOWN_CALL.equals(action) && data != null) {
             CallNotificationReceiver.addNewUnknownCall(this, data, intent.getExtras());
         } else if (ACTION_HANGUP_CALLS.equals(action)) {
diff --git a/testapps/src/com/android/server/telecom/testapps/TestCallList.java b/testapps/src/com/android/server/telecom/testapps/TestCallList.java
index a16c4e2..704c83d 100644
--- a/testapps/src/com/android/server/telecom/testapps/TestCallList.java
+++ b/testapps/src/com/android/server/telecom/testapps/TestCallList.java
@@ -24,6 +24,8 @@
 import android.util.ArraySet;
 import android.util.Log;
 
+import java.util.LinkedList;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
@@ -31,6 +33,12 @@
  * Maintains a list of calls received via the {@link TestInCallServiceImpl}.
  */
 public class TestCallList extends Call.Listener {
+
+    public static abstract class Listener {
+        public void onCallAdded(Call call) {}
+        public void onCallRemoved(Call call) {}
+    }
+
     private static final TestCallList INSTANCE = new TestCallList();
     private static final String TAG = "TestCallList";
 
@@ -85,9 +93,10 @@
     }
 
     // The calls the call list knows about.
-    private Set<Call> mCalls = new ArraySet<Call>();
+    private List<Call> mCalls = new LinkedList<Call>();
     private Map<Call, TestVideoCallListener> mVideoCallListeners =
             new ArrayMap<Call, TestVideoCallListener>();
+    private Set<Listener> mListeners = new ArraySet<Listener>();
 
     /**
      * Singleton accessor.
@@ -96,14 +105,32 @@
         return INSTANCE;
     }
 
+    public void addListener(Listener listener) {
+        if (listener != null) {
+            mListeners.add(listener);
+        }
+    }
+
+    public boolean removeListener(Listener listener) {
+        return mListeners.remove(listener);
+    }
+
+    public Call getCall(int position) {
+        return mCalls.get(position);
+    }
+
     public void addCall(Call call) {
         if (mCalls.contains(call)) {
             Log.e(TAG, "addCall: Call already added.");
             return;
         }
-        Log.v(TAG, "addCall: " + call + " " + System.identityHashCode(this));
+        Log.i(TAG, "addCall: " + call + " " + System.identityHashCode(this));
         mCalls.add(call);
         call.addListener(this);
+
+        for (Listener l : mListeners) {
+            l.onCallAdded(call);
+        }
     }
 
     public void removeCall(Call call) {
@@ -111,9 +138,13 @@
             Log.e(TAG, "removeCall: Call cannot be removed -- doesn't exist.");
             return;
         }
-        Log.v(TAG, "removeCall: " + call);
+        Log.i(TAG, "removeCall: " + call);
         mCalls.remove(call);
         call.removeListener(this);
+
+        for (Listener l : mListeners) {
+            l.onCallRemoved(call);
+        }
     }
 
     public void clearCalls() {
@@ -126,6 +157,10 @@
         mVideoCallListeners.clear();
     }
 
+    public int size() {
+        return mCalls.size();
+    }
+
     /**
      * For any video calls tracked, sends an upgrade to video request.
      */
diff --git a/testapps/src/com/android/server/telecom/testapps/TestConnectionService.java b/testapps/src/com/android/server/telecom/testapps/TestConnectionService.java
index 7964355..c819838 100644
--- a/testapps/src/com/android/server/telecom/testapps/TestConnectionService.java
+++ b/testapps/src/com/android/server/telecom/testapps/TestConnectionService.java
@@ -51,10 +51,9 @@
  */
 public class TestConnectionService extends ConnectionService {
     /**
-     * Intent extra used to pass along whether a call is video or audio based on the user's choice
-     * in the notification.
+     * Intent extra used to pass along the video state for a new test call.
      */
-    public static final String EXTRA_IS_VIDEO_CALL = "extra_is_video_call";
+    public static final String EXTRA_START_VIDEO_STATE = "extra_start_video_state";
 
     public static final String EXTRA_HANDLE = "extra_handle";
 
@@ -350,17 +349,14 @@
             final TestConnection connection = new TestConnection(true);
             // Get the stashed intent extra that determines if this is a video call or audio call.
             Bundle extras = request.getExtras();
-            boolean isVideoCall = extras.getBoolean(EXTRA_IS_VIDEO_CALL);
+            int videoState = extras.getInt(EXTRA_START_VIDEO_STATE, VideoProfile.STATE_AUDIO_ONLY);
             Uri providedHandle = extras.getParcelable(EXTRA_HANDLE);
 
             // Use dummy number for testing incoming calls.
             Uri address = providedHandle == null ?
-                    Uri.fromParts(PhoneAccount.SCHEME_TEL, getDummyNumber(isVideoCall), null)
+                    Uri.fromParts(PhoneAccount.SCHEME_TEL, getDummyNumber(
+                            VideoProfile.isVideo(videoState)), null)
                     : providedHandle;
-
-            int videoState = isVideoCall ?
-                    VideoProfile.STATE_BIDIRECTIONAL :
-                    VideoProfile.STATE_AUDIO_ONLY;
             connection.setVideoState(videoState);
 
             Bundle connectionExtras = connection.getExtras();
diff --git a/testapps/src/com/android/server/telecom/testapps/TestInCallServiceImpl.java b/testapps/src/com/android/server/telecom/testapps/TestInCallServiceImpl.java
index 68bbac9..03ca3d0 100644
--- a/testapps/src/com/android/server/telecom/testapps/TestInCallServiceImpl.java
+++ b/testapps/src/com/android/server/telecom/testapps/TestInCallServiceImpl.java
@@ -16,6 +16,8 @@
 
 package com.android.server.telecom.testapps;
 
+import android.content.Context;
+import android.content.Intent;
 import android.telecom.Call;
 import android.telecom.InCallService;
 import android.telecom.Phone;
@@ -37,7 +39,12 @@
         @Override
         public void onCallAdded(Phone phone, Call call) {
             Log.i(TAG, "onCallAdded: " + call.toString());
-            TestCallList.getInstance().addCall(call);
+            TestCallList callList = TestCallList.getInstance();
+            callList.addCall(call);
+
+            if (callList.size() == 1) {
+                startInCallUI();
+            }
         }
 
         @Override
@@ -62,4 +69,11 @@
         mPhone = null;
         TestCallList.getInstance().clearCalls();
     }
+
+    private void startInCallUI() {
+        Intent intent = new Intent(Intent.ACTION_MAIN);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION | Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.setClass(this, TestInCallUI.class);
+        startActivity(intent);
+    }
 }
diff --git a/testapps/src/com/android/server/telecom/testapps/TestInCallUI.java b/testapps/src/com/android/server/telecom/testapps/TestInCallUI.java
new file mode 100644
index 0000000..ce53709
--- /dev/null
+++ b/testapps/src/com/android/server/telecom/testapps/TestInCallUI.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2015 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.testapps;
+
+import android.app.Activity;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.telecom.Call;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.ListView;
+
+public class TestInCallUI extends Activity {
+
+    private ListView mListView;
+    private TestCallList mCallList;
+
+    /** ${inheritDoc} */
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.incall_screen);
+
+        mListView = (ListView) findViewById(R.id.callListView);
+        mListView.setAdapter(new CallListAdapter(this));
+        mListView.setVisibility(View.VISIBLE);
+
+        mCallList = TestCallList.getInstance();
+        mCallList.addListener(new TestCallList.Listener() {
+            @Override
+            public void onCallRemoved(Call call) {
+                if (mCallList.size() == 0) {
+                    Log.i("Santos", "Ending the incall UI");
+                    finish();
+                }
+            }
+        });
+
+        View endCallButton = findViewById(R.id.end_call_button);
+        View holdButton = findViewById(R.id.hold_button);
+        View muteButton = findViewById(R.id.mute_button);
+
+        endCallButton.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                Call call = mCallList.getCall(0);
+                if (call != null) {
+                    call.disconnect();
+                }
+            }
+        });
+        holdButton.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                Call call = mCallList.getCall(0);
+                if (call != null) {
+                    if (call.getState() == Call.STATE_HOLDING) {
+                        call.unhold();
+                    } else {
+                        call.hold();
+                    }
+                }
+            }
+        });
+        muteButton.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                Call call = mCallList.getCall(0);
+                if (call != null) {
+                }
+            }
+        });
+    }
+
+    /** ${inheritDoc} */
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+    }
+}
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 46056ae..f84a545 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" />
@@ -43,8 +47,8 @@
         adb shell am instrument -w com.android.server.telecom.tests/android.test.InstrumentationTestRunner
 
         To run a single test case:
-        adb shell am instrument -w com.android.server.telecom.tests/android.test.InstrumentationTestRunner
-                                -e com.android.server.telecom.tests.unit.FooUnitTest
+        adb shell am instrument -w -e class com.android.server.telecom.tests.unit.FooUnitTest \
+                               com.android.server.telecom.tests/android.test.InstrumentationTestRunner
     -->
     <instrumentation android:name="android.test.InstrumentationTestRunner"
             android:targetPackage="com.android.server.telecom.tests"
diff --git a/tests/res/drawable-xhdpi/contacts_sample_photo.png b/tests/res/drawable-xhdpi/contacts_sample_photo.png
new file mode 100644
index 0000000..6c0ba64
--- /dev/null
+++ b/tests/res/drawable-xhdpi/contacts_sample_photo.png
Binary files differ
diff --git a/tests/res/drawable-xhdpi/contacts_sample_photo_small.png b/tests/res/drawable-xhdpi/contacts_sample_photo_small.png
new file mode 100644
index 0000000..f53602e
--- /dev/null
+++ b/tests/res/drawable-xhdpi/contacts_sample_photo_small.png
Binary files differ
diff --git a/tests/res/values/dimens.xml b/tests/res/values/dimens.xml
new file mode 100644
index 0000000..031f5b4
--- /dev/null
+++ b/tests/res/values/dimens.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+
+<resources>
+    <dimen name="notification_icon_size">64dp</dimen>
+</resources>
diff --git a/tests/src/com/android/server/telecom/tests/BasicCallTests.java b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
new file mode 100644
index 0000000..e7b76ac
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
@@ -0,0 +1,540 @@
+/*
+ * 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 com.android.internal.util.IndentingPrintWriter;
+import com.android.server.telecom.Analytics;
+import com.android.server.telecom.Log;
+
+import android.content.Context;
+import android.media.AudioManager;
+import android.os.Process;
+import android.telecom.Call;
+import android.telecom.CallAudioState;
+import android.telecom.Connection;
+import android.telecom.DisconnectCause;
+import android.telecom.ParcelableCall;
+import android.telecom.ParcelableCallAnalytics;
+import android.telecom.TelecomManager;
+import android.telecom.VideoProfile;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.internal.telecom.IInCallAdapter;
+
+import java.io.StringWriter;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.BrokenBarrierException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.CyclicBarrier;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+/**
+ * Performs various basic call tests in Telecom.
+ */
+public class BasicCallTests extends TelecomSystemTest {
+    @LargeTest
+    public void testSingleOutgoingCallLocalDisconnect() throws Exception {
+        IdPair ids = startAndMakeActiveOutgoingCall("650-555-1212",
+                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
+
+        mInCallServiceFixtureX.mInCallAdapter.disconnectCall(ids.mCallId);
+        assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
+        assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureY.getCall(ids.mCallId).getState());
+
+        mConnectionServiceFixtureA.sendSetDisconnected(ids.mConnectionId, DisconnectCause.LOCAL);
+        assertEquals(Call.STATE_DISCONNECTED,
+                mInCallServiceFixtureX.getCall(ids.mCallId).getState());
+        assertEquals(Call.STATE_DISCONNECTED,
+                mInCallServiceFixtureY.getCall(ids.mCallId).getState());
+    }
+
+    @LargeTest
+    public void testSingleOutgoingCallRemoteDisconnect() throws Exception {
+        IdPair ids = startAndMakeActiveOutgoingCall("650-555-1212",
+                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
+
+        mConnectionServiceFixtureA.sendSetDisconnected(ids.mConnectionId, DisconnectCause.LOCAL);
+        assertEquals(Call.STATE_DISCONNECTED,
+                mInCallServiceFixtureX.getCall(ids.mCallId).getState());
+        assertEquals(Call.STATE_DISCONNECTED,
+                mInCallServiceFixtureY.getCall(ids.mCallId).getState());
+    }
+
+    /**
+     * Tests the {@link TelecomManager#acceptRingingCall()} API.  Tests simple case of an incoming
+     * audio-only call.
+     *
+     * @throws Exception
+     */
+    @LargeTest
+    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);
+        mConnectionServiceFixtureA.sendSetActive(ids.mConnectionId);
+
+        mInCallServiceFixtureX.mInCallAdapter.disconnectCall(ids.mCallId);
+    }
+
+    /**
+     * Tests the {@link TelecomManager#acceptRingingCall()} API.  Tests simple case of an incoming
+     * video call, which should be answered as video.
+     *
+     * @throws Exception
+     */
+    @LargeTest
+    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));
+        mConnectionServiceFixtureA.sendSetActive(ids.mConnectionId);
+
+        mInCallServiceFixtureX.mInCallAdapter.disconnectCall(ids.mCallId);
+    }
+
+    /**
+     * Tests the {@link TelecomManager#acceptRingingCall(int)} API.  Tests answering a video call
+     * as an audio call.
+     *
+     * @throws Exception
+     */
+    @LargeTest
+    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));
+        mConnectionServiceFixtureA.sendSetActive(ids.mConnectionId);
+
+        mInCallServiceFixtureX.mInCallAdapter.disconnectCall(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
+     */
+    @LargeTest
+    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));
+        mConnectionServiceFixtureA.sendSetActive(ids.mConnectionId);
+        mInCallServiceFixtureX.mInCallAdapter.disconnectCall(ids.mCallId);
+    }
+
+    @LargeTest
+    public void testSingleIncomingCallLocalDisconnect() throws Exception {
+        IdPair ids = startAndMakeActiveIncomingCall("650-555-1212",
+                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
+        mInCallServiceFixtureX.mInCallAdapter.disconnectCall(ids.mCallId);
+        assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
+        assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureY.getCall(ids.mCallId).getState());
+
+        mConnectionServiceFixtureA.sendSetDisconnected(ids.mConnectionId, DisconnectCause.LOCAL);
+        assertEquals(Call.STATE_DISCONNECTED,
+                mInCallServiceFixtureX.getCall(ids.mCallId).getState());
+        assertEquals(Call.STATE_DISCONNECTED,
+                mInCallServiceFixtureY.getCall(ids.mCallId).getState());
+    }
+
+    @LargeTest
+    public void testSingleIncomingCallRemoteDisconnect() throws Exception {
+        IdPair ids = startAndMakeActiveIncomingCall("650-555-1212",
+                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
+
+        mConnectionServiceFixtureA.sendSetDisconnected(ids.mConnectionId, DisconnectCause.LOCAL);
+        assertEquals(Call.STATE_DISCONNECTED,
+                mInCallServiceFixtureX.getCall(ids.mCallId).getState());
+        assertEquals(Call.STATE_DISCONNECTED,
+                mInCallServiceFixtureY.getCall(ids.mCallId).getState());
+    }
+
+    public void do_testDeadlockOnOutgoingCall() throws Exception {
+        final IdPair ids = startOutgoingPhoneCall("650-555-1212",
+                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA,
+                Process.myUserHandle());
+        rapidFire(
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        while (mCallerInfoAsyncQueryFactoryFixture.mRequests.size() > 0) {
+                            mCallerInfoAsyncQueryFactoryFixture.mRequests.remove(0).reply();
+                        }
+                    }
+                },
+                new Runnable() {
+                    @Override
+                    public void run() {
+                        try {
+                            mConnectionServiceFixtureA.sendSetActive(ids.mConnectionId);
+                        } catch (Exception e) {
+                            Log.e(this, e, "");
+                        }
+                    }
+                });
+    }
+
+    @MediumTest
+    public void testDeadlockOnOutgoingCall() throws Exception {
+        for (int i = 0; i < 100; i++) {
+            BasicCallTests test = new BasicCallTests();
+            test.setContext(getContext());
+            test.setTestContext(getTestContext());
+            test.setName(getName());
+            test.setUp();
+            test.do_testDeadlockOnOutgoingCall();
+            test.tearDown();
+        }
+    }
+
+    @LargeTest
+    public void testIncomingThenOutgoingCalls() throws Exception {
+        // TODO: We have to use the same PhoneAccount for both; see http://b/18461539
+        IdPair incoming = startAndMakeActiveIncomingCall("650-555-2323",
+                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
+        IdPair outgoing = startAndMakeActiveOutgoingCall("650-555-1212",
+                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
+
+        mInCallServiceFixtureX.mInCallAdapter.disconnectCall(incoming.mCallId);
+        mInCallServiceFixtureX.mInCallAdapter.disconnectCall(outgoing.mCallId);
+    }
+
+    @LargeTest
+    public void testOutgoingThenIncomingCalls() throws Exception {
+        // TODO: We have to use the same PhoneAccount for both; see http://b/18461539
+        IdPair outgoing = startAndMakeActiveOutgoingCall("650-555-1212",
+                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
+        IdPair incoming = startAndMakeActiveIncomingCall("650-555-2323",
+                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
+        verify(mConnectionServiceFixtureA.getTestDouble())
+                .hold(outgoing.mConnectionId);
+        mConnectionServiceFixtureA.mConnectionById.get(outgoing.mConnectionId).state =
+                Connection.STATE_HOLDING;
+        mConnectionServiceFixtureA.sendSetOnHold(outgoing.mConnectionId);
+        assertEquals(Call.STATE_HOLDING,
+                mInCallServiceFixtureX.getCall(outgoing.mCallId).getState());
+        assertEquals(Call.STATE_HOLDING,
+                mInCallServiceFixtureY.getCall(outgoing.mCallId).getState());
+
+        mInCallServiceFixtureX.mInCallAdapter.disconnectCall(incoming.mCallId);
+        mInCallServiceFixtureX.mInCallAdapter.disconnectCall(outgoing.mCallId);
+    }
+
+    public void testAudioManagerOperations() throws Exception {
+        AudioManager audioManager = (AudioManager) mComponentContextFixture.getTestDouble()
+                .getApplicationContext().getSystemService(Context.AUDIO_SERVICE);
+
+        IdPair outgoing = startAndMakeActiveOutgoingCall("650-555-1212",
+                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
+
+        verify(audioManager, timeout(TEST_TIMEOUT)).requestAudioFocusForCall(anyInt(), anyInt());
+        verify(audioManager, timeout(TEST_TIMEOUT).atLeastOnce())
+                .setMode(AudioManager.MODE_IN_CALL);
+
+        mInCallServiceFixtureX.mInCallAdapter.mute(true);
+        verify(mAudioService, timeout(TEST_TIMEOUT))
+                .setMicrophoneMute(eq(true), any(String.class), any(Integer.class));
+        mInCallServiceFixtureX.mInCallAdapter.mute(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))
+                .setSpeakerphoneOn(true);
+        mInCallServiceFixtureX.mInCallAdapter.setAudioRoute(CallAudioState.ROUTE_EARPIECE);
+        verify(audioManager, timeout(TEST_TIMEOUT))
+                .setSpeakerphoneOn(false);
+
+        mConnectionServiceFixtureA.
+                sendSetDisconnected(outgoing.mConnectionId, DisconnectCause.REMOTE);
+
+        verify(audioManager, timeout(TEST_TIMEOUT))
+                .abandonAudioFocusForCall();
+        verify(audioManager, timeout(TEST_TIMEOUT).atLeastOnce())
+                .setMode(AudioManager.MODE_NORMAL);
+    }
+
+    private void rapidFire(Runnable... tasks) {
+        final CyclicBarrier barrier = new CyclicBarrier(tasks.length);
+        final CountDownLatch latch = new CountDownLatch(tasks.length);
+        for (int i = 0; i < tasks.length; i++) {
+            final Runnable task = tasks[i];
+            new Thread(new Runnable() {
+                @Override
+                public void run() {
+                    try {
+                        barrier.await();
+                        task.run();
+                    } catch (InterruptedException | BrokenBarrierException e){
+                        Log.e(BasicCallTests.this, e, "Unexpectedly interrupted");
+                    } finally {
+                        latch.countDown();
+                    }
+                }
+            }).start();
+        }
+        try {
+            latch.await();
+        } catch (InterruptedException e) {
+            Log.e(BasicCallTests.this, e, "Unexpectedly interrupted");
+        }
+    }
+
+    @MediumTest
+    public void testBasicConferenceCall() throws Exception {
+        makeConferenceCall();
+    }
+
+    @MediumTest
+    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));
+    }
+
+    @MediumTest
+    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;
+    }
+
+    @MediumTest
+    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", "connectionService"};
+        for (String field : expectedFields) {
+            assertTrue(dumpResult.contains(field));
+        }
+    }
+
+    @SmallTest
+    public void testAnalyticsDumping() throws Exception {
+        Analytics.reset();
+        IdPair testCall = startAndMakeActiveIncomingCall(
+                "650-555-1212",
+                mPhoneAccountA0.getAccountHandle(),
+                mConnectionServiceFixtureA);
+
+        mConnectionServiceFixtureA.
+                sendSetDisconnected(testCall.mConnectionId, DisconnectCause.ERROR);
+        Analytics.CallInfoImpl expectedAnalytics = Analytics.cloneData().get(testCall.mCallId);
+
+        TelecomManager tm = (TelecomManager) mSpyContext.getSystemService(Context.TELECOM_SERVICE);
+        List<ParcelableCallAnalytics> analyticsList = tm.dumpAnalytics();
+
+        assertEquals(1, analyticsList.size());
+        ParcelableCallAnalytics pCA = analyticsList.get(0);
+
+        assertTrue(Math.abs(expectedAnalytics.startTime - pCA.getStartTimeMillis()) <
+                ParcelableCallAnalytics.MILLIS_IN_5_MINUTES);
+        assertEquals(0, pCA.getStartTimeMillis() % ParcelableCallAnalytics.MILLIS_IN_5_MINUTES);
+        assertTrue(Math.abs((expectedAnalytics.endTime - expectedAnalytics.startTime) -
+                pCA.getCallDurationMillis()) < ParcelableCallAnalytics.MILLIS_IN_1_SECOND);
+        assertEquals(0, pCA.getCallDurationMillis() % ParcelableCallAnalytics.MILLIS_IN_1_SECOND);
+
+        assertEquals(expectedAnalytics.callDirection, pCA.getCallType());
+        assertEquals(expectedAnalytics.isAdditionalCall, pCA.isAdditionalCall());
+        assertEquals(expectedAnalytics.isInterrupted, pCA.isInterrupted());
+        assertEquals(expectedAnalytics.callTechnologies, pCA.getCallTechnologies());
+        assertEquals(expectedAnalytics.callTerminationReason.getCode(),
+                pCA.getCallTerminationCode());
+        assertEquals(expectedAnalytics.connectionService, pCA.getConnectionService());
+    }
+
+    @MediumTest
+    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());
+    }
+}
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/CallAudioModeStateMachineTest.java b/tests/src/com/android/server/telecom/tests/CallAudioModeStateMachineTest.java
new file mode 100644
index 0000000..4c32dd7
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/CallAudioModeStateMachineTest.java
@@ -0,0 +1,580 @@
+/*
+ * Copyright (C) 2016 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.telecom.CallAudioState;
+import android.test.suitebuilder.annotation.LargeTest;
+
+import com.android.server.telecom.CallAudioManager;
+import com.android.server.telecom.CallAudioModeStateMachine;
+
+import org.mockito.Mock;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+
+public class CallAudioModeStateMachineTest extends StateMachineTestBase<CallAudioModeStateMachine> {
+    private static class ModeTestParameters extends TestParameters {
+        public String name;
+        public int initialAudioState; // One of the explicit switch focus constants in CAMSM
+        public int messageType; // Any of the commands from the state machine
+        public CallAudioModeStateMachine.MessageArgs externalState;
+        public String expectedFinalStateName;
+        public int expectedFocus; // one of the FOCUS_* constants below
+        public int expectedMode; // NO_CHANGE, or an AudioManager.MODE_* constant
+        public int expectedRingingInteraction; // NO_CHANGE, ON, or OFF
+        public int expectedCallWaitingInteraction; // NO_CHANGE, ON, or OFF
+
+        public ModeTestParameters(String name, int initialAudioState, int messageType,
+                CallAudioModeStateMachine.MessageArgs externalState, String
+                expectedFinalStateName, int expectedFocus, int expectedMode, int
+                expectedRingingInteraction, int expectedCallWaitingInteraction) {
+            this.name = name;
+            this.initialAudioState = initialAudioState;
+            this.messageType = messageType;
+            this.externalState = externalState;
+            this.expectedFinalStateName = expectedFinalStateName;
+            this.expectedFocus = expectedFocus;
+            this.expectedMode = expectedMode;
+            this.expectedRingingInteraction = expectedRingingInteraction;
+            this.expectedCallWaitingInteraction = expectedCallWaitingInteraction;
+        }
+
+        @Override
+        public String toString() {
+            return "ModeTestParameters{" +
+                    "name='" + name + '\'' +
+                    ", initialAudioState=" + initialAudioState +
+                    ", messageType=" + messageType +
+                    ", externalState=" + externalState +
+                    ", expectedFinalStateName='" + expectedFinalStateName + '\'' +
+                    ", expectedFocus=" + expectedFocus +
+                    ", expectedMode=" + expectedMode +
+                    ", expectedRingingInteraction=" + expectedRingingInteraction +
+                    ", expectedCallWaitingInteraction=" + expectedCallWaitingInteraction +
+                    '}';
+        }
+    }
+
+    private static final int FOCUS_NO_CHANGE = 0;
+    private static final int FOCUS_RING = 1;
+    private static final int FOCUS_VOICE = 2;
+    private static final int FOCUS_OFF = 3;
+
+    private static final int NO_CHANGE = -1;
+    private static final int ON = 0;
+    private static final int OFF = 1;
+
+    @Mock private AudioManager mAudioManager;
+    @Mock private CallAudioManager mCallAudioManager;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
+    }
+
+    @LargeTest
+    public void testTransitions() throws Throwable {
+        List<ModeTestParameters> testCases = generateTestCases();
+        parametrizedTestStateMachine(testCases);
+    }
+
+    private List<ModeTestParameters> generateTestCases() {
+        List<ModeTestParameters> result = new ArrayList<>();
+        result.add(new ModeTestParameters(
+                "New active/dialing call with no other calls when unfocused",
+                CallAudioModeStateMachine.ABANDON_FOCUS_FOR_TESTING, // initialAudioState
+                CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL, // messageType
+                new CallAudioModeStateMachine.MessageArgs(
+                        true, // hasActiveOrDialingCalls
+                        false, // hasRingingCalls
+                        false, // hasHoldingCalls
+                        false, // isTonePlaying
+                        false, // foregroundCallIsVoip
+                        null // session
+                ),
+                CallAudioModeStateMachine.CALL_STATE_NAME, // expectedFinalStateName
+                FOCUS_VOICE, // expectedFocus
+                AudioManager.MODE_IN_CALL, // expectedMode
+                NO_CHANGE, // expectedRingingInteraction
+                NO_CHANGE // expectedCallWaitingInteraction
+        ));
+
+        result.add(new ModeTestParameters(
+                "New active/dialing voip call with no other calls when unfocused",
+                CallAudioModeStateMachine.ABANDON_FOCUS_FOR_TESTING, // initialAudioState
+                CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL, // messageType
+                new CallAudioModeStateMachine.MessageArgs(
+                        true, // hasActiveOrDialingCalls
+                        false, // hasRingingCalls
+                        false, // hasHoldingCalls
+                        false, // isTonePlaying
+                        true, // foregroundCallIsVoip
+                        null // session
+                ),
+                CallAudioModeStateMachine.COMMS_STATE_NAME, // expectedFinalStateName
+                FOCUS_VOICE, // expectedFocus
+                AudioManager.MODE_IN_COMMUNICATION, // expectedMode
+                NO_CHANGE, // expectedRingingInteraction
+                NO_CHANGE // expectedCallWaitingInteraction
+        ));
+
+        result.add(new ModeTestParameters(
+                "New ringing call with no other calls when unfocused",
+                CallAudioModeStateMachine.ABANDON_FOCUS_FOR_TESTING, // initialAudioState
+                CallAudioModeStateMachine.NEW_RINGING_CALL, // messageType
+                new CallAudioModeStateMachine.MessageArgs(
+                        false, // hasActiveOrDialingCalls
+                        true, // hasRingingCalls
+                        false, // hasHoldingCalls
+                        false, // isTonePlaying
+                        false, // foregroundCallIsVoip
+                        null // session
+                ),
+                CallAudioModeStateMachine.RING_STATE_NAME, // expectedFinalStateName
+                FOCUS_RING, // expectedFocus
+                AudioManager.MODE_RINGTONE, // expectedMode
+                ON, // expectedRingingInteraction
+                OFF // expectedCallWaitingInteraction
+        ));
+
+        result.add(new ModeTestParameters(
+                "New ringing call coming in on top of active/dialing call",
+                CallAudioModeStateMachine.ENTER_CALL_FOCUS_FOR_TESTING, // initialAudioState
+                CallAudioModeStateMachine.NEW_RINGING_CALL, // messageType
+                new CallAudioModeStateMachine.MessageArgs(
+                        true, // hasActiveOrDialingCalls
+                        true, // hasRingingCalls
+                        false, // hasHoldingCalls
+                        false, // isTonePlaying
+                        false, // foregroundCallIsVoip
+                        null // session
+                ),
+                CallAudioModeStateMachine.CALL_STATE_NAME, // expectedFinalStateName
+                NO_CHANGE, // expectedFocus
+                NO_CHANGE, // expectedMode
+                NO_CHANGE, // expectedRingingInteraction
+                ON // expectedCallWaitingInteraction
+        ));
+
+        result.add(new ModeTestParameters(
+                "Ringing call becomes active, part 1",
+                CallAudioModeStateMachine.ENTER_RING_FOCUS_FOR_TESTING, // initialAudioState
+                CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL, // messageType
+                new CallAudioModeStateMachine.MessageArgs(
+                        true, // hasActiveOrDialingCalls
+                        false, // hasRingingCalls
+                        false, // hasHoldingCalls
+                        false, // isTonePlaying
+                        false, // foregroundCallIsVoip
+                        null // session
+                ),
+                CallAudioModeStateMachine.CALL_STATE_NAME, // expectedFinalStateName
+                FOCUS_VOICE, // expectedFocus
+                AudioManager.MODE_IN_CALL, // expectedMode
+                OFF, // expectedRingingInteraction
+                NO_CHANGE // expectedCallWaitingInteraction
+        ));
+
+        result.add(new ModeTestParameters(
+                "Ringing call becomes active, part 2",
+                CallAudioModeStateMachine.ENTER_CALL_FOCUS_FOR_TESTING, // initialAudioState
+                CallAudioModeStateMachine.NO_MORE_RINGING_CALLS, // messageType
+                new CallAudioModeStateMachine.MessageArgs(
+                        true, // hasActiveOrDialingCalls
+                        false, // hasRingingCalls
+                        false, // hasHoldingCalls
+                        false, // isTonePlaying
+                        false, // foregroundCallIsVoip
+                        null // session
+                ),
+                CallAudioModeStateMachine.CALL_STATE_NAME, // expectedFinalStateName
+                NO_CHANGE, // expectedFocus
+                NO_CHANGE, // expectedMode
+                NO_CHANGE, // expectedRingingInteraction
+                NO_CHANGE // expectedCallWaitingInteraction
+        ));
+
+        result.add(new ModeTestParameters(
+                "Active call disconnects, but tone is playing",
+                CallAudioModeStateMachine.ENTER_CALL_FOCUS_FOR_TESTING, // initialAudioState
+                CallAudioModeStateMachine.NO_MORE_ACTIVE_OR_DIALING_CALLS, // messageType
+                new CallAudioModeStateMachine.MessageArgs(
+                        false, // hasActiveOrDialingCalls
+                        false, // hasRingingCalls
+                        false, // hasHoldingCalls
+                        true, // isTonePlaying
+                        false, // foregroundCallIsVoip
+                        null // session
+                ),
+                CallAudioModeStateMachine.TONE_HOLD_STATE_NAME, // expectedFinalStateName
+                FOCUS_VOICE, // expectedFocus
+                AudioManager.MODE_IN_CALL, // expectedMode
+                NO_CHANGE, // expectedRingingInteraction
+                NO_CHANGE // expectedCallWaitingInteraction
+        ));
+
+        result.add(new ModeTestParameters(
+                "Tone stops playing, with no active calls",
+                CallAudioModeStateMachine.ENTER_TONE_OR_HOLD_FOCUS_FOR_TESTING, // initialAudioState
+                CallAudioModeStateMachine.TONE_STOPPED_PLAYING, // messageType
+                new CallAudioModeStateMachine.MessageArgs(
+                        false, // hasActiveOrDialingCalls
+                        false, // hasRingingCalls
+                        false, // hasHoldingCalls
+                        false, // isTonePlaying
+                        false, // foregroundCallIsVoip
+                        null // session
+                ),
+                CallAudioModeStateMachine.UNFOCUSED_STATE_NAME, // expectedFinalStateName
+                FOCUS_OFF, // expectedFocus
+                AudioManager.MODE_NORMAL, // expectedMode
+                NO_CHANGE, // expectedRingingInteraction
+                NO_CHANGE // expectedCallWaitingInteraction
+        ));
+
+        result.add(new ModeTestParameters(
+                "Ringing call disconnects",
+                CallAudioModeStateMachine.ENTER_RING_FOCUS_FOR_TESTING, // initialAudioState
+                CallAudioModeStateMachine.NO_MORE_RINGING_CALLS, // messageType
+                new CallAudioModeStateMachine.MessageArgs(
+                        false, // hasActiveOrDialingCalls
+                        false, // hasRingingCalls
+                        false, // hasHoldingCalls
+                        false, // isTonePlaying
+                        false, // foregroundCallIsVoip
+                        null // session
+                ),
+                CallAudioModeStateMachine.UNFOCUSED_STATE_NAME, // expectedFinalStateName
+                FOCUS_OFF, // expectedFocus
+                AudioManager.MODE_NORMAL, // expectedMode
+                OFF, // expectedRingingInteraction
+                NO_CHANGE // expectedCallWaitingInteraction
+        ));
+
+        result.add(new ModeTestParameters(
+                "Call-waiting call disconnects",
+                CallAudioModeStateMachine.ENTER_CALL_FOCUS_FOR_TESTING, // initialAudioState
+                CallAudioModeStateMachine.NO_MORE_RINGING_CALLS, // messageType
+                new CallAudioModeStateMachine.MessageArgs(
+                        true, // hasActiveOrDialingCalls
+                        false, // hasRingingCalls
+                        false, // hasHoldingCalls
+                        true, // isTonePlaying
+                        false, // foregroundCallIsVoip
+                        null // session
+                ),
+                CallAudioModeStateMachine.CALL_STATE_NAME, // expectedFinalStateName
+                FOCUS_NO_CHANGE, // expectedFocus
+                NO_CHANGE, // expectedMode
+                NO_CHANGE, // expectedRingingInteraction
+                OFF // expectedCallWaitingInteraction
+        ));
+
+        result.add(new ModeTestParameters(
+                "Call is placed on hold - 1",
+                CallAudioModeStateMachine.ENTER_CALL_FOCUS_FOR_TESTING, // initialAudioState
+                CallAudioModeStateMachine.NO_MORE_ACTIVE_OR_DIALING_CALLS, // messageType
+                new CallAudioModeStateMachine.MessageArgs(
+                        false, // hasActiveOrDialingCalls
+                        false, // hasRingingCalls
+                        true, // hasHoldingCalls
+                        false, // isTonePlaying
+                        false, // foregroundCallIsVoip
+                        null // session
+                ),
+                CallAudioModeStateMachine.TONE_HOLD_STATE_NAME, // expectedFinalStateName
+                FOCUS_VOICE, // expectedFocus
+                AudioManager.MODE_IN_CALL, // expectedMode
+                NO_CHANGE, // expectedRingingInteraction
+                NO_CHANGE // expectedCallWaitingInteraction
+        ));
+
+        result.add(new ModeTestParameters(
+                "Call is placed on hold - 2",
+                CallAudioModeStateMachine.ENTER_TONE_OR_HOLD_FOCUS_FOR_TESTING, // initialAudioState
+                CallAudioModeStateMachine.NEW_HOLDING_CALL, // messageType
+                new CallAudioModeStateMachine.MessageArgs(
+                        false, // hasActiveOrDialingCalls
+                        false, // hasRingingCalls
+                        true, // hasHoldingCalls
+                        false, // isTonePlaying
+                        false, // foregroundCallIsVoip
+                        null // session
+                ),
+                CallAudioModeStateMachine.TONE_HOLD_STATE_NAME, // expectedFinalStateName
+                FOCUS_NO_CHANGE, // expectedFocus
+                NO_CHANGE, // expectedMode
+                NO_CHANGE, // expectedRingingInteraction
+                NO_CHANGE // expectedCallWaitingInteraction
+        ));
+
+        result.add(new ModeTestParameters(
+                "Call is taken off hold - 1",
+                CallAudioModeStateMachine.ENTER_TONE_OR_HOLD_FOCUS_FOR_TESTING, // initialAudioState
+                CallAudioModeStateMachine.NO_MORE_HOLDING_CALLS, // messageType
+                new CallAudioModeStateMachine.MessageArgs(
+                        true, // hasActiveOrDialingCalls
+                        false, // hasRingingCalls
+                        false, // hasHoldingCalls
+                        false, // isTonePlaying
+                        false, // foregroundCallIsVoip
+                        null // session
+                ),
+                CallAudioModeStateMachine.CALL_STATE_NAME, // expectedFinalStateName
+                FOCUS_VOICE, // expectedFocus
+                AudioManager.MODE_IN_CALL, // expectedMode
+                NO_CHANGE, // expectedRingingInteraction
+                NO_CHANGE // expectedCallWaitingInteraction
+        ));
+
+        result.add(new ModeTestParameters(
+                "Call is taken off hold - 2",
+                CallAudioModeStateMachine.ENTER_CALL_FOCUS_FOR_TESTING, // initialAudioState
+                CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL, // messageType
+                new CallAudioModeStateMachine.MessageArgs(
+                        true, // hasActiveOrDialingCalls
+                        false, // hasRingingCalls
+                        false, // hasHoldingCalls
+                        false, // isTonePlaying
+                        false, // foregroundCallIsVoip
+                        null // session
+                ),
+                CallAudioModeStateMachine.CALL_STATE_NAME, // expectedFinalStateName
+                FOCUS_NO_CHANGE, // expectedFocus
+                NO_CHANGE, // expectedMode
+                NO_CHANGE, // expectedRingingInteraction
+                NO_CHANGE // expectedCallWaitingInteraction
+        ));
+
+        result.add(new ModeTestParameters(
+                "Active call disconnects while there's a call-waiting call",
+                CallAudioModeStateMachine.ENTER_CALL_FOCUS_FOR_TESTING, // initialAudioState
+                CallAudioModeStateMachine.NO_MORE_ACTIVE_OR_DIALING_CALLS, // messageType
+                new CallAudioModeStateMachine.MessageArgs(
+                        false, // hasActiveOrDialingCalls
+                        true, // hasRingingCalls
+                        false, // hasHoldingCalls
+                        true, // isTonePlaying
+                        false, // foregroundCallIsVoip
+                        null // session
+                ),
+                CallAudioModeStateMachine.RING_STATE_NAME, // expectedFinalStateName
+                FOCUS_RING, // expectedFocus
+                AudioManager.MODE_RINGTONE, // expectedMode
+                ON, // expectedRingingInteraction
+                OFF // expectedCallWaitingInteraction
+        ));
+
+        result.add(new ModeTestParameters(
+                "New dialing call when there's a call on hold",
+                CallAudioModeStateMachine.ENTER_TONE_OR_HOLD_FOCUS_FOR_TESTING, // initialAudioState
+                CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL, // messageType
+                new CallAudioModeStateMachine.MessageArgs(
+                        true, // hasActiveOrDialingCalls
+                        false, // hasRingingCalls
+                        true, // hasHoldingCalls
+                        false, // isTonePlaying
+                        false, // foregroundCallIsVoip
+                        null // session
+                ),
+                CallAudioModeStateMachine.CALL_STATE_NAME, // expectedFinalStateName
+                FOCUS_VOICE, // expectedFocus
+                AudioManager.MODE_IN_CALL, // expectedMode
+                NO_CHANGE, // expectedRingingInteraction
+                NO_CHANGE // expectedCallWaitingInteraction
+        ));
+
+        result.add(new ModeTestParameters(
+                "Ringing call disconnects with a holding call in the background",
+                CallAudioModeStateMachine.ENTER_RING_FOCUS_FOR_TESTING, // initialAudioState
+                CallAudioModeStateMachine.NO_MORE_RINGING_CALLS, // messageType
+                new CallAudioModeStateMachine.MessageArgs(
+                        false, // hasActiveOrDialingCalls
+                        false, // hasRingingCalls
+                        true, // hasHoldingCalls
+                        false, // isTonePlaying
+                        false, // foregroundCallIsVoip
+                        null // session
+                ),
+                CallAudioModeStateMachine.TONE_HOLD_STATE_NAME, // expectedFinalStateName
+                FOCUS_VOICE, // expectedFocus
+                AudioManager.MODE_NORMAL, // expectedMode -- we're expecting this because
+                                          // mMostRecentMode hasn't been set properly.
+                OFF, // expectedRingingInteraction
+                NO_CHANGE // expectedCallWaitingInteraction
+        ));
+
+        result.add(new ModeTestParameters(
+                "Foreground call transitions from sim to voip",
+                CallAudioModeStateMachine.ENTER_CALL_FOCUS_FOR_TESTING, // initialAudioState
+                CallAudioModeStateMachine.FOREGROUND_VOIP_MODE_CHANGE, // messageType
+                new CallAudioModeStateMachine.MessageArgs(
+                        true, // hasActiveOrDialingCalls
+                        false, // hasRingingCalls
+                        false, // hasHoldingCalls
+                        false, // isTonePlaying
+                        true, // foregroundCallIsVoip
+                        null // session
+                ),
+                CallAudioModeStateMachine.COMMS_STATE_NAME, // expectedFinalStateName
+                FOCUS_VOICE, // expectedFocus
+                AudioManager.MODE_IN_COMMUNICATION, // expectedMode
+                NO_CHANGE, // expectedRingingInteraction
+                NO_CHANGE // expectedCallWaitingInteraction
+        ));
+
+        result.add(new ModeTestParameters(
+                "Foreground call transitions from voip to sim",
+                CallAudioModeStateMachine.ENTER_COMMS_FOCUS_FOR_TESTING, // initialAudioState
+                CallAudioModeStateMachine.FOREGROUND_VOIP_MODE_CHANGE, // messageType
+                new CallAudioModeStateMachine.MessageArgs(
+                        true, // hasActiveOrDialingCalls
+                        false, // hasRingingCalls
+                        false, // hasHoldingCalls
+                        false, // isTonePlaying
+                        false, // foregroundCallIsVoip
+                        null // session
+                ),
+                CallAudioModeStateMachine.CALL_STATE_NAME, // expectedFinalStateName
+                FOCUS_VOICE, // expectedFocus
+                AudioManager.MODE_IN_CALL, // expectedMode
+                NO_CHANGE, // expectedRingingInteraction
+                NO_CHANGE // expectedCallWaitingInteraction
+        ));
+
+        result.add(new ModeTestParameters(
+                "Call-waiting hangs up before being answered, with another sim call in " +
+                        "foreground",
+                CallAudioModeStateMachine.ENTER_CALL_FOCUS_FOR_TESTING, // initialAudioState
+                CallAudioModeStateMachine.NO_MORE_RINGING_CALLS, // messageType
+                new CallAudioModeStateMachine.MessageArgs(
+                        true, // hasActiveOrDialingCalls
+                        false, // hasRingingCalls
+                        false, // hasHoldingCalls
+                        true, // isTonePlaying
+                        false, // foregroundCallIsVoip
+                        null // session
+                ),
+                CallAudioModeStateMachine.CALL_STATE_NAME, // expectedFinalStateName
+                FOCUS_NO_CHANGE, // expectedFocus
+                NO_CHANGE, // expectedMode
+                NO_CHANGE, // expectedRingingInteraction
+                OFF // expectedCallWaitingInteraction
+        ));
+
+        result.add(new ModeTestParameters(
+                "Call-waiting hangs up before being answered, with another voip call in " +
+                        "foreground",
+                CallAudioModeStateMachine.ENTER_COMMS_FOCUS_FOR_TESTING, // initialAudioState
+                CallAudioModeStateMachine.NO_MORE_RINGING_CALLS, // messageType
+                new CallAudioModeStateMachine.MessageArgs(
+                        true, // hasActiveOrDialingCalls
+                        false, // hasRingingCalls
+                        false, // hasHoldingCalls
+                        true, // isTonePlaying
+                        true, // foregroundCallIsVoip
+                        null // session
+                ),
+                CallAudioModeStateMachine.COMMS_STATE_NAME, // expectedFinalStateName
+                FOCUS_NO_CHANGE, // expectedFocus
+                NO_CHANGE, // expectedMode
+                NO_CHANGE, // expectedRingingInteraction
+                OFF // expectedCallWaitingInteraction
+        ));
+
+        return result;
+    }
+
+    @Override
+    protected void runParametrizedTestCase(TestParameters _params) {
+        ModeTestParameters params = (ModeTestParameters) _params;
+        CallAudioModeStateMachine sm = new CallAudioModeStateMachine(mAudioManager);
+        sm.setCallAudioManager(mCallAudioManager);
+        sm.sendMessage(params.initialAudioState);
+        waitForStateMachineActionCompletion(sm, CallAudioModeStateMachine.RUN_RUNNABLE);
+
+        resetMocks();
+
+        sm.sendMessage(params.messageType, params.externalState);
+        waitForStateMachineActionCompletion(sm, CallAudioModeStateMachine.RUN_RUNNABLE);
+
+        assertEquals(params.expectedFinalStateName, sm.getCurrentStateName());
+
+        switch (params.expectedFocus) {
+            case FOCUS_NO_CHANGE:
+                verify(mAudioManager, never()).requestAudioFocusForCall(anyInt(), anyInt());
+                break;
+            case FOCUS_OFF:
+                verify(mAudioManager).abandonAudioFocusForCall();
+                break;
+            case FOCUS_RING:
+                verify(mAudioManager).requestAudioFocusForCall(
+                        eq(AudioManager.STREAM_RING), anyInt());
+                break;
+            case FOCUS_VOICE:
+                verify(mAudioManager).requestAudioFocusForCall(
+                        eq(AudioManager.STREAM_VOICE_CALL), anyInt());
+                break;
+        }
+
+        if (params.expectedMode != NO_CHANGE) {
+            verify(mAudioManager).setMode(eq(params.expectedMode));
+        } else {
+            verify(mAudioManager, never()).setMode(anyInt());
+        }
+
+        switch (params.expectedRingingInteraction) {
+            case NO_CHANGE:
+                verify(mCallAudioManager, never()).startRinging();
+                verify(mCallAudioManager, never()).stopRinging();
+                break;
+            case ON:
+                verify(mCallAudioManager).startRinging();
+                break;
+            case OFF:
+                verify(mCallAudioManager).stopRinging();
+                break;
+        }
+
+        switch (params.expectedCallWaitingInteraction) {
+            case NO_CHANGE:
+                verify(mCallAudioManager, never()).startCallWaiting();
+                verify(mCallAudioManager, never()).stopCallWaiting();
+                break;
+            case ON:
+                verify(mCallAudioManager).startCallWaiting();
+                break;
+            case OFF:
+                verify(mCallAudioManager).stopCallWaiting();
+                break;
+        }
+    }
+
+    private void resetMocks() {
+        reset(mCallAudioManager, mAudioManager);
+    }
+}
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..776bd8c
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
@@ -0,0 +1,751 @@
+/*
+ * 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 android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.telecom.BluetoothManager;
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallAudioModeStateMachine;
+import com.android.server.telecom.CallAudioRouteStateMachine;
+import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.ConnectionServiceWrapper;
+import com.android.server.telecom.CallAudioManager;
+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 StateMachineTestBase<CallAudioRouteStateMachine> {
+    private static final int NONE = 0;
+    private static final int ON = 1;
+    private static final int OFF = 2;
+
+    static class RoutingTestParameters extends 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 boolean doesDeviceSupportEarpiece; // set to false in the case of Wear devices
+        public boolean shouldRunWithFocus;
+
+        public RoutingTestParameters(String name, int initialRoute, int availableRoutes, int
+                speakerInteraction, int bluetoothInteraction, int action, int expectedRoute, int
+                expectedAvailableRoutes, boolean doesDeviceSupportEarpiece,
+                boolean shouldRunWithFocus) {
+            this.name = name;
+            this.initialRoute = initialRoute;
+            this.availableRoutes = availableRoutes;
+            this.speakerInteraction = speakerInteraction;
+            this.bluetoothInteraction = bluetoothInteraction;
+            this.action = action;
+            this.expectedRoute = expectedRoute;
+            this.expectedAvailableRoutes = expectedAvailableRoutes;
+            this.doesDeviceSupportEarpiece = doesDeviceSupportEarpiece;
+            this.shouldRunWithFocus = shouldRunWithFocus;
+        }
+
+        @Override
+        public String toString() {
+            return "RoutingTestParameters{" +
+                    "name='" + name + '\'' +
+                    ", initialRoute=" + initialRoute +
+                    ", availableRoutes=" + availableRoutes +
+                    ", speakerInteraction=" + speakerInteraction +
+                    ", bluetoothInteraction=" + bluetoothInteraction +
+                    ", action=" + action +
+                    ", expectedRoute=" + expectedRoute +
+                    ", expectedAvailableRoutes=" + expectedAvailableRoutes +
+                    ", doesDeviceSupportEarpiece=" + doesDeviceSupportEarpiece +
+                    ", shouldRunWithFocus=" + shouldRunWithFocus +
+                    '}';
+        }
+    }
+
+    @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));
+    }
+
+    @LargeTest
+    public void testStateMachineTransitionsWithFocus() throws Throwable {
+        List<RoutingTestParameters> paramList = generateTransitionTests(true);
+        parametrizedTestStateMachine(paramList);
+    }
+
+    @LargeTest
+    public void testStateMachineTransitionsWithoutFocus() throws Throwable {
+        List<RoutingTestParameters> paramList = generateTransitionTests(false);
+        parametrizedTestStateMachine(paramList);
+    }
+
+    @MediumTest
+    public void testSpeakerPersistence() {
+        CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
+                mContext,
+                mockCallsManager,
+                mockBluetoothManager,
+                mockWiredHeadsetManager,
+                mockStatusBarNotifier,
+                mAudioServiceFactory,
+                true);
+
+        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);
+    }
+
+    @SmallTest
+    public void testInitializationWithEarpieceNoHeadsetNoBluetooth() {
+        CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_EARPIECE,
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER);
+        initializationTestHelper(expectedState, true);
+    }
+
+    @SmallTest
+    public void testInitializationWithEarpieceAndHeadsetNoBluetooth() {
+        CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_WIRED_HEADSET,
+                CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_SPEAKER);
+        initializationTestHelper(expectedState, true);
+    }
+
+    @SmallTest
+    public void testInitializationWithEarpieceAndHeadsetAndBluetooth() {
+        CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH,
+                CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_SPEAKER
+                | CallAudioState.ROUTE_BLUETOOTH);
+        initializationTestHelper(expectedState, true);
+    }
+
+    @SmallTest
+    public void testInitializationWithEarpieceAndBluetoothNoHeadset() {
+        CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH,
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER
+                        | CallAudioState.ROUTE_BLUETOOTH);
+        initializationTestHelper(expectedState, true);
+    }
+
+    @SmallTest
+    public void testInitializationWithNoEarpieceNoHeadsetNoBluetooth() {
+        CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_SPEAKER,
+                CallAudioState.ROUTE_SPEAKER);
+        initializationTestHelper(expectedState, false);
+    }
+
+    @SmallTest
+    public void testInitializationWithHeadsetNoBluetoothNoEarpiece() {
+        CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_WIRED_HEADSET,
+                CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_SPEAKER);
+        initializationTestHelper(expectedState, false);
+    }
+
+    @SmallTest
+    public void testInitializationWithHeadsetAndBluetoothNoEarpiece() {
+        CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH,
+                CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_SPEAKER
+                | CallAudioState.ROUTE_BLUETOOTH);
+        initializationTestHelper(expectedState, false);
+    }
+
+    @SmallTest
+    public void testInitializationWithBluetoothNoHeadsetNoEarpiece() {
+        CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH,
+                CallAudioState.ROUTE_SPEAKER | CallAudioState.ROUTE_BLUETOOTH);
+        initializationTestHelper(expectedState, false);
+    }
+
+    private void initializationTestHelper(CallAudioState expectedState,
+            boolean doesDeviceSupportEarpiece) {
+        when(mockWiredHeadsetManager.isPluggedIn()).thenReturn(
+                (expectedState.getSupportedRouteMask() & CallAudioState.ROUTE_WIRED_HEADSET) != 0);
+        when(mockBluetoothManager.isBluetoothAvailable()).thenReturn(
+                (expectedState.getSupportedRouteMask() & CallAudioState.ROUTE_BLUETOOTH) != 0);
+
+        CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
+                mContext,
+                mockCallsManager,
+                mockBluetoothManager,
+                mockWiredHeadsetManager,
+                mockStatusBarNotifier,
+                mAudioServiceFactory,
+                doesDeviceSupportEarpiece);
+        stateMachine.initialize();
+        assertEquals(expectedState, stateMachine.getCurrentCallAudioState());
+    }
+
+    private List<RoutingTestParameters> generateTransitionTests(boolean shouldRunWithFocus) {
+        List<RoutingTestParameters> params = new ArrayList<>();
+        params.add(new RoutingTestParameters(
+                "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
+                true, // doesDeviceSupportEarpiece
+                shouldRunWithFocus
+        ));
+
+        params.add(new RoutingTestParameters(
+                "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, // expectedAvai
+                true, // doesDeviceSupportEarpiece
+                shouldRunWithFocus
+        ));
+
+        params.add(new RoutingTestParameters(
+                "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
+                true, // doesDeviceSupportEarpiece
+                shouldRunWithFocus
+        ));
+
+        params.add(new RoutingTestParameters(
+                "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
+                true, // doesDeviceSupportEarpiece
+                shouldRunWithFocus
+        ));
+
+        params.add(new RoutingTestParameters(
+                "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, // expectedAvailable
+                true, // doesDeviceSupportEarpiece
+                shouldRunWithFocus
+        ));
+
+        params.add(new RoutingTestParameters(
+                "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, // expectedAvailable
+                true, // doesDeviceSupportEarpiece
+                shouldRunWithFocus
+        ));
+
+        params.add(new RoutingTestParameters(
+                "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
+                true, // doesDeviceSupportEarpiece
+                shouldRunWithFocus
+        ));
+
+        params.add(new RoutingTestParameters(
+                "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, // expectedAvailable
+                true, // doesDeviceSupportEarpiece
+                shouldRunWithFocus
+        ));
+
+        params.add(new RoutingTestParameters(
+                "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, // expectedAvailable
+                true, // doesDeviceSupportEarpiece
+                shouldRunWithFocus
+        ));
+
+        params.add(new RoutingTestParameters(
+                "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, // expectedAvai
+                true, // doesDeviceSupportEarpiece
+                shouldRunWithFocus
+        ));
+
+        params.add(new RoutingTestParameters(
+                "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, // expectedAvailable
+                true, // doesDeviceSupportEarpiece
+                shouldRunWithFocus
+        ));
+
+        params.add(new RoutingTestParameters(
+                "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
+                true, // doesDeviceSupportEarpiece
+                shouldRunWithFocus
+        ));
+
+        params.add(new RoutingTestParameters(
+                "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
+                true, // doesDeviceSupportEarpiece
+                shouldRunWithFocus
+        ));
+
+        params.add(new RoutingTestParameters(
+                "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
+                true, // doesDeviceSupportEarpiece
+                shouldRunWithFocus
+        ));
+
+        params.add(new RoutingTestParameters(
+                "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
+                true, // doesDeviceSupportEarpiece
+                shouldRunWithFocus
+        ));
+
+        params.add(new RoutingTestParameters(
+                "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
+                true, // doesDeviceSupportEarpiece
+                shouldRunWithFocus
+        ));
+
+        params.add(new RoutingTestParameters(
+                "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
+                true, // doesDeviceSupportEarpiece
+                shouldRunWithFocus
+        ));
+
+        params.add(new RoutingTestParameters(
+                "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, // expectedAvai
+                true, // doesDeviceSupportEarpiece
+                shouldRunWithFocus
+        ));
+
+        params.add(new RoutingTestParameters(
+                "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, // expectedAvailable
+                true, // doesDeviceSupportEarpiece
+                shouldRunWithFocus
+        ));
+
+        params.add(new RoutingTestParameters(
+                "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
+                true, // doesDeviceSupportEarpiece
+                shouldRunWithFocus
+        ));
+
+        params.add(new RoutingTestParameters(
+                "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, // expectedAvailable
+                true, // doesDeviceSupportEarpiece
+                shouldRunWithFocus
+        ));
+
+        params.add(new RoutingTestParameters(
+                "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, // expectedAvailable
+                true, // doesDeviceSupportEarpiece
+                shouldRunWithFocus
+        ));
+
+        params.add(new RoutingTestParameters(
+                "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, // expectedAvai
+                true, // doesDeviceSupportEarpiece
+                shouldRunWithFocus
+        ));
+
+        params.add(new RoutingTestParameters(
+                "Switch from bluetooth to wired/earpiece when neither are available", // name
+                CallAudioState.ROUTE_BLUETOOTH, // initialRoute
+                CallAudioState.ROUTE_BLUETOOTH, // availableRoutes
+                ON, // speakerInteraction
+                OFF, // bluetoothInteraction
+                CallAudioRouteStateMachine.SWITCH_BASELINE_ROUTE, // action
+                CallAudioState.ROUTE_SPEAKER, // expectedRoute
+                CallAudioState.ROUTE_BLUETOOTH, // expectedAvailableRoutes
+                false, // doesDeviceSupportEarpiece
+                shouldRunWithFocus
+        ));
+
+        params.add(new RoutingTestParameters(
+                "Disconnect wired headset when device does not support earpiece", // name
+                CallAudioState.ROUTE_WIRED_HEADSET, // initialRoute
+                CallAudioState.ROUTE_WIRED_HEADSET, // availableRoutes
+                ON, // speakerInteraction
+                NONE, // bluetoothInteraction
+                CallAudioRouteStateMachine.DISCONNECT_WIRED_HEADSET, // action
+                CallAudioState.ROUTE_SPEAKER, // expectedRoute
+                CallAudioState.ROUTE_SPEAKER, // expectedAvailableRoutes
+                false, // doesDeviceSupportEarpiece
+                shouldRunWithFocus
+        ));
+
+        return params;
+    }
+
+    @Override
+    protected void runParametrizedTestCase(TestParameters _params) throws Throwable {
+        RoutingTestParameters params = (RoutingTestParameters) _params;
+        if (params.shouldRunWithFocus) {
+            runParametrizedTestCaseWithFocus(params);
+        } else {
+            runParametrizedTestCaseWithoutFocus(params);
+        }
+    }
+
+    private void runParametrizedTestCaseWithFocus(final RoutingTestParameters params)
+            throws Throwable {
+        resetMocks();
+
+        // Construct a fresh state machine on every case
+        final CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
+                mContext,
+                mockCallsManager,
+                mockBluetoothManager,
+                mockWiredHeadsetManager,
+                mockStatusBarNotifier,
+                mAudioServiceFactory,
+                params.doesDeviceSupportEarpiece);
+
+        // 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
+        final 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);
+
+        waitForStateMachineActionCompletion(stateMachine, CallAudioRouteStateMachine.RUN_RUNNABLE);
+
+        stateMachine.quitStateMachine();
+
+        // 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).connectBluetoothAudio();
+
+                verify(mockBluetoothManager, never()).disconnectBluetoothAudio();
+                break;
+            case OFF:
+                verify(mockBluetoothManager, never()).connectBluetoothAudio();
+                verify(mockBluetoothManager).disconnectBluetoothAudio();
+        }
+
+        switch (params.speakerInteraction) {
+            case NONE:
+                verify(mockAudioManager, never()).setSpeakerphoneOn(any(Boolean.class));
+                break;
+            case ON: // fall through
+            case OFF:
+                verify(mockAudioManager).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(final RoutingTestParameters params)
+            throws Throwable {
+        resetMocks();
+
+        // Construct a fresh state machine on every case
+        final CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
+                mContext,
+                mockCallsManager,
+                mockBluetoothManager,
+                mockWiredHeadsetManager,
+                mockStatusBarNotifier,
+                mAudioServiceFactory,
+                params.doesDeviceSupportEarpiece);
+
+        // 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);
+
+        waitForStateMachineActionCompletion(stateMachine, CallAudioModeStateMachine.RUN_RUNNABLE);
+
+        stateMachine.quitStateMachine();
+
+        // 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..7f5297a
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java
@@ -0,0 +1,625 @@
+/*
+ * 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 android.test.suitebuilder.annotation.MediumTest;
+
+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.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doAnswer;
+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 org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+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 = 200;
+    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);
+
+        doAnswer(new Answer<Uri>() {
+            @Override
+            public Uri answer(InvocationOnMock invocation) throws Throwable {
+                return (Uri) invocation.getArguments()[1];
+            }
+        }).when(mContentProvider).insert(anyString(), any(Uri.class), any(ContentValues.class));
+
+        when(userManager.isUserRunning(any(UserHandle.class))).thenReturn(true);
+        when(userManager.isUserUnlocked(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);
+    }
+
+    @MediumTest
+    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();
+    }
+
+    @MediumTest
+    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();
+    }
+
+    @MediumTest
+    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();
+    }
+
+    @MediumTest
+    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));
+    }
+
+    @MediumTest
+    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));
+    }
+
+    @MediumTest
+    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));
+    }
+
+    @MediumTest
+    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));
+    }
+
+    @MediumTest
+    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);
+    }
+
+    @MediumTest
+    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);
+    }
+
+    @MediumTest
+    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);
+    }
+
+    @MediumTest
+    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);
+    }
+
+    @MediumTest
+    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);
+    }
+
+    @MediumTest
+    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));
+    }
+
+    @MediumTest
+    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));
+    }
+
+    @MediumTest
+    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.
+     */
+    @MediumTest
+    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.
+     */
+    @MediumTest
+    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).atLeastOnce())
+                    .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..7ac324e 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,12 +172,24 @@
                     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;
             }
         }
 
         @Override
+        public String getSystemServiceName(Class<?> svcClass) {
+            if (svcClass == UserManager.class) {
+                return Context.USER_SERVICE;
+            }
+            throw new UnsupportedOperationException();
+        }
+
+        @Override
         public int getUserId() {
             return 0;
         }
@@ -170,7 +201,7 @@
 
         @Override
         public String getOpPackageName() {
-            return "test";
+            return "com.android.server.telecom.tests";
         }
 
         @Override
@@ -179,7 +210,7 @@
                 @Override
                 protected IContentProvider acquireProvider(Context c, String name) {
                     Log.i(this, "acquireProvider %s", name);
-                    return mock(IContentProvider.class);
+                    return mContentProvider;
                 }
 
                 @Override
@@ -190,7 +221,7 @@
                 @Override
                 protected IContentProvider acquireUnstableProvider(Context c, String name) {
                     Log.i(this, "acquireUnstableProvider %s", name);
-                    return mock(IContentProvider.class);
+                    return mContentProvider;
                 }
 
                 @Override
@@ -211,6 +242,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 +258,11 @@
         }
 
         @Override
+        public void sendBroadcastAsUser(Intent intent, UserHandle userHandle) {
+            // TODO -- need to ensure this is captured
+        }
+
+        @Override
         public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user,
                 String receiverPermission, BroadcastReceiver resultReceiver, Handler scheduler,
                 int initialCode, String initialData, Bundle initialExtras) {
@@ -238,13 +280,30 @@
                 throws PackageManager.NameNotFoundException {
             return this;
         }
-    };
+
+        @Override
+        public int checkCallingOrSelfPermission(String permission) {
+            return PackageManager.PERMISSION_GRANTED;
+        }
+
+        @Override
+        public void enforceCallingOrSelfPermission(String permission, String message) {
+            // Don't bother enforcing anything in mock.
+        }
+
+        @Override
+        public void startActivityAsUser(Intent intent, UserHandle userHandle) {
+            // For capturing
+        }
+    }
 
     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 +338,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 +396,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 +430,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 +442,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 +476,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 903028c..17a4d90 100644
--- a/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
+++ b/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
@@ -27,19 +27,24 @@
 import org.mockito.Mockito;
 
 import android.content.ComponentName;
+import android.net.Uri;
 import android.os.Bundle;
 import android.os.IBinder;
 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;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.StatusHints;
+import android.telecom.TelecomManager;
 
+import java.lang.Override;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -47,13 +52,82 @@
 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> {
+    static int INVALID_VIDEO_STATE = -1;
+
+    /**
+     * Implementation of ConnectionService that performs no-ops for tasks normally meant for
+     * Telephony and reports success back to Telecom
+     */
+    public class FakeConnectionServiceDelegate extends ConnectionService {
+        int mVideoState = INVALID_VIDEO_STATE;
+
+        @Override
+        public Connection onCreateUnknownConnection(
+                PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request) {
+            return new FakeConnection(request.getVideoState(), request.getAddress());
+        }
+
+        @Override
+        public Connection onCreateIncomingConnection(
+                PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request) {
+            return new FakeConnection(
+                    mVideoState == INVALID_VIDEO_STATE ? request.getVideoState() : mVideoState,
+                    request.getAddress());
+        }
+
+        @Override
+        public Connection onCreateOutgoingConnection(
+                PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request) {
+            return new FakeConnection(request.getVideoState(), request.getAddress());
+        }
+
+        @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(int videoState, Uri address) {
+            super();
+            int capabilities = getConnectionCapabilities();
+            capabilities |= CAPABILITY_MUTE;
+            capabilities |= CAPABILITY_SUPPORT_HOLD;
+            capabilities |= CAPABILITY_HOLD;
+            setVideoState(videoState);
+            setConnectionCapabilities(capabilities);
+            setActive();
+            setAddress(address, TelecomManager.PRESENTATION_ALLOWED);
+        }
+    }
+
+    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 {
 
@@ -63,6 +137,7 @@
             if (!mConnectionServiceAdapters.add(adapter)) {
                 throw new RuntimeException("Adapter already added: " + adapter);
             }
+            mConnectionServiceDelegateAdapter.addConnectionServiceAdapter(adapter);
         }
 
         @Override
@@ -71,6 +146,7 @@
             if (!mConnectionServiceAdapters.remove(adapter)) {
                 throw new RuntimeException("Adapter never added: " + adapter);
             }
+            mConnectionServiceDelegateAdapter.removeConnectionServiceAdapter(adapter);
         }
 
         @Override
@@ -91,7 +167,12 @@
             c.isIncoming = isIncoming;
             c.isUnknown = isUnknown;
             c.capabilities |= Connection.CAPABILITY_HOLD | Connection.CAPABILITY_SUPPORT_HOLD;
+            c.videoState = request.getVideoState();
+            c.mockVideoProvider = new MockVideoProvider();
+            c.videoProvider = c.mockVideoProvider.getInterface();
             mConnectionById.put(id, c);
+            mConnectionServiceDelegateAdapter.createConnection(connectionManagerPhoneAccount,
+                    id, request, isIncoming, isUnknown);
         }
 
         @Override
@@ -113,6 +194,9 @@
         public void disconnect(String callId) throws RemoteException { }
 
         @Override
+        public void silence(String callId) throws RemoteException { }
+
+        @Override
         public void hold(String callId) throws RemoteException { }
 
         @Override
@@ -129,7 +213,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 { }
@@ -152,7 +238,12 @@
         public IInterface queryLocalInterface(String descriptor) {
             return this;
         }
-    };
+    }
+
+    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);
@@ -174,6 +265,8 @@
         int callerDisplayNamePresentation;
         final List<String> conferenceableConnectionIds = new ArrayList<>();
         IVideoProvider videoProvider;
+        Connection.VideoProvider videoProviderImpl;
+        MockVideoProvider mockVideoProvider;
         int videoState;
         boolean isVoipAudioMode;
         Bundle extras;
diff --git a/tests/src/com/android/server/telecom/tests/ContactsAsyncHelperTest.java b/tests/src/com/android/server/telecom/tests/ContactsAsyncHelperTest.java
new file mode 100644
index 0000000..363d613
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/ContactsAsyncHelperTest.java
@@ -0,0 +1,157 @@
+/*
+ * 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.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.telecom.ContactsAsyncHelper;
+
+import org.mockito.ArgumentCaptor;
+
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyObject;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isNull;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+public class ContactsAsyncHelperTest extends TelecomTestCase {
+    private static final Uri SAMPLE_CONTACT_PHOTO_URI = Uri.parse(
+            "android.resource://com.android.server.telecom.tests/"
+                    + R.drawable.contacts_sample_photo);
+
+    private static final Uri SAMPLE_CONTACT_PHOTO_URI_SMALL = Uri.parse(
+            "android.resource://com.android.server.telecom.tests/"
+                    + R.drawable.contacts_sample_photo_small);
+
+    private static final int TOKEN = 4847524;
+    private static final int TEST_TIMEOUT = 500;
+    private static final Object COOKIE = new Object();
+
+    public static class ImageLoadListenerImpl
+            implements ContactsAsyncHelper.OnImageLoadCompleteListener {
+        @Override
+        public void onImageLoadComplete(int token, Drawable photo,
+                Bitmap photoIcon, Object cookie) {
+        }
+    }
+
+    private ImageLoadListenerImpl mListener = spy(new ImageLoadListenerImpl());
+
+    private ContactsAsyncHelper.ContentResolverAdapter mWorkingContentResolverAdapter =
+            new ContactsAsyncHelper.ContentResolverAdapter() {
+                @Override
+                public InputStream openInputStream(Context context, Uri uri)
+                        throws FileNotFoundException {
+                    return context.getContentResolver().openInputStream(uri);
+                }
+            };
+
+    private ContactsAsyncHelper.ContentResolverAdapter mNullContentResolverAdapter =
+            new ContactsAsyncHelper.ContentResolverAdapter() {
+                @Override
+                public InputStream openInputStream(Context context, Uri uri)
+                        throws FileNotFoundException {
+                    return null;
+                }
+            };
+
+    @Override
+    public void setUp() throws Exception {
+        mContext = getTestContext();
+        super.setUp();
+    }
+
+    @SmallTest
+    public void testEmptyUri() {
+        ContactsAsyncHelper cah = new ContactsAsyncHelper(mNullContentResolverAdapter);
+        try {
+            cah.startObtainPhotoAsync(TOKEN, mContext, null, mListener, COOKIE);
+        } catch (IllegalStateException e) {
+            // expected to fail
+        }
+        verify(mListener, timeout(TEST_TIMEOUT).never()).onImageLoadComplete(anyInt(),
+                any(Drawable.class), any(Bitmap.class), anyObject());
+    }
+
+    @SmallTest
+    public void testNullReturnFromOpenInputStream() {
+        ContactsAsyncHelper cah = new ContactsAsyncHelper(mNullContentResolverAdapter);
+        cah.startObtainPhotoAsync(TOKEN, mContext, SAMPLE_CONTACT_PHOTO_URI, mListener, COOKIE);
+
+        verify(mListener, timeout(TEST_TIMEOUT)).onImageLoadComplete(eq(TOKEN),
+                isNull(Drawable.class), isNull(Bitmap.class), eq(COOKIE));
+    }
+
+    @SmallTest
+    public void testImageScaling() {
+        ContactsAsyncHelper cah = new ContactsAsyncHelper(mWorkingContentResolverAdapter);
+        cah.startObtainPhotoAsync(TOKEN, mContext, SAMPLE_CONTACT_PHOTO_URI, mListener, COOKIE);
+
+        ArgumentCaptor<Drawable> photoCaptor = ArgumentCaptor.forClass(Drawable.class);
+        ArgumentCaptor<Bitmap> iconCaptor = ArgumentCaptor.forClass(Bitmap.class);
+
+        verify(mListener, timeout(TEST_TIMEOUT)).onImageLoadComplete(eq(TOKEN),
+                photoCaptor.capture(), iconCaptor.capture(), eq(COOKIE));
+
+        Bitmap capturedPhoto = ((BitmapDrawable) photoCaptor.getValue()).getBitmap();
+        assertTrue(getExpectedPhoto(SAMPLE_CONTACT_PHOTO_URI).sameAs(capturedPhoto));
+        int iconSize = mContext.getResources()
+                .getDimensionPixelSize(R.dimen.notification_icon_size);
+        assertTrue(iconSize >= iconCaptor.getValue().getHeight());
+        assertTrue(iconSize >= iconCaptor.getValue().getWidth());
+    }
+
+    @SmallTest
+    public void testNoScaling() {
+        ContactsAsyncHelper cah = new ContactsAsyncHelper(mWorkingContentResolverAdapter);
+        cah.startObtainPhotoAsync(TOKEN, mContext, SAMPLE_CONTACT_PHOTO_URI_SMALL,
+                mListener, COOKIE);
+
+        ArgumentCaptor<Drawable> photoCaptor = ArgumentCaptor.forClass(Drawable.class);
+        ArgumentCaptor<Bitmap> iconCaptor = ArgumentCaptor.forClass(Bitmap.class);
+
+        verify(mListener, timeout(TEST_TIMEOUT)).onImageLoadComplete(eq(TOKEN),
+                photoCaptor.capture(), iconCaptor.capture(), eq(COOKIE));
+
+        Bitmap capturedPhoto = ((BitmapDrawable) photoCaptor.getValue()).getBitmap();
+        assertTrue(getExpectedPhoto(SAMPLE_CONTACT_PHOTO_URI_SMALL).sameAs(capturedPhoto));
+        assertTrue(capturedPhoto.sameAs(iconCaptor.getValue()));
+    }
+
+    private Bitmap getExpectedPhoto(Uri uri) {
+        InputStream is;
+        try {
+            is = mContext.getContentResolver().openInputStream(uri);
+        } catch (FileNotFoundException e) {
+            return null;
+        }
+
+        Drawable d = Drawable.createFromStream(is, uri.toString());
+        return ((BitmapDrawable) d).getBitmap();
+    }
+}
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..53e58af 100644
--- a/tests/src/com/android/server/telecom/tests/InCallServiceFixture.java
+++ b/tests/src/com/android/server/telecom/tests/InCallServiceFixture.java
@@ -45,6 +45,7 @@
     public boolean mBringToForeground;
     public boolean mShowDialpad;
     public boolean mCanAddCall;
+    public boolean mSilenceRinger;
 
     public class FakeInCallService extends IInCallService.Stub {
         @Override
@@ -105,6 +106,11 @@
         }
 
         @Override
+        public void silenceRinger() throws RemoteException {
+            mSilenceRinger = true;
+        }
+
+        @Override
         public IBinder asBinder() {
             return this;
         }
@@ -128,4 +134,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/InCallWakeLockControllerTest.java b/tests/src/com/android/server/telecom/tests/InCallWakeLockControllerTest.java
index bc294aa..76bac9f 100644
--- a/tests/src/com/android/server/telecom/tests/InCallWakeLockControllerTest.java
+++ b/tests/src/com/android/server/telecom/tests/InCallWakeLockControllerTest.java
@@ -16,91 +16,95 @@
 
 package com.android.server.telecom.tests;
 
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 import static org.mockito.Mockito.never;
 
-import android.content.Context;
 import android.os.PowerManager;
 
 import com.android.server.telecom.Call;
 import com.android.server.telecom.CallState;
 import com.android.server.telecom.CallsManager;
 import com.android.server.telecom.InCallWakeLockController;
+import com.android.server.telecom.TelecomWakeLock;
 
 import org.mockito.Mock;
 
-/**
- * TODO: The tests here are disabled because they depend on classes {@code PowerManager} and
- * {@code PowerManager.WakeLock}, which are both {@code final} and therefore cannot easily be
- * mocked.
- *
- * At the moment, we are using an {@link com.android.server.telecom.InCallWakeLockControllerFactory}
- * in the system under test to abstract out this class, and are assuming it's simple enough that it
- * is not the highest priority to test.
- */
 public class InCallWakeLockControllerTest extends TelecomTestCase {
 
-    @Mock Context mContext;
-    @Mock PowerManager mPowerManager;
-    @Mock PowerManager.WakeLock mWakeLock;
     @Mock CallsManager mCallsManager;
     @Mock Call mCall;
-
+    @Mock TelecomWakeLock.WakeLockAdapter mWakeLockAdapter;
     private InCallWakeLockController mInCallWakeLockController;
 
     @Override
     public void setUp() throws Exception {
-        /*
         super.setUp();
-
-        when(mContext.getSystemService(Context.POWER_SERVICE)).thenReturn(mPowerManager);
-        when(mPowerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, "InCallWakeLockController"))
-                .thenReturn(mWakeLock);
-        mInCallWakeLockController = new InCallWakeLockController(mContext, mCallsManager);
-        */
+        TelecomWakeLock telecomWakeLock = new TelecomWakeLock(
+                null, //context never used due to mock WakeLockAdapter
+                mWakeLockAdapter, PowerManager.FULL_WAKE_LOCK,
+                InCallWakeLockControllerTest.class.getSimpleName());
+        mInCallWakeLockController = new InCallWakeLockController(telecomWakeLock, mCallsManager);
     }
 
     @Override
     public void tearDown() throws Exception {
-        /*
+        mInCallWakeLockController = null;
         super.tearDown();
-        */
     }
 
-    public void DONT_test_RingingCallAdded() throws Exception {
+    public void testRingingCallAdded() throws Exception {
         when(mCallsManager.getRingingCall()).thenReturn(mCall);
+        when(mWakeLockAdapter.isHeld()).thenReturn(false);
+
         mInCallWakeLockController.onCallAdded(mCall);
-        verify(mWakeLock).acquire();
+
+        verify(mWakeLockAdapter).acquire();
     }
 
-    public void DONT_test_NonRingingCallAdded() throws Exception {
+    public void testNonRingingCallAdded() throws Exception {
         when(mCallsManager.getRingingCall()).thenReturn(null);
-        when(mWakeLock.isHeld()).thenReturn(false);
+        when(mWakeLockAdapter.isHeld()).thenReturn(false);
 
         mInCallWakeLockController.onCallAdded(mCall);
-        verify(mWakeLock, never()).acquire();
+
+        verify(mWakeLockAdapter, never()).acquire();
     }
 
-    public void DONT_test_RingingCallTransition() throws Exception {
+    public void testRingingCallTransition() throws Exception {
         when(mCallsManager.getRingingCall()).thenReturn(mCall);
+        when(mWakeLockAdapter.isHeld()).thenReturn(false);
+
         mInCallWakeLockController.onCallStateChanged(mCall, CallState.NEW, CallState.RINGING);
-        verify(mWakeLock).acquire();
+
+        verify(mWakeLockAdapter).acquire();
     }
 
-    public void DONT_test_RingingCallRemoved() throws Exception {
+    public void testRingingCallRemoved() throws Exception {
         when(mCallsManager.getRingingCall()).thenReturn(null);
-        when(mWakeLock.isHeld()).thenReturn(false);
+        when(mWakeLockAdapter.isHeld()).thenReturn(false);
 
         mInCallWakeLockController.onCallRemoved(mCall);
-        verify(mWakeLock, never()).acquire();
+
+        verify(mWakeLockAdapter, never()).acquire();
     }
 
-    public void DONT_test_WakeLockReleased() throws Exception {
+    public void testWakeLockReleased() throws Exception {
         when(mCallsManager.getRingingCall()).thenReturn(null);
-        when(mWakeLock.isHeld()).thenReturn(true);
+        when(mWakeLockAdapter.isHeld()).thenReturn(true);
 
         mInCallWakeLockController.onCallRemoved(mCall);
-        verify(mWakeLock).release();
+
+        verify(mWakeLockAdapter).release(0);
+    }
+
+    public void testAcquireWakeLockWhenHeld() throws Exception {
+        when(mCallsManager.getRingingCall()).thenReturn(mCall);
+        when(mWakeLockAdapter.isHeld()).thenReturn(true);
+
+        mInCallWakeLockController.onCallAdded(mock(Call.class));
+
+        verify(mWakeLockAdapter, never()).acquire();
     }
 }
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..d6c534e
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/LogTest.java
@@ -0,0 +1,545 @@
+/*
+ * 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.Runnable;
+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(processMessage(msg));
+                }
+            }
+        }
+
+        @Override
+        public synchronized void i(String msgTag, String msg) {
+            if (msgTag.equals(LogTest.TESTING_TAG)) {
+                synchronized (this) {
+                    receivedStrings.add(processMessage(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);
+            }
+        }
+
+        // Remove Unnecessary parts of message string before processing
+        private String processMessage(String msg) {
+            if(msg.contains(Session.CREATE_SUBSESSION)) {
+                return clipMsg(Session.CREATE_SUBSESSION, msg);
+            }
+            if(msg.contains(Session.CONTINUE_SUBSESSION)) {
+                return clipMsg(Session.CONTINUE_SUBSESSION, msg);
+            }
+            if (msg.contains(Session.END_SUBSESSION)) {
+                return clipMsg(Session.END_SUBSESSION, msg);
+            }
+            if (msg.contains(Session.END_SESSION)) {
+                return clipMsg(Session.END_SESSION, msg);
+            }
+            return msg;
+        }
+
+        private String clipMsg(String id, String msg) {
+                int clipStartIndex = msg.indexOf(id) + id.length();
+                int clipEndIndex = msg.lastIndexOf(":");
+                return msg.substring(0, clipStartIndex) + msg.substring(clipEndIndex, msg.length());
+        }
+    }
+
+    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 java.lang.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.setIsExtendedLoggingEnabled(true);
+        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 Log.DEFAULT_SESSION_TIMEOUT_MS;
+            }
+        };
+    }
+
+    @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", 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, sessionName + "->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, sessionName + "->lTSCH.hM", "_0", 0,
+                TEST_VERIFY_TIMEOUT_MS);
+        verifyEventResult(Session.CREATE_SUBSESSION, sessionName + "->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, sessionName + "->lTSCH.hM->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, sessionName + "->lTSCH.hM", "_0", i, 0);
+            verifyEventResult(Session.CREATE_SUBSESSION, sessionName + "->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, sessionName + "->lTSCH.hM->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, "", "", 0, 0);
+        verifyEventResult(Session.END_SUBSESSION, sessionName, "", 0, 0);
+        verifyMethodCall("", sessionName, 0, "", TEST_ENTER_METHOD4, 0);
+        verifyEventResult(Session.END_SUBSESSION, sessionName, "", 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 java.lang.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, "", "", 0, 0);
+        verifyMethodCall("", sessionName, 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 + ": " +  logText + ": " +
+                buildExpectedSession(shortMethodName, sessionId) + subsessionId;
+    }
+
+    private void verifyContinueEventResult(String shortOldMethodName, String shortNewMethodName,
+                String subsession, int sessionId, int timeoutMs) {
+        String expectedSession = buildExpectedSession(shortNewMethodName, sessionId);
+        if(!shortNewMethodName.isEmpty()) {
+            shortOldMethodName += "->";
+        }
+        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,event + ": "  +
+                expectedSession + subsession);
+        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..c997b31
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/MissedCallNotifierImplTest.java
@@ -0,0 +1,383 @@
+/*
+ * 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.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.net.Uri;
+import android.os.UserHandle;
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccount.Builder;
+import android.telecom.PhoneAccountHandle;
+import android.telecom.TelecomManager;
+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.PhoneAccountRegistrar;
+import com.android.server.telecom.TelecomBroadcastIntentProcessor;
+import com.android.server.telecom.components.TelecomBroadcastReceiver;
+import com.android.server.telecom.ui.MissedCallNotifierImpl;
+import com.android.server.telecom.ui.MissedCallNotifierImpl.NotificationBuilderFactory;
+
+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;
+    }
+
+    private static final UserHandle PRIMARY_USER = UserHandle.of(0);
+    private static final UserHandle SECONARY_USER = UserHandle.of(12);
+    private static final int NO_CAPABILITY = 0;
+
+    @Mock
+    private NotificationManager mNotificationManager;
+
+    @Mock
+    private PhoneAccountRegistrar mPhoneAccountRegistrar;
+
+    @Mock
+    private TelecomManager mTelecomManager;
+
+    @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);
+        mComponentContextFixture.setTelecomManager(mTelecomManager);
+    }
+
+    public void testCancelNotificationInPrimaryUser() {
+        cancelNotificationTestInternal(PRIMARY_USER);
+    }
+
+    public void testCancelNotificationInSecondaryUser() {
+        cancelNotificationTestInternal(SECONARY_USER);
+    }
+
+    private void cancelNotificationTestInternal(UserHandle userHandle) {
+        Notification.Builder builder1 = makeNotificationBuilder("builder1");
+        Notification.Builder builder2 = makeNotificationBuilder("builder2");
+        MissedCallNotifierImpl.NotificationBuilderFactory fakeBuilderFactory =
+                makeNotificationBuilderFactory(builder1, builder1, builder2, builder2);
+
+        MissedCallNotifier missedCallNotifier = makeMissedCallNotifier(fakeBuilderFactory,
+                PRIMARY_USER);
+        PhoneAccount phoneAccount = makePhoneAccount(userHandle, NO_CAPABILITY);
+        Call fakeCall = makeFakeCall(TEL_CALL_HANDLE, CALLER_NAME, CALL_TIMESTAMP,
+                phoneAccount.getAccountHandle());
+
+        missedCallNotifier.showMissedCallNotification(fakeCall);
+        missedCallNotifier.clearMissedCalls(userHandle);
+        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));
+        verify(mNotificationManager).cancelAsUser(any(String.class), eq(requestIdCaptor.getValue()),
+                eq(userHandle));
+
+        // 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));
+        }
+
+        PhoneAccount phoneAccount = makePhoneAccount(PRIMARY_USER, NO_CAPABILITY);
+        Call fakeCall = makeFakeCall(TEL_CALL_HANDLE, CALLER_NAME, CALL_TIMESTAMP,
+                phoneAccount.getAccountHandle());
+
+        MissedCallNotifierImpl.NotificationBuilderFactory fakeBuilderFactory =
+                makeNotificationBuilderFactory(builders);
+
+        MissedCallNotifier missedCallNotifier = new MissedCallNotifierImpl(mContext,
+                mPhoneAccountRegistrar, 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(PRIMARY_USER));
+        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 testNotifySingleCallInPrimaryUser() {
+        PhoneAccount phoneAccount = makePhoneAccount(PRIMARY_USER, NO_CAPABILITY);
+        notifySingleCallTestInternal(phoneAccount, PRIMARY_USER);
+    }
+
+    public void testNotifySingleCallInSecondaryUser() {
+        PhoneAccount phoneAccount = makePhoneAccount(SECONARY_USER, NO_CAPABILITY);
+        notifySingleCallTestInternal(phoneAccount, PRIMARY_USER);
+    }
+
+    public void testNotifySingleCallInSecondaryUserWithMultiUserCapability() {
+        PhoneAccount phoneAccount = makePhoneAccount(PRIMARY_USER,
+                PhoneAccount.CAPABILITY_MULTI_USER);
+        notifySingleCallTestInternal(phoneAccount, PRIMARY_USER);
+    }
+
+    public void testNotifySingleCallWhenCurrentUserIsSecondaryUser() {
+        PhoneAccount phoneAccount = makePhoneAccount(PRIMARY_USER, NO_CAPABILITY);
+        notifySingleCallTestInternal(phoneAccount, SECONARY_USER);
+    }
+
+    public void testNotifySingleCall() {
+        PhoneAccount phoneAccount = makePhoneAccount(PRIMARY_USER, NO_CAPABILITY);
+        notifySingleCallTestInternal(phoneAccount, PRIMARY_USER);
+    }
+
+    private void notifySingleCallTestInternal(PhoneAccount phoneAccount, UserHandle currentUser) {
+        Notification.Builder builder1 = makeNotificationBuilder("builder1");
+        Notification.Builder builder2 = makeNotificationBuilder("builder2");
+        MissedCallNotifierImpl.NotificationBuilderFactory fakeBuilderFactory =
+                makeNotificationBuilderFactory(builder1, builder2);
+
+        MissedCallNotifier missedCallNotifier = makeMissedCallNotifier(fakeBuilderFactory,
+                currentUser);
+
+        Call fakeCall = makeFakeCall(TEL_CALL_HANDLE, CALLER_NAME, CALL_TIMESTAMP,
+                phoneAccount.getAccountHandle());
+        missedCallNotifier.showMissedCallNotification(fakeCall);
+
+        ArgumentCaptor<Notification> notificationArgumentCaptor = ArgumentCaptor.forClass(
+                Notification.class);
+
+        UserHandle expectedUserHandle;
+        if (phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_MULTI_USER)) {
+            expectedUserHandle = currentUser;
+        } else {
+            expectedUserHandle = phoneAccount.getAccountHandle().getUserHandle();
+        }
+        verify(mNotificationManager).notifyAsUser(isNull(String.class), eq(1),
+                notificationArgumentCaptor.capture(), eq((expectedUserHandle)));
+
+        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,
+                mPhoneAccountRegistrar, fakeBuilderFactory);
+        PhoneAccount phoneAccount = makePhoneAccount(PRIMARY_USER, NO_CAPABILITY);
+
+        Call fakeCall =
+                makeFakeCall(SIP_CALL_HANDLE, CALLER_NAME, CALL_TIMESTAMP,
+                phoneAccount.getAccountHandle());
+        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,
+            PhoneAccountHandle phoneAccountHandle) {
+        Call fakeCall = mock(Call.class);
+        when(fakeCall.getHandle()).thenReturn(handle);
+        when(fakeCall.getName()).thenReturn(name);
+        when(fakeCall.getCreationTimeMillis()).thenReturn(timestamp);
+        when(fakeCall.getTargetPhoneAccount()).thenReturn(phoneAccountHandle);
+        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;
+    }
+
+    private MissedCallNotifier makeMissedCallNotifier(
+            NotificationBuilderFactory fakeBuilderFactory, UserHandle currentUser) {
+        MissedCallNotifier missedCallNotifier = new MissedCallNotifierImpl(mContext,
+                mPhoneAccountRegistrar, fakeBuilderFactory);
+        missedCallNotifier.setCurrentUserHandle(currentUser);
+        return missedCallNotifier;
+    }
+
+    private PhoneAccount makePhoneAccount(UserHandle userHandle, int capability) {
+        ComponentName componentName = new ComponentName("com.anything", "com.whatever");
+        PhoneAccountHandle phoneAccountHandle = new PhoneAccountHandle(componentName, "id",
+                userHandle);
+        PhoneAccount.Builder builder = new PhoneAccount.Builder(phoneAccountHandle, "test");
+        builder.setCapabilities(capability);
+        PhoneAccount phoneAccount = builder.build();
+        when(mPhoneAccountRegistrar.getPhoneAccountUnchecked(phoneAccountHandle))
+                .thenReturn(phoneAccount);
+        return phoneAccount;
+    }
+}
diff --git a/tests/src/com/android/server/telecom/tests/MockVideoProvider.java b/tests/src/com/android/server/telecom/tests/MockVideoProvider.java
new file mode 100644
index 0000000..147d232
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/MockVideoProvider.java
@@ -0,0 +1,259 @@
+/*
+ * 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 org.mockito.Mockito;
+
+import android.net.Uri;
+import android.telecom.Connection;
+import android.telecom.Connection.VideoProvider;
+import android.telecom.InCallService;
+import android.telecom.InCallService.VideoCall;
+import android.telecom.Log;
+import android.telecom.VideoProfile;
+import android.view.Surface;
+
+/**
+ * Provides a mock implementation of a video provider.
+ */
+public class MockVideoProvider extends VideoProvider {
+    public static final String CAMERA_NONE = "none";
+    public static final String CAMERA_FRONT = "front";
+    public static final String CAMERA_BACK = "back";
+    public static final int CAMERA_FRONT_DIMENSIONS = 1024;
+    public static final int CAMERA_BACK_DIMENSIONS = 2048;
+    public static final long DATA_USAGE = 1024;
+    public static final int PEER_DIMENSIONS = 4096;
+    public static final int DEVICE_ORIENTATION_UNDEFINED = -1;
+    public static final float ZOOM_UNDEFINED = -1.0f;
+
+    private Surface mPreviewSurface = null;
+    private Surface mDisplaySurface = null;
+    private int mDeviceOrientation = DEVICE_ORIENTATION_UNDEFINED;
+    private float mZoom = ZOOM_UNDEFINED;
+    private VideoProfile mSessionModifyResponse = null;
+    private Uri mPauseImage = null;
+
+    /**
+     * Responds to a request to set the camera by reporting the new camera information via the
+     * {@link #changeCameraCapabilities(VideoProfile.CameraCapabilities)} API.
+     *
+     * @param cameraId The id of the camera (use ids as reported by
+     */
+    @Override
+    public void onSetCamera(String cameraId) {
+        handleCameraChange(cameraId);
+    }
+
+    /**
+     * Stores the preview surface set via the {@link VideoCall#setPreviewSurface(Surface)} API for
+     * retrieval using {@link #getPreviewSurface()}.
+     *
+     * @param surface The {@link Surface}.
+     */
+    @Override
+    public void onSetPreviewSurface(Surface surface) {
+        mPreviewSurface = surface;
+    }
+
+    /**
+     * Stores the display surface set via the {@link VideoCall#setDisplaySurface(Surface)} API for
+     * retrieval using {@link #getDisplaySurface()}.
+     *
+     * @param surface The {@link Surface}.
+     */
+    @Override
+    public void onSetDisplaySurface(Surface surface) {
+        mDisplaySurface = surface;
+    }
+
+    /**
+     * Stores the device orientation set via the {@link VideoCall#setDeviceOrientation(int)} API for
+     * retrieval using {@link #getDeviceOrientation()}.
+     *
+     * @param rotation The device orientation, in degrees.
+     */
+    @Override
+    public void onSetDeviceOrientation(int rotation) {
+        mDeviceOrientation = rotation;
+    }
+
+    /**
+     * Stores the zoom level set via the {@link VideoCall#setZoom(float)} API for retrieval using
+     * {@link #getZoom()}.
+     *
+     * @param value The camera zoom.
+     */
+    @Override
+    public void onSetZoom(float value) {
+        mZoom = value;
+    }
+
+    /**
+     * Responds to any incoming session modify request by accepting the requested profile.
+     *
+     * @param fromProfile The video profile prior to the request.
+     * @param toProfile The video profile with the requested changes made.
+     */
+    @Override
+    public void onSendSessionModifyRequest(VideoProfile fromProfile, VideoProfile toProfile) {
+        super.receiveSessionModifyResponse(VideoProvider.SESSION_MODIFY_REQUEST_SUCCESS,
+                fromProfile, toProfile);
+    }
+
+    /**
+     * Stores session modify responses received via the
+     * {@link VideoCall#sendSessionModifyResponse(VideoProfile)} API for retrieval via
+     * {@link #getSessionModifyResponse()}.
+     *
+     * @param responseProfile The response video profile.
+     */
+    @Override
+    public void onSendSessionModifyResponse(VideoProfile responseProfile) {
+        mSessionModifyResponse = responseProfile;
+    }
+
+    /**
+     * Responds to requests for camera capabilities by reporting the front camera capabilities.
+     */
+    @Override
+    public void onRequestCameraCapabilities() {
+        handleCameraChange(CAMERA_FRONT);
+    }
+
+    /**
+     * Responds to all requests for data usage by reporting {@link #DATA_USAGE}.
+     */
+    @Override
+    public void onRequestConnectionDataUsage() {
+        super.setCallDataUsage(DATA_USAGE);
+    }
+
+    /**
+     * Stores pause image URIs received via the {@link VideoCall#setPauseImage(Uri)} API for
+     * retrieval via {@link #getPauseImage()}.
+     *
+     * @param uri URI of image to display.
+     */
+    @Override
+    public void onSetPauseImage(Uri uri) {
+        mPauseImage = uri;
+    }
+
+    /**
+     * Handles a change to the current camera selection.  Responds by reporting the capabilities of
+     * the camera.
+     */
+    private void handleCameraChange(String cameraId) {
+        if (CAMERA_FRONT.equals(cameraId)) {
+            super.changeCameraCapabilities(new VideoProfile.CameraCapabilities(
+                    CAMERA_FRONT_DIMENSIONS, CAMERA_FRONT_DIMENSIONS));
+        } else if (CAMERA_BACK.equals(cameraId)) {
+            super.changeCameraCapabilities(new VideoProfile.CameraCapabilities(
+                    CAMERA_BACK_DIMENSIONS, CAMERA_BACK_DIMENSIONS));
+        }
+    }
+
+    /**
+     * Retrieves the last preview surface sent to the provider.
+     *
+     * @return the surface.
+     */
+    public Surface getPreviewSurface() {
+        return mPreviewSurface;
+    }
+
+    /**
+     * Retrieves the last display surface sent to the provider.
+     *
+     * @return the surface.
+     */
+    public Surface getDisplaySurface() {
+        return mDisplaySurface;
+    }
+
+    /**
+     * Retrieves the last device orientation sent to the provider.
+     *
+     * @return the orientation.
+     */
+    public int getDeviceOrientation() {
+        return mDeviceOrientation;
+    }
+
+    /**
+     * Retrieves the last zoom sent to the provider.
+     *
+     * @return the zoom.
+     */
+    public float getZoom() {
+        return mZoom;
+    }
+
+    /**
+     * Retrieves the last session modify response sent to the provider.
+     *
+     * @return the session modify response.
+     */
+    public VideoProfile getSessionModifyResponse() {
+        return mSessionModifyResponse;
+    }
+
+    /**
+     * Retrieves the last pause image sent to the provider.
+     *
+     * @return the pause image URI.
+     */
+    public Uri getPauseImage() {
+        return mPauseImage;
+    }
+
+    /**
+     * Sends a mock session modify request via the provider.
+     */
+    public void sendMockSessionModifyRequest() {
+        super.receiveSessionModifyRequest(new VideoProfile(VideoProfile.STATE_BIDIRECTIONAL));
+    }
+
+    /**
+     * Sends a mock session event via the provider.
+     *
+     * @param event the event.
+     */
+    public void sendMockSessionEvent(int event) {
+        super.handleCallSessionEvent(event);
+    }
+
+    /**
+     * Sends a mock peer dimension change via the provider.
+     *
+     * @param width The new width.
+     * @param height The new height.
+     */
+    public void sendMockPeerDimensions(int width, int height) {
+        super.changePeerDimensions(width, height);
+    }
+
+    /**
+     * Sends a mock video quality change via the provider.
+     *
+     * @param videoQuality the video quality.
+     */
+    public void sendMockVideoQuality(int videoQuality) {
+        super.changeVideoQuality(videoQuality);
+    }
+}
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/NewOutgoingCallIntentBroadcasterTest.java b/tests/src/com/android/server/telecom/tests/NewOutgoingCallIntentBroadcasterTest.java
new file mode 100644
index 0000000..17ca5b9
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/NewOutgoingCallIntentBroadcasterTest.java
@@ -0,0 +1,418 @@
+/*
+ * 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.Manifest;
+import android.app.Activity;
+import android.app.AppOpsManager;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.telecom.GatewayInfo;
+import android.telecom.TelecomManager;
+import android.telecom.VideoProfile;
+import android.telephony.DisconnectCause;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.NewOutgoingCallIntentBroadcaster;
+import com.android.server.telecom.PhoneNumberUtilsAdapter;
+import com.android.server.telecom.PhoneNumberUtilsAdapterImpl;
+
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+
+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.Matchers.isNotNull;
+import static org.mockito.Matchers.isNull;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+public class NewOutgoingCallIntentBroadcasterTest extends TelecomTestCase {
+    private static class ReceiverIntentPair {
+        public BroadcastReceiver receiver;
+        public Intent intent;
+
+        public ReceiverIntentPair(BroadcastReceiver receiver, Intent intent) {
+            this.receiver = receiver;
+            this.intent = intent;
+        }
+    }
+
+    @Mock private CallsManager mCallsManager;
+    @Mock private Call mCall;
+
+    private PhoneNumberUtilsAdapter mPhoneNumberUtilsAdapterSpy;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
+        mPhoneNumberUtilsAdapterSpy = spy(new PhoneNumberUtilsAdapterImpl());
+    }
+
+    @SmallTest
+    public void testNullHandle() {
+        Intent intent = new Intent(Intent.ACTION_CALL, null);
+        int result = processIntent(intent, true);
+        assertEquals(DisconnectCause.INVALID_NUMBER, result);
+        verifyNoBroadcastSent();
+        verifyNoCallPlaced();
+    }
+
+    @SmallTest
+    public void testVoicemailCall() {
+        String voicemailNumber = "voicemail:18005551234";
+        Intent intent = new Intent(Intent.ACTION_CALL, Uri.parse(voicemailNumber));
+        intent.putExtra(TelecomManager.EXTRA_START_CALL_WITH_SPEAKERPHONE, true);
+
+        int result = processIntent(intent, true);
+
+        assertEquals(DisconnectCause.NOT_DISCONNECTED, result);
+        verify(mCallsManager).placeOutgoingCall(eq(mCall), eq(Uri.parse(voicemailNumber)),
+                any(GatewayInfo.class), eq(true), eq(VideoProfile.STATE_AUDIO_ONLY));
+    }
+
+    @SmallTest
+    public void testVoicemailCallWithBadAction() {
+        badCallActionHelper(Uri.parse("voicemail:18005551234"), DisconnectCause.OUTGOING_CANCELED);
+    }
+
+    @SmallTest
+    public void testTelCallWithBadCallAction() {
+        badCallActionHelper(Uri.parse("tel:6505551234"), DisconnectCause.INVALID_NUMBER);
+    }
+
+    @SmallTest
+    public void testSipCallWithBadCallAction() {
+        badCallActionHelper(Uri.parse("sip:testuser@testsite.com"), DisconnectCause.INVALID_NUMBER);
+    }
+
+    private void badCallActionHelper(Uri handle, int expectedCode) {
+        Intent intent = new Intent(Intent.ACTION_ALARM_CHANGED, handle);
+
+        int result = processIntent(intent, true);
+
+        assertEquals(expectedCode, result);
+        verifyNoBroadcastSent();
+        verifyNoCallPlaced();
+    }
+
+    @SmallTest
+    public void testNoNumberSupplied() {
+        Uri handle = Uri.parse("tel:");
+        Intent intent = new Intent(Intent.ACTION_CALL, handle);
+
+        int result = processIntent(intent, true);
+
+        assertEquals(DisconnectCause.NO_PHONE_NUMBER_SUPPLIED, result);
+        verifyNoBroadcastSent();
+        verifyNoCallPlaced();
+    }
+
+    @SmallTest
+    public void testEmergencyCallWithNonDefaultDialer() {
+        Uri handle = Uri.parse("tel:6505551911");
+        doReturn(true).when(mPhoneNumberUtilsAdapterSpy).isPotentialLocalEmergencyNumber(
+                any(Context.class), eq(handle.getSchemeSpecificPart()));
+        Intent intent = new Intent(Intent.ACTION_CALL, handle);
+
+        String ui_package_string = "sample_string_1";
+        String dialer_default_class_string = "sample_string_2";
+        mComponentContextFixture.putResource(R.string.ui_default_package, ui_package_string);
+        mComponentContextFixture.putResource(R.string.dialer_default_class,
+                dialer_default_class_string);
+
+        int result = processIntent(intent, false);
+
+        assertEquals(DisconnectCause.OUTGOING_CANCELED, result);
+        verifyNoBroadcastSent();
+        verifyNoCallPlaced();
+
+        ArgumentCaptor<Intent> dialerIntentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext).startActivityAsUser(dialerIntentCaptor.capture(), any(UserHandle.class));
+        Intent dialerIntent = dialerIntentCaptor.getValue();
+        assertEquals(new ComponentName(ui_package_string, dialer_default_class_string),
+                dialerIntent.getComponent());
+        assertEquals(Intent.ACTION_DIAL, dialerIntent.getAction());
+        assertEquals(handle, dialerIntent.getData());
+        assertEquals(Intent.FLAG_ACTIVITY_NEW_TASK, dialerIntent.getFlags());
+    }
+
+    @SmallTest
+    public void testActionCallEmergencyCall() {
+        Uri handle = Uri.parse("tel:6505551911");
+        Intent intent = buildIntent(handle, Intent.ACTION_CALL, null);
+        emergencyCallTestHelper(intent, null);
+    }
+
+    @SmallTest
+    public void testActionEmergencyWithEmergencyNumber() {
+        Uri handle = Uri.parse("tel:6505551911");
+        Intent intent = buildIntent(handle, Intent.ACTION_CALL_EMERGENCY, null);
+        emergencyCallTestHelper(intent, null);
+    }
+
+    @SmallTest
+    public void testActionPrivCallWithEmergencyNumber() {
+        Uri handle = Uri.parse("tel:6505551911");
+        Intent intent = buildIntent(handle, Intent.ACTION_CALL_PRIVILEGED, null);
+        emergencyCallTestHelper(intent, null);
+    }
+
+    @SmallTest
+    public void testEmergencyCallWithGatewayExtras() {
+        Uri handle = Uri.parse("tel:6505551911");
+        Bundle gatewayExtras = new Bundle();
+        gatewayExtras.putString(NewOutgoingCallIntentBroadcaster.EXTRA_GATEWAY_PROVIDER_PACKAGE,
+                "sample1");
+        gatewayExtras.putString(NewOutgoingCallIntentBroadcaster.EXTRA_GATEWAY_URI, "sample2");
+
+        Intent intent = buildIntent(handle, Intent.ACTION_CALL, gatewayExtras);
+        emergencyCallTestHelper(intent, gatewayExtras);
+    }
+
+    @SmallTest
+    public void testActionEmergencyWithNonEmergencyNumber() {
+        Uri handle = Uri.parse("tel:6505551911");
+        doReturn(false).when(mPhoneNumberUtilsAdapterSpy).isPotentialLocalEmergencyNumber(
+                any(Context.class), eq(handle.getSchemeSpecificPart()));
+        Intent intent = new Intent(Intent.ACTION_CALL_EMERGENCY, handle);
+        int result = processIntent(intent, true);
+
+        assertEquals(DisconnectCause.OUTGOING_CANCELED, result);
+        verifyNoCallPlaced();
+        verifyNoBroadcastSent();
+    }
+
+    private void emergencyCallTestHelper(Intent intent, Bundle expectedAdditionalExtras) {
+        Uri handle = intent.getData();
+        int videoState = VideoProfile.STATE_BIDIRECTIONAL;
+        boolean isSpeakerphoneOn = true;
+        doReturn(true).when(mPhoneNumberUtilsAdapterSpy).isPotentialLocalEmergencyNumber(
+                any(Context.class), eq(handle.getSchemeSpecificPart()));
+        intent.putExtra(TelecomManager.EXTRA_START_CALL_WITH_SPEAKERPHONE, isSpeakerphoneOn);
+        intent.putExtra(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, videoState);
+        int result = processIntent(intent, true);
+
+        assertEquals(DisconnectCause.NOT_DISCONNECTED, result);
+        verify(mCallsManager).placeOutgoingCall(eq(mCall), eq(handle), isNull(GatewayInfo.class),
+                eq(isSpeakerphoneOn), eq(videoState));
+
+        Bundle expectedExtras = createNumberExtras(handle.getSchemeSpecificPart());
+        if (expectedAdditionalExtras != null) {
+            expectedExtras.putAll(expectedAdditionalExtras);
+        }
+        BroadcastReceiver receiver = verifyBroadcastSent(handle.getSchemeSpecificPart(),
+                expectedExtras).receiver;
+        assertNull(receiver);
+    }
+
+    @SmallTest
+    public void testUnmodifiedRegularCall() {
+        Uri handle = Uri.parse("tel:6505551234");
+        Intent callIntent = buildIntent(handle, Intent.ACTION_CALL, null);
+        ReceiverIntentPair result = regularCallTestHelper(callIntent, null);
+
+        result.receiver.setResultData(
+                result.intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER));
+
+        result.receiver.onReceive(mContext, result.intent);
+
+        verify(mCallsManager).placeOutgoingCall(eq(mCall), eq(handle), isNull(GatewayInfo.class),
+                eq(true), eq(VideoProfile.STATE_BIDIRECTIONAL));
+    }
+
+    @SmallTest
+    public void testUnmodifiedSipCall() {
+        Uri handle = Uri.parse("sip:test@test.com");
+        Intent callIntent = buildIntent(handle, Intent.ACTION_CALL, null);
+        ReceiverIntentPair result = regularCallTestHelper(callIntent, null);
+
+        result.receiver.setResultData(
+                result.intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER));
+
+        result.receiver.onReceive(mContext, result.intent);
+
+        Uri encHandle = Uri.fromParts(handle.getScheme(),
+                handle.getSchemeSpecificPart(), null);
+        verify(mCallsManager).placeOutgoingCall(eq(mCall), eq(encHandle), isNull(GatewayInfo.class),
+                eq(true), eq(VideoProfile.STATE_BIDIRECTIONAL));
+    }
+
+    @SmallTest
+    public void testCallWithGatewayInfo() {
+        Uri handle = Uri.parse("tel:6505551234");
+        Intent callIntent = buildIntent(handle, Intent.ACTION_CALL, null);
+
+        callIntent.putExtra(NewOutgoingCallIntentBroadcaster
+                        .EXTRA_GATEWAY_PROVIDER_PACKAGE, "sample1");
+        callIntent.putExtra(NewOutgoingCallIntentBroadcaster.EXTRA_GATEWAY_URI, "sample2");
+        ReceiverIntentPair result = regularCallTestHelper(callIntent, callIntent.getExtras());
+
+        result.receiver.setResultData(
+                result.intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER));
+
+        result.receiver.onReceive(mContext, result.intent);
+
+        verify(mCallsManager).placeOutgoingCall(eq(mCall), eq(handle),
+                isNotNull(GatewayInfo.class), eq(true), eq(VideoProfile.STATE_BIDIRECTIONAL));
+    }
+
+    @SmallTest
+    public void testCallNumberModifiedToNull() {
+        Uri handle = Uri.parse("tel:6505551234");
+        Intent callIntent = buildIntent(handle, Intent.ACTION_CALL, null);
+        ReceiverIntentPair result = regularCallTestHelper(callIntent, null);
+
+        result.receiver.setResultData(null);
+
+        result.receiver.onReceive(mContext, result.intent);
+        verifyNoCallPlaced();
+        verify(mCall).disconnect(true);
+    }
+
+    @SmallTest
+    public void testCallModifiedToEmergency() {
+        Uri handle = Uri.parse("tel:6505551234");
+        Intent callIntent = buildIntent(handle, Intent.ACTION_CALL, null);
+        ReceiverIntentPair result = regularCallTestHelper(callIntent, null);
+
+        String newEmergencyNumber = "1234567890";
+        result.receiver.setResultData(newEmergencyNumber);
+
+        doReturn(true).when(mPhoneNumberUtilsAdapterSpy).isPotentialLocalEmergencyNumber(
+                any(Context.class), eq(newEmergencyNumber));
+        result.receiver.onReceive(mContext, result.intent);
+        verify(mCall).disconnect(true);
+    }
+
+    private ReceiverIntentPair regularCallTestHelper(Intent intent,
+            Bundle expectedAdditionalExtras) {
+        Uri handle = intent.getData();
+        int videoState = VideoProfile.STATE_BIDIRECTIONAL;
+        boolean isSpeakerphoneOn = true;
+        intent.putExtra(TelecomManager.EXTRA_START_CALL_WITH_SPEAKERPHONE, isSpeakerphoneOn);
+        intent.putExtra(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, videoState);
+
+        int result = processIntent(intent, true);
+
+        assertEquals(DisconnectCause.NOT_DISCONNECTED, result);
+        Bundle expectedExtras = createNumberExtras(handle.getSchemeSpecificPart());
+        if (expectedAdditionalExtras != null) {
+            expectedExtras.putAll(expectedAdditionalExtras);
+        }
+        return verifyBroadcastSent(handle.getSchemeSpecificPart(), expectedExtras);
+    }
+
+    private Intent buildIntent(Uri handle, String action, Bundle extras) {
+        Intent i = new Intent(action, handle);
+        if (extras != null) {
+            i.putExtras(extras);
+        }
+        return i;
+    }
+
+    private int processIntent(Intent intent,
+            boolean isDefaultPhoneApp) {
+        NewOutgoingCallIntentBroadcaster b = new NewOutgoingCallIntentBroadcaster(
+                mContext, mCallsManager, mCall, intent, mPhoneNumberUtilsAdapterSpy,
+                isDefaultPhoneApp);
+        return b.processIntent();
+    }
+
+    private ReceiverIntentPair verifyBroadcastSent(String number, Bundle expectedExtras) {
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        ArgumentCaptor<BroadcastReceiver> receiverCaptor =
+                ArgumentCaptor.forClass(BroadcastReceiver.class);
+
+        verify(mContext).sendOrderedBroadcastAsUser(
+                intentCaptor.capture(),
+                eq(UserHandle.CURRENT),
+                eq(Manifest.permission.PROCESS_OUTGOING_CALLS),
+                eq(AppOpsManager.OP_PROCESS_OUTGOING_CALLS),
+                receiverCaptor.capture(),
+                isNull(Handler.class),
+                eq(Activity.RESULT_OK),
+                eq(number),
+                isNull(Bundle.class));
+
+        Intent capturedIntent = intentCaptor.getValue();
+        assertEquals(Intent.ACTION_NEW_OUTGOING_CALL, capturedIntent.getAction());
+        assertEquals(Intent.FLAG_RECEIVER_FOREGROUND, capturedIntent.getFlags());
+        assertTrue(areBundlesEqual(expectedExtras, capturedIntent.getExtras()));
+
+        BroadcastReceiver receiver = receiverCaptor.getValue();
+        if (receiver != null) {
+            receiver.setPendingResult(
+                    new BroadcastReceiver.PendingResult(0, "", null, 0, true, false, null, 0, 0));
+        }
+
+        return new ReceiverIntentPair(receiver, capturedIntent);
+    }
+
+    private Bundle createNumberExtras(String number) {
+        Bundle b = new Bundle();
+        b.putString(Intent.EXTRA_PHONE_NUMBER, number);
+        return b;
+    }
+
+    private void verifyNoCallPlaced() {
+        verify(mCallsManager, never()).placeOutgoingCall(any(Call.class), any(Uri.class),
+                any(GatewayInfo.class), anyBoolean(), anyInt());
+    }
+
+    private void verifyNoBroadcastSent() {
+        verify(mContext, never()).sendOrderedBroadcastAsUser(
+                any(Intent.class),
+                any(UserHandle.class),
+                anyString(),
+                anyInt(),
+                any(BroadcastReceiver.class),
+                any(Handler.class),
+                anyInt(),
+                anyString(),
+                any(Bundle.class));
+    }
+
+    private static boolean areBundlesEqual(Bundle b1, Bundle b2) {
+        for (String key1 : b1.keySet()) {
+            if (!b1.get(key1).equals(b2.get(key1))) {
+                return false;
+            }
+        }
+
+        for (String key2 : b2.keySet()) {
+            if (!b2.get(key2).equals(b1.get(key2))) {
+                return false;
+            }
+        }
+        return true;
+    }
+}
diff --git a/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java b/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
index f815fed..67e3f70 100644
--- a/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
+++ b/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
@@ -16,44 +16,54 @@
 
 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();
-        mComponentContextFixture = new ComponentContextFixture();
+        MockitoAnnotations.initMocks(this);
+        mComponentContextFixture.setTelecomManager(mTelecomManager);
         new File(
                 mComponentContextFixture.getTestDouble().getApplicationContext().getFilesDir(),
                 FILE_NAME)
@@ -70,7 +80,6 @@
                 mComponentContextFixture.getTestDouble().getApplicationContext().getFilesDir(),
                 FILE_NAME)
                 .delete();
-        mComponentContextFixture = null;
         super.tearDown();
     }
 
@@ -88,9 +97,19 @@
     }
 
     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)
+                .setIsEnabled(true)
                 .build();
         PhoneAccount result = roundTripXml(this, input, PhoneAccountRegistrar.sPhoneAccountXml,
                 mContext);
@@ -98,6 +117,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 +185,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 +202,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 +212,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 +221,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 +234,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 +285,7 @@
         return new PhoneAccountHandle(
                 makeQuickConnectionServiceComponentName(),
                 id,
-                Binder.getCallingUserHandle());
+                Process.myUserHandle());
     }
 
     private PhoneAccount.Builder makeQuickAccountBuilder(String id, int idx) {
@@ -318,6 +375,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 +399,40 @@
                 assertEquals(a.getLabel(), b.getLabel());
                 assertEquals(a.getShortDescription(), b.getShortDescription());
                 assertEquals(a.getSupportedUriSchemes(), b.getSupportedUriSchemes());
+                assertBundlesEqual(a.getExtras(), b.getExtras());
+                assertEquals(a.isEnabled(), b.isEnabled());
             } 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/ProximitySensorManagerTest.java b/tests/src/com/android/server/telecom/tests/ProximitySensorManagerTest.java
new file mode 100644
index 0000000..f08da0c
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/ProximitySensorManagerTest.java
@@ -0,0 +1,106 @@
+/*
+ * 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.os.PowerManager;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.ProximitySensorManager;
+import com.android.server.telecom.TelecomWakeLock;
+
+import org.mockito.Mock;
+
+import java.util.ArrayList;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class ProximitySensorManagerTest extends TelecomTestCase{
+
+    @Mock CallsManager mCallsManager;
+    @Mock Call mCall;
+    @Mock TelecomWakeLock.WakeLockAdapter mWakeLockAdapter;
+    private ProximitySensorManager mProximitySensorManager;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        TelecomWakeLock telecomWakeLock = new TelecomWakeLock(
+                null, // Context is never used due to mock WakeLockAdapter
+                mWakeLockAdapter, PowerManager.FULL_WAKE_LOCK,
+                InCallWakeLockControllerTest.class.getSimpleName());
+        mProximitySensorManager = new ProximitySensorManager(telecomWakeLock, mCallsManager);
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        mProximitySensorManager = null;
+        super.tearDown();
+    }
+
+    public void testTurnOnProximityWithCallsActive() throws Exception {
+        when(mCallsManager.getCalls()).thenReturn(new ArrayList<Call>(){{
+            add(mCall);
+        }});
+        when(mWakeLockAdapter.isHeld()).thenReturn(false);
+
+        mProximitySensorManager.turnOn();
+
+        verify(mWakeLockAdapter).acquire();
+    }
+
+    public void testTurnOnProximityWithNoCallsActive() throws Exception {
+        when(mCallsManager.getCalls()).thenReturn(new ArrayList<Call>());
+        when(mWakeLockAdapter.isHeld()).thenReturn(false);
+
+        mProximitySensorManager.turnOn();
+
+        verify(mWakeLockAdapter, never()).acquire();
+
+    }
+
+    public void testTurnOffProximityExplicitly() throws Exception {
+        when(mWakeLockAdapter.isHeld()).thenReturn(true);
+
+        mProximitySensorManager.turnOff(true);
+
+        verify(mWakeLockAdapter).release(0);
+    }
+
+    public void testCallRemovedFromCallsManagerCallsActive() throws Exception {
+        when(mCallsManager.getCalls()).thenReturn(new ArrayList<Call>(){{
+            add(mCall);
+        }});
+        when(mWakeLockAdapter.isHeld()).thenReturn(true);
+
+        mProximitySensorManager.onCallRemoved(mock(Call.class));
+
+        verify(mWakeLockAdapter, never()).release(0);
+    }
+
+    public void testCallRemovedFromCallsManagerNoCallsActive() throws Exception {
+        when(mCallsManager.getCalls()).thenReturn(new ArrayList<Call>());
+        when(mWakeLockAdapter.isHeld()).thenReturn(true);
+
+        mProximitySensorManager.onCallRemoved(mock(Call.class));
+
+        verify(mWakeLockAdapter).release(0);
+    }
+}
diff --git a/tests/src/com/android/server/telecom/tests/StateMachineTestBase.java b/tests/src/com/android/server/telecom/tests/StateMachineTestBase.java
new file mode 100644
index 0000000..358b960
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/StateMachineTestBase.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2016 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 com.android.internal.util.StateMachine;
+
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+
+public abstract class StateMachineTestBase<T extends StateMachine> extends TelecomTestCase {
+    abstract static class TestParameters {}
+
+    protected final void waitForStateMachineActionCompletion(T stateMachine, int runnableCode) {
+        final CountDownLatch lock = new CountDownLatch(1);
+        Runnable actionComplete = new Runnable() {
+            @Override
+            public void run() {
+                lock.countDown();
+            }
+        };
+        stateMachine.sendMessage(runnableCode, actionComplete);
+        while (lock.getCount() > 0) {
+            try {
+                lock.await();
+            } catch (InterruptedException e) {
+                // do nothing
+            }
+        }
+    }
+
+    protected final void parametrizedTestStateMachine(
+            List<? extends TestParameters> paramList) throws Throwable {
+        for (TestParameters params : paramList) {
+            try {
+                runParametrizedTestCase(params);
+            } catch (Throwable e) {
+                String newMessage = "Failed at parameters: \n" + params.toString() + '\n'
+                        + e.getMessage();
+                Throwable t = new Throwable(newMessage, e);
+                t.setStackTrace(e.getStackTrace());
+                throw t;
+            }
+        }
+    }
+
+    protected abstract void runParametrizedTestCase(TestParameters params) throws Throwable;
+}
diff --git a/tests/src/com/android/server/telecom/tests/SystemStateProviderTest.java b/tests/src/com/android/server/telecom/tests/SystemStateProviderTest.java
new file mode 100644
index 0000000..02e7ecf
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/SystemStateProviderTest.java
@@ -0,0 +1,109 @@
+/*
+ * 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 static org.mockito.Matchers.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.UiModeManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Configuration;
+
+import com.android.server.telecom.SystemStateProvider;
+import com.android.server.telecom.SystemStateProvider.SystemStateListener;
+
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit tests for SystemStateProvider
+ */
+public class SystemStateProviderTest extends TelecomTestCase {
+
+    SystemStateProvider mSystemStateProvider;
+
+    @Mock Context mContext;
+    @Mock SystemStateListener mSystemStateListener;
+    @Mock UiModeManager mUiModeManager;
+    @Mock Intent mIntentEnter;
+    @Mock Intent mIntentExit;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    public void testListeners() throws Exception {
+        SystemStateProvider systemStateProvider = new SystemStateProvider(mContext);
+
+        assertFalse(systemStateProvider.removeListener(mSystemStateListener));
+        systemStateProvider.addListener(mSystemStateListener);
+        assertTrue(systemStateProvider.removeListener(mSystemStateListener));
+        assertFalse(systemStateProvider.removeListener(mSystemStateListener));
+    }
+
+    public void testQuerySystemForCarMode_True() {
+        when(mContext.getSystemService(Context.UI_MODE_SERVICE)).thenReturn(mUiModeManager);
+        when(mUiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_CAR);
+        assertTrue(new SystemStateProvider(mContext).isCarMode());
+    }
+
+    public void testQuerySystemForCarMode_False() {
+        when(mContext.getSystemService(Context.UI_MODE_SERVICE)).thenReturn(mUiModeManager);
+        when(mUiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_NORMAL);
+        assertFalse(new SystemStateProvider(mContext).isCarMode());
+    }
+
+    public void testReceiverAndIntentFilter() {
+        ArgumentCaptor<IntentFilter> intentFilter = ArgumentCaptor.forClass(IntentFilter.class);
+        new SystemStateProvider(mContext);
+        verify(mContext).registerReceiver(any(BroadcastReceiver.class), intentFilter.capture());
+
+        assertEquals(2, intentFilter.getValue().countActions());
+        assertEquals(UiModeManager.ACTION_ENTER_CAR_MODE, intentFilter.getValue().getAction(0));
+        assertEquals(UiModeManager.ACTION_EXIT_CAR_MODE, intentFilter.getValue().getAction(1));
+    }
+
+    public void testOnEnterExitCarMode() {
+        ArgumentCaptor<BroadcastReceiver> receiver =
+                ArgumentCaptor.forClass(BroadcastReceiver.class);
+        new SystemStateProvider(mContext).addListener(mSystemStateListener);
+
+        verify(mContext).registerReceiver(receiver.capture(), any(IntentFilter.class));
+
+        when(mIntentEnter.getAction()).thenReturn(UiModeManager.ACTION_ENTER_CAR_MODE);
+        receiver.getValue().onReceive(mContext, mIntentEnter);
+        verify(mSystemStateListener).onCarModeChanged(true);
+
+        when(mIntentExit.getAction()).thenReturn(UiModeManager.ACTION_EXIT_CAR_MODE);
+        receiver.getValue().onReceive(mContext, mIntentExit);
+        verify(mSystemStateListener).onCarModeChanged(false);
+
+        receiver.getValue().onReceive(mContext, new Intent("invalid action"));
+    }
+}
diff --git a/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java b/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
new file mode 100644
index 0000000..d0b723a
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
@@ -0,0 +1,935 @@
+/*
+ * 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 static android.Manifest.permission.CALL_PHONE;
+import static android.Manifest.permission.MODIFY_PHONE_STATE;
+import static android.Manifest.permission.READ_PHONE_STATE;
+import static android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE;
+
+import android.app.AppOpsManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
+import android.telecom.TelecomManager;
+import android.telecom.VideoProfile;
+import android.telephony.TelephonyManager;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.internal.telecom.ITelecomService;
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallIntentProcessor;
+import com.android.server.telecom.CallState;
+import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.PhoneAccountRegistrar;
+import com.android.server.telecom.TelecomServiceImpl;
+import com.android.server.telecom.TelecomSystem;
+import com.android.server.telecom.components.UserCallIntentProcessor;
+import com.android.server.telecom.components.UserCallIntentProcessorFactory;
+
+import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatcher;
+import org.mockito.Mock;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import static android.Manifest.permission.REGISTER_SIM_SUBSCRIPTION;
+import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
+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.argThat;
+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.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class TelecomServiceImplTest extends TelecomTestCase {
+    public static class CallIntentProcessAdapterFake implements CallIntentProcessor.Adapter {
+        @Override
+        public void processOutgoingCallIntent(Context context, CallsManager callsManager,
+                Intent intent) {
+
+        }
+
+        @Override
+        public void processIncomingCallIntent(CallsManager callsManager, Intent intent) {
+
+        }
+
+        @Override
+        public void processUnknownCallIntent(CallsManager callsManager, Intent intent) {
+
+        }
+    }
+
+    public static class DefaultDialerManagerAdapterFake
+            implements TelecomServiceImpl.DefaultDialerManagerAdapter {
+        @Override
+        public String getDefaultDialerApplication(Context context) {
+            return null;
+        }
+
+        @Override
+        public boolean setDefaultDialerApplication(Context context, String packageName) {
+            return false;
+        }
+
+        @Override
+        public boolean isDefaultOrSystemDialer(Context context, String packageName) {
+            return false;
+        }
+    }
+
+    public static class SubscriptionManagerAdapterFake
+            implements TelecomServiceImpl.SubscriptionManagerAdapter {
+        @Override
+        public int getDefaultVoiceSubId() {
+            return 0;
+        }
+    }
+
+    private static class AnyStringIn extends ArgumentMatcher<String> {
+        private Collection<String> mStrings;
+        public AnyStringIn(Collection<String> strings) {
+            this.mStrings = strings;
+        }
+
+        @Override
+        public boolean matches(Object string) {
+            return mStrings.contains(string);
+        }
+    }
+
+    private ITelecomService.Stub mTSIBinder;
+    private AppOpsManager mAppOpsManager;
+    private UserManager mUserManager;
+
+    @Mock private CallsManager mFakeCallsManager;
+    @Mock private PhoneAccountRegistrar mFakePhoneAccountRegistrar;
+    @Mock private TelecomManager mTelecomManager;
+    private CallIntentProcessor.Adapter mCallIntentProcessorAdapter =
+            spy(new CallIntentProcessAdapterFake());
+    private TelecomServiceImpl.DefaultDialerManagerAdapter mDefaultDialerManagerAdapter =
+            spy(new DefaultDialerManagerAdapterFake());
+    private TelecomServiceImpl.SubscriptionManagerAdapter mSubscriptionManagerAdapter =
+            spy(new SubscriptionManagerAdapterFake());
+    @Mock private UserCallIntentProcessor mUserCallIntentProcessor;
+
+    private final TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() { };
+
+    private static final String DEFAULT_DIALER_PACKAGE = "com.google.android.dialer";
+    private static final UserHandle USER_HANDLE_16 = new UserHandle(16);
+    private static final UserHandle USER_HANDLE_17 = new UserHandle(17);
+    private static final PhoneAccountHandle TEL_PA_HANDLE_16 = new PhoneAccountHandle(
+            new ComponentName("test", "telComponentName"), "0", USER_HANDLE_16);
+    private static final PhoneAccountHandle SIP_PA_HANDLE_17 = new PhoneAccountHandle(
+            new ComponentName("test", "sipComponentName"), "1", USER_HANDLE_17);
+    private static final PhoneAccountHandle TEL_PA_HANDLE_CURRENT = new PhoneAccountHandle(
+            new ComponentName("test", "telComponentName"), "2", Binder.getCallingUserHandle());
+    private static final PhoneAccountHandle SIP_PA_HANDLE_CURRENT = new PhoneAccountHandle(
+            new ComponentName("test", "sipComponentName"), "3", Binder.getCallingUserHandle());
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
+        mComponentContextFixture.putBooleanResource(
+                com.android.internal.R.bool.config_voice_capable, true);
+
+        doReturn(mContext).when(mContext).getApplicationContext();
+        doNothing().when(mContext).sendBroadcastAsUser(any(Intent.class), any(UserHandle.class),
+                anyString());
+        TelecomServiceImpl telecomServiceImpl = new TelecomServiceImpl(
+                mContext,
+                mFakeCallsManager,
+                mFakePhoneAccountRegistrar,
+                mCallIntentProcessorAdapter,
+                new UserCallIntentProcessorFactory() {
+                    @Override
+                    public UserCallIntentProcessor create(Context context, UserHandle userHandle) {
+                        return mUserCallIntentProcessor;
+                    }
+                },
+                mDefaultDialerManagerAdapter,
+                mSubscriptionManagerAdapter,
+                mLock);
+        mTSIBinder = telecomServiceImpl.getBinder();
+        mComponentContextFixture.setTelecomManager(mTelecomManager);
+        when(mTelecomManager.getDefaultDialerPackage()).thenReturn(DEFAULT_DIALER_PACKAGE);
+        when(mTelecomManager.getSystemDialerPackage()).thenReturn(DEFAULT_DIALER_PACKAGE);
+
+        mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
+        mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+
+        doReturn(DEFAULT_DIALER_PACKAGE)
+                .when(mDefaultDialerManagerAdapter)
+                .getDefaultDialerApplication(any(Context.class));
+
+        doReturn(true)
+                .when(mDefaultDialerManagerAdapter)
+                .isDefaultOrSystemDialer(any(Context.class), eq(DEFAULT_DIALER_PACKAGE));
+    }
+
+    @SmallTest
+    public void testGetDefaultOutgoingPhoneAccount() throws RemoteException {
+        when(mFakePhoneAccountRegistrar
+                .getOutgoingPhoneAccountForScheme(eq("tel"), any(UserHandle.class)))
+                .thenReturn(TEL_PA_HANDLE_16);
+        when(mFakePhoneAccountRegistrar
+                .getOutgoingPhoneAccountForScheme(eq("sip"), any(UserHandle.class)))
+                .thenReturn(SIP_PA_HANDLE_17);
+        makeAccountsVisibleToAllUsers(TEL_PA_HANDLE_16, SIP_PA_HANDLE_17);
+
+        PhoneAccountHandle returnedHandleTel
+                = mTSIBinder.getDefaultOutgoingPhoneAccount("tel", DEFAULT_DIALER_PACKAGE);
+        assertEquals(TEL_PA_HANDLE_16, returnedHandleTel);
+
+        PhoneAccountHandle returnedHandleSip
+                = mTSIBinder.getDefaultOutgoingPhoneAccount("sip", DEFAULT_DIALER_PACKAGE);
+        assertEquals(SIP_PA_HANDLE_17, returnedHandleSip);
+    }
+
+    @SmallTest
+    public void testGetDefaultOutgoingPhoneAccountFailure() throws RemoteException {
+        // make sure that the list of user profiles doesn't include anything the PhoneAccountHandles
+        // are associated with
+
+        when(mFakePhoneAccountRegistrar
+                .getOutgoingPhoneAccountForScheme(eq("tel"), any(UserHandle.class)))
+                .thenReturn(TEL_PA_HANDLE_16);
+        when(mFakePhoneAccountRegistrar.getPhoneAccountUnchecked(TEL_PA_HANDLE_16)).thenReturn(
+                makePhoneAccount(TEL_PA_HANDLE_16).build());
+        when(mAppOpsManager.noteOp(eq(AppOpsManager.OP_READ_PHONE_STATE), anyInt(), anyString()))
+                .thenReturn(AppOpsManager.MODE_IGNORED);
+        doThrow(new SecurityException()).when(mContext)
+                .enforceCallingOrSelfPermission(eq(READ_PRIVILEGED_PHONE_STATE), anyString());
+
+        PhoneAccountHandle returnedHandleTel
+                = mTSIBinder.getDefaultOutgoingPhoneAccount("tel", "");
+        assertNull(returnedHandleTel);
+    }
+
+    @SmallTest
+    public void testGetUserSelectedOutgoingPhoneAccount() throws RemoteException {
+        when(mFakePhoneAccountRegistrar.getUserSelectedOutgoingPhoneAccount(any(UserHandle.class)))
+                .thenReturn(TEL_PA_HANDLE_16);
+        when(mFakePhoneAccountRegistrar.getPhoneAccountUnchecked(TEL_PA_HANDLE_16)).thenReturn(
+                makeMultiUserPhoneAccount(TEL_PA_HANDLE_16).build());
+
+        PhoneAccountHandle returnedHandle
+                = mTSIBinder.getUserSelectedOutgoingPhoneAccount();
+        assertEquals(TEL_PA_HANDLE_16, returnedHandle);
+    }
+
+    @SmallTest
+    public void testSetUserSelectedOutgoingPhoneAccount() throws RemoteException {
+        mTSIBinder.setUserSelectedOutgoingPhoneAccount(TEL_PA_HANDLE_16);
+        verify(mFakePhoneAccountRegistrar)
+                .setUserSelectedOutgoingPhoneAccount(eq(TEL_PA_HANDLE_16), any(UserHandle.class));
+    }
+
+    @SmallTest
+    public void testSetUserSelectedOutgoingPhoneAccountFailure() throws RemoteException {
+        doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission(
+                anyString(), anyString());
+        try {
+            mTSIBinder.setUserSelectedOutgoingPhoneAccount(TEL_PA_HANDLE_16);
+        } catch (SecurityException e) {
+            // desired result
+        }
+        verify(mFakePhoneAccountRegistrar, never())
+                .setUserSelectedOutgoingPhoneAccount(
+                        any(PhoneAccountHandle.class), any(UserHandle.class));
+    }
+
+    @SmallTest
+    public void testGetCallCapablePhoneAccounts() throws RemoteException {
+        List<PhoneAccountHandle> fullPHList = new ArrayList<PhoneAccountHandle>() {{
+            add(TEL_PA_HANDLE_16);
+            add(SIP_PA_HANDLE_17);
+        }};
+
+        List<PhoneAccountHandle> smallPHList = new ArrayList<PhoneAccountHandle>() {{
+            add(SIP_PA_HANDLE_17);
+        }};
+        // Returns all phone accounts when getCallCapablePhoneAccounts is called.
+        when(mFakePhoneAccountRegistrar
+                .getCallCapablePhoneAccounts(anyString(), eq(true), any(UserHandle.class)))
+                .thenReturn(fullPHList);
+        // Returns only enabled phone accounts when getCallCapablePhoneAccounts is called.
+        when(mFakePhoneAccountRegistrar
+                .getCallCapablePhoneAccounts(anyString(), eq(false), any(UserHandle.class)))
+                .thenReturn(smallPHList);
+        makeAccountsVisibleToAllUsers(TEL_PA_HANDLE_16, SIP_PA_HANDLE_17);
+
+        assertEquals(fullPHList,
+                mTSIBinder.getCallCapablePhoneAccounts(true, DEFAULT_DIALER_PACKAGE));
+        assertEquals(smallPHList,
+                mTSIBinder.getCallCapablePhoneAccounts(false, DEFAULT_DIALER_PACKAGE));
+    }
+
+    @SmallTest
+    public void testGetCallCapablePhoneAccountsFailure() throws RemoteException {
+        List<String> enforcedPermissions = new ArrayList<String>() {{
+            add(READ_PHONE_STATE);
+            add(READ_PRIVILEGED_PHONE_STATE);
+        }};
+        doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission(
+                argThat(new AnyStringIn(enforcedPermissions)), anyString());
+
+        List<PhoneAccountHandle> result = null;
+        try {
+            result = mTSIBinder.getCallCapablePhoneAccounts(true, "");
+        } catch (SecurityException e) {
+            // intended behavior
+        }
+        assertNull(result);
+        verify(mFakePhoneAccountRegistrar, never())
+                .getCallCapablePhoneAccounts(anyString(), anyBoolean(), any(UserHandle.class));
+    }
+
+    @SmallTest
+    public void testGetPhoneAccountsSupportingScheme() throws RemoteException {
+        List<PhoneAccountHandle> sipPHList = new ArrayList<PhoneAccountHandle>() {{
+            add(SIP_PA_HANDLE_17);
+        }};
+
+        List<PhoneAccountHandle> telPHList = new ArrayList<PhoneAccountHandle>() {{
+            add(TEL_PA_HANDLE_16);
+        }};
+        when(mFakePhoneAccountRegistrar
+                .getCallCapablePhoneAccounts(eq("tel"), anyBoolean(), any(UserHandle.class)))
+                .thenReturn(telPHList);
+        when(mFakePhoneAccountRegistrar
+                .getCallCapablePhoneAccounts(eq("sip"), anyBoolean(), any(UserHandle.class)))
+                .thenReturn(sipPHList);
+        makeAccountsVisibleToAllUsers(TEL_PA_HANDLE_16, SIP_PA_HANDLE_17);
+
+        assertEquals(telPHList,
+                mTSIBinder.getPhoneAccountsSupportingScheme("tel", DEFAULT_DIALER_PACKAGE));
+        assertEquals(sipPHList,
+                mTSIBinder.getPhoneAccountsSupportingScheme("sip", DEFAULT_DIALER_PACKAGE));
+    }
+
+    @SmallTest
+    public void testGetPhoneAccountsForPackage() throws RemoteException {
+        List<PhoneAccountHandle> phoneAccountHandleList = new ArrayList<PhoneAccountHandle>() {{
+            add(TEL_PA_HANDLE_16);
+            add(SIP_PA_HANDLE_17);
+        }};
+        when(mFakePhoneAccountRegistrar
+                .getPhoneAccountsForPackage(anyString(), any(UserHandle.class)))
+                .thenReturn(phoneAccountHandleList);
+        makeAccountsVisibleToAllUsers(TEL_PA_HANDLE_16, SIP_PA_HANDLE_17);
+        assertEquals(phoneAccountHandleList,
+                mTSIBinder.getPhoneAccountsForPackage(
+                        TEL_PA_HANDLE_16.getComponentName().getPackageName()));
+    }
+
+    @SmallTest
+    public void testGetPhoneAccount() throws RemoteException {
+        makeAccountsVisibleToAllUsers(TEL_PA_HANDLE_16, SIP_PA_HANDLE_17);
+        assertEquals(TEL_PA_HANDLE_16, mTSIBinder.getPhoneAccount(TEL_PA_HANDLE_16)
+                .getAccountHandle());
+        assertEquals(SIP_PA_HANDLE_17, mTSIBinder.getPhoneAccount(SIP_PA_HANDLE_17)
+                .getAccountHandle());
+    }
+
+    @SmallTest
+    public void testGetAllPhoneAccounts() throws RemoteException {
+        List<PhoneAccount> phoneAccountList = new ArrayList<PhoneAccount>() {{
+            add(makePhoneAccount(TEL_PA_HANDLE_16).build());
+            add(makePhoneAccount(SIP_PA_HANDLE_17).build());
+        }};
+        when(mFakePhoneAccountRegistrar.getAllPhoneAccounts(any(UserHandle.class)))
+                .thenReturn(phoneAccountList);
+
+        assertEquals(2, mTSIBinder.getAllPhoneAccounts().size());
+    }
+
+    @SmallTest
+    public void testRegisterPhoneAccount() throws RemoteException {
+        String packageNameToUse = "com.android.officialpackage";
+        PhoneAccountHandle phHandle = new PhoneAccountHandle(new ComponentName(
+                packageNameToUse, "cs"), "test", Binder.getCallingUserHandle());
+        PhoneAccount phoneAccount = makePhoneAccount(phHandle).build();
+        doReturn(PackageManager.PERMISSION_GRANTED)
+                .when(mContext).checkCallingOrSelfPermission(MODIFY_PHONE_STATE);
+
+        registerPhoneAccountTestHelper(phoneAccount, true);
+    }
+
+    @SmallTest
+    public void testRegisterPhoneAccountWithoutModifyPermission() throws RemoteException {
+        // tests the case where the package does not have MODIFY_PHONE_STATE but is
+        // registering its own phone account as a third-party connection service
+        String packageNameToUse = "com.thirdparty.connectionservice";
+        PhoneAccountHandle phHandle = new PhoneAccountHandle(new ComponentName(
+                packageNameToUse, "cs"), "asdf", Binder.getCallingUserHandle());
+        PhoneAccount phoneAccount = makePhoneAccount(phHandle).build();
+
+        doReturn(PackageManager.PERMISSION_DENIED)
+                .when(mContext).checkCallingOrSelfPermission(MODIFY_PHONE_STATE);
+        PackageManager pm = mContext.getPackageManager();
+        when(pm.hasSystemFeature(PackageManager.FEATURE_CONNECTION_SERVICE)).thenReturn(true);
+
+        registerPhoneAccountTestHelper(phoneAccount, true);
+    }
+
+    @SmallTest
+    public void testRegisterPhoneAccountWithoutModifyPermissionFailure() throws RemoteException {
+        // tests the case where the third party package should not be allowed to register a phone
+        // account due to the lack of modify permission.
+        String packageNameToUse = "com.thirdparty.connectionservice";
+        PhoneAccountHandle phHandle = new PhoneAccountHandle(new ComponentName(
+                packageNameToUse, "cs"), "asdf", Binder.getCallingUserHandle());
+        PhoneAccount phoneAccount = makePhoneAccount(phHandle).build();
+
+        doReturn(PackageManager.PERMISSION_DENIED)
+                .when(mContext).checkCallingOrSelfPermission(MODIFY_PHONE_STATE);
+        PackageManager pm = mContext.getPackageManager();
+        when(pm.hasSystemFeature(PackageManager.FEATURE_CONNECTION_SERVICE)).thenReturn(false);
+
+        registerPhoneAccountTestHelper(phoneAccount, false);
+    }
+
+    @SmallTest
+    public void testRegisterPhoneAccountWithoutSimSubscriptionPermissionFailure()
+            throws RemoteException {
+        String packageNameToUse = "com.thirdparty.connectionservice";
+        PhoneAccountHandle phHandle = new PhoneAccountHandle(new ComponentName(
+                packageNameToUse, "cs"), "asdf", Binder.getCallingUserHandle());
+        PhoneAccount phoneAccount = makePhoneAccount(phHandle)
+                .setCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION).build();
+
+        doReturn(PackageManager.PERMISSION_GRANTED)
+                .when(mContext).checkCallingOrSelfPermission(MODIFY_PHONE_STATE);
+        doThrow(new SecurityException())
+                .when(mContext)
+                .enforceCallingOrSelfPermission(eq(REGISTER_SIM_SUBSCRIPTION), anyString());
+
+        registerPhoneAccountTestHelper(phoneAccount, false);
+    }
+
+    @SmallTest
+    public void testRegisterPhoneAccountWithoutMultiUserPermissionFailure()
+            throws Exception {
+        String packageNameToUse = "com.thirdparty.connectionservice";
+        PhoneAccountHandle phHandle = new PhoneAccountHandle(new ComponentName(
+                packageNameToUse, "cs"), "asdf", Binder.getCallingUserHandle());
+        PhoneAccount phoneAccount = makeMultiUserPhoneAccount(phHandle).build();
+
+        doReturn(PackageManager.PERMISSION_GRANTED)
+                .when(mContext).checkCallingOrSelfPermission(MODIFY_PHONE_STATE);
+
+        PackageManager packageManager = mContext.getPackageManager();
+        when(packageManager.getApplicationInfo(packageNameToUse, PackageManager.GET_META_DATA))
+                .thenReturn(new ApplicationInfo());
+
+        registerPhoneAccountTestHelper(phoneAccount, false);
+    }
+
+    private void registerPhoneAccountTestHelper(PhoneAccount testPhoneAccount,
+            boolean shouldSucceed) throws RemoteException {
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        boolean didExceptionOccur = false;
+        try {
+            mTSIBinder.registerPhoneAccount(testPhoneAccount);
+        } catch (Exception e) {
+            didExceptionOccur = true;
+        }
+
+        if (shouldSucceed) {
+            assertFalse(didExceptionOccur);
+            verify(mFakePhoneAccountRegistrar).registerPhoneAccount(testPhoneAccount);
+            verify(mContext).sendBroadcastAsUser(intentCaptor.capture(), eq(UserHandle.ALL),
+                    anyString());
+
+            Intent capturedIntent = intentCaptor.getValue();
+            assertEquals(TelecomManager.ACTION_PHONE_ACCOUNT_REGISTERED,
+                    capturedIntent.getAction());
+            Bundle intentExtras = capturedIntent.getExtras();
+            assertEquals(1, intentExtras.size());
+            assertEquals(testPhoneAccount.getAccountHandle(),
+                    intentExtras.get(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE));
+        } else {
+            assertTrue(didExceptionOccur);
+            verify(mFakePhoneAccountRegistrar, never())
+                    .registerPhoneAccount(any(PhoneAccount.class));
+            verify(mContext, never())
+                    .sendBroadcastAsUser(any(Intent.class), any(UserHandle.class), anyString());
+        }
+    }
+
+    @SmallTest
+    public void testUnregisterPhoneAccount() throws RemoteException {
+        String packageNameToUse = "com.android.officialpackage";
+        PhoneAccountHandle phHandle = new PhoneAccountHandle(new ComponentName(
+                packageNameToUse, "cs"), "test", Binder.getCallingUserHandle());
+
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        doReturn(PackageManager.PERMISSION_GRANTED)
+                .when(mContext).checkCallingOrSelfPermission(MODIFY_PHONE_STATE);
+
+        mTSIBinder.unregisterPhoneAccount(phHandle);
+        verify(mFakePhoneAccountRegistrar).unregisterPhoneAccount(phHandle);
+        verify(mContext).sendBroadcastAsUser(intentCaptor.capture(), eq(UserHandle.ALL),
+                anyString());
+        Intent capturedIntent = intentCaptor.getValue();
+        assertEquals(TelecomManager.ACTION_PHONE_ACCOUNT_UNREGISTERED,
+                capturedIntent.getAction());
+        Bundle intentExtras = capturedIntent.getExtras();
+        assertEquals(1, intentExtras.size());
+        assertEquals(phHandle, intentExtras.get(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE));
+    }
+
+    @SmallTest
+    public void testUnregisterPhoneAccountFailure() throws RemoteException {
+        String packageNameToUse = "com.thirdparty.connectionservice";
+        PhoneAccountHandle phHandle = new PhoneAccountHandle(new ComponentName(
+                packageNameToUse, "cs"), "asdf", Binder.getCallingUserHandle());
+
+        doReturn(PackageManager.PERMISSION_DENIED)
+                .when(mContext).checkCallingOrSelfPermission(MODIFY_PHONE_STATE);
+        PackageManager pm = mContext.getPackageManager();
+        when(pm.hasSystemFeature(PackageManager.FEATURE_CONNECTION_SERVICE)).thenReturn(false);
+
+        try {
+            mTSIBinder.unregisterPhoneAccount(phHandle);
+        } catch (UnsupportedOperationException e) {
+            // expected behavior
+        }
+        verify(mFakePhoneAccountRegistrar, never())
+                .unregisterPhoneAccount(any(PhoneAccountHandle.class));
+        verify(mContext, never())
+                .sendBroadcastAsUser(any(Intent.class), any(UserHandle.class), anyString());
+    }
+
+    @SmallTest
+    public void testAddNewIncomingCall() throws Exception {
+        doNothing().when(mAppOpsManager).checkPackage(anyInt(), anyString());
+        Bundle extras = createSampleExtras();
+
+        mTSIBinder.addNewIncomingCall(TEL_PA_HANDLE_CURRENT, extras);
+
+        addCallTestHelper(TelecomManager.ACTION_INCOMING_CALL,
+                CallIntentProcessor.KEY_IS_INCOMING_CALL, extras, false);
+    }
+
+    @SmallTest
+    public void testAddNewIncomingCallFailure() throws Exception {
+        try {
+            mTSIBinder.addNewIncomingCall(TEL_PA_HANDLE_16, null);
+        } catch (SecurityException e) {
+            // expected
+        }
+
+        doThrow(new SecurityException()).when(mAppOpsManager).checkPackage(anyInt(), anyString());
+
+        try {
+            mTSIBinder.addNewIncomingCall(TEL_PA_HANDLE_CURRENT, null);
+        } catch (SecurityException e) {
+            // expected
+        }
+
+        // Verify that neither of these attempts got through
+        verify(mCallIntentProcessorAdapter, never())
+                .processIncomingCallIntent(any(CallsManager.class), any(Intent.class));
+    }
+
+    @SmallTest
+    public void testAddNewUnknownCall() throws Exception {
+        doNothing().when(mAppOpsManager).checkPackage(anyInt(), anyString());
+        Bundle extras = createSampleExtras();
+
+        mTSIBinder.addNewUnknownCall(TEL_PA_HANDLE_CURRENT, extras);
+
+        addCallTestHelper(TelecomManager.ACTION_NEW_UNKNOWN_CALL,
+                CallIntentProcessor.KEY_IS_UNKNOWN_CALL, extras, true);
+    }
+
+    @SmallTest
+    public void testAddNewUnknownCallFailure() throws Exception {
+        try {
+            mTSIBinder.addNewUnknownCall(TEL_PA_HANDLE_16, null);
+        } catch (SecurityException e) {
+            // expected
+        }
+
+        doThrow(new SecurityException()).when(mAppOpsManager).checkPackage(anyInt(), anyString());
+
+        try {
+            mTSIBinder.addNewUnknownCall(TEL_PA_HANDLE_CURRENT, null);
+        } catch (SecurityException e) {
+            // expected
+        }
+
+        // Verify that neither of these attempts got through
+        verify(mCallIntentProcessorAdapter, never())
+                .processIncomingCallIntent(any(CallsManager.class), any(Intent.class));
+    }
+
+    private void addCallTestHelper(String expectedAction, String extraCallKey,
+            Bundle expectedExtras, boolean isUnknown) {
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        if (isUnknown) {
+            verify(mCallIntentProcessorAdapter).processUnknownCallIntent(any(CallsManager.class),
+                    intentCaptor.capture());
+        } else {
+            verify(mCallIntentProcessorAdapter).processIncomingCallIntent(any(CallsManager.class),
+                    intentCaptor.capture());
+        }
+        Intent capturedIntent = intentCaptor.getValue();
+        assertEquals(expectedAction, capturedIntent.getAction());
+        Bundle intentExtras = capturedIntent.getExtras();
+        assertEquals(TEL_PA_HANDLE_CURRENT,
+                intentExtras.get(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE));
+        assertTrue(intentExtras.getBoolean(extraCallKey));
+
+        if (isUnknown) {
+            for (String expectedKey : expectedExtras.keySet()) {
+                assertTrue(intentExtras.containsKey(expectedKey));
+                assertEquals(expectedExtras.get(expectedKey), intentExtras.get(expectedKey));
+            }
+        }
+        else {
+            assertTrue(areBundlesEqual(expectedExtras,
+                    (Bundle) intentExtras.get(TelecomManager.EXTRA_INCOMING_CALL_EXTRAS)));
+        }
+    }
+
+    @SmallTest
+    public void testPlaceCallWithNonEmergencyPermission() throws Exception {
+        Uri handle = Uri.parse("tel:6505551234");
+        Bundle extras = createSampleExtras();
+
+        when(mAppOpsManager.noteOp(eq(AppOpsManager.OP_CALL_PHONE), anyInt(), anyString()))
+                .thenReturn(AppOpsManager.MODE_ALLOWED);
+        doReturn(PackageManager.PERMISSION_GRANTED)
+                .when(mContext).checkCallingPermission(CALL_PHONE);
+
+        mTSIBinder.placeCall(handle, extras, DEFAULT_DIALER_PACKAGE);
+        placeCallTestHelper(handle, extras, true);
+    }
+
+    @SmallTest
+    public void testPlaceCallWithAppOpsOff() throws Exception {
+        Uri handle = Uri.parse("tel:6505551234");
+        Bundle extras = createSampleExtras();
+
+        when(mAppOpsManager.noteOp(eq(AppOpsManager.OP_CALL_PHONE), anyInt(), anyString()))
+                .thenReturn(AppOpsManager.MODE_IGNORED);
+        doReturn(PackageManager.PERMISSION_GRANTED)
+                .when(mContext).checkCallingPermission(CALL_PHONE);
+
+        mTSIBinder.placeCall(handle, extras, DEFAULT_DIALER_PACKAGE);
+        placeCallTestHelper(handle, extras, false);
+    }
+
+    @SmallTest
+    public void testPlaceCallWithNoCallingPermission() throws Exception {
+        Uri handle = Uri.parse("tel:6505551234");
+        Bundle extras = createSampleExtras();
+
+        when(mAppOpsManager.noteOp(eq(AppOpsManager.OP_CALL_PHONE), anyInt(), anyString()))
+                .thenReturn(AppOpsManager.MODE_ALLOWED);
+        doReturn(PackageManager.PERMISSION_DENIED)
+                .when(mContext).checkCallingPermission(CALL_PHONE);
+
+        mTSIBinder.placeCall(handle, extras, DEFAULT_DIALER_PACKAGE);
+        placeCallTestHelper(handle, extras, false);
+    }
+
+    private void placeCallTestHelper(Uri expectedHandle, Bundle expectedExtras,
+            boolean shouldNonEmergencyBeAllowed) {
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mUserCallIntentProcessor).processIntent(intentCaptor.capture(), anyString(),
+                eq(shouldNonEmergencyBeAllowed));
+        Intent capturedIntent = intentCaptor.getValue();
+        assertEquals(Intent.ACTION_CALL, capturedIntent.getAction());
+        assertEquals(expectedHandle, capturedIntent.getData());
+        assertTrue(areBundlesEqual(expectedExtras, capturedIntent.getExtras()));
+    }
+
+    @SmallTest
+    public void testPlaceCallFailure() throws Exception {
+        Uri handle = Uri.parse("tel:6505551234");
+        Bundle extras = createSampleExtras();
+
+        doThrow(new SecurityException())
+                .when(mContext).enforceCallingOrSelfPermission(eq(CALL_PHONE), anyString());
+
+        try {
+            mTSIBinder.placeCall(handle, extras, "arbitrary_package_name");
+        } catch (SecurityException e) {
+            // expected
+        }
+
+        verify(mUserCallIntentProcessor, never())
+                .processIntent(any(Intent.class), anyString(), anyBoolean());
+    }
+
+    @SmallTest
+    public void testSetDefaultDialer() throws Exception {
+        String packageName = "sample.package";
+
+        doReturn(true)
+                .when(mDefaultDialerManagerAdapter)
+                .setDefaultDialerApplication(any(Context.class), eq(packageName));
+
+        mTSIBinder.setDefaultDialer(packageName);
+
+        verify(mDefaultDialerManagerAdapter).setDefaultDialerApplication(any(Context.class),
+                eq(packageName));
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext).sendBroadcastAsUser(intentCaptor.capture(), any(UserHandle.class));
+        Intent capturedIntent = intentCaptor.getValue();
+        assertEquals(TelecomManager.ACTION_DEFAULT_DIALER_CHANGED, capturedIntent.getAction());
+        String packageNameExtra = capturedIntent.getStringExtra(
+                TelecomManager.EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME);
+        assertEquals(packageName, packageNameExtra);
+    }
+
+    @SmallTest
+    public void testSetDefaultDialerNoModifyPhoneStatePermission() throws Exception {
+        doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission(
+                eq(MODIFY_PHONE_STATE), anyString());
+        setDefaultDialerFailureTestHelper();
+    }
+
+    @SmallTest
+    public void testSetDefaultDialerNoWriteSecureSettingsPermission() throws Exception {
+        doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission(
+                eq(WRITE_SECURE_SETTINGS), anyString());
+        setDefaultDialerFailureTestHelper();
+    }
+
+    private void setDefaultDialerFailureTestHelper() throws Exception {
+        boolean exceptionThrown = false;
+        try {
+            mTSIBinder.setDefaultDialer(DEFAULT_DIALER_PACKAGE);
+        } catch (SecurityException e) {
+            exceptionThrown = true;
+        }
+        assertTrue(exceptionThrown);
+        verify(mDefaultDialerManagerAdapter, never()).setDefaultDialerApplication(
+                any(Context.class), anyString());
+        verify(mContext, never()).sendBroadcastAsUser(any(Intent.class), any(UserHandle.class));
+    }
+
+    @SmallTest
+    public void testIsVoicemailNumber() throws Exception {
+        String vmNumber = "010";
+        makeAccountsVisibleToAllUsers(TEL_PA_HANDLE_CURRENT);
+
+        doReturn(true).when(mFakePhoneAccountRegistrar).isVoiceMailNumber(TEL_PA_HANDLE_CURRENT,
+                vmNumber);
+        assertTrue(mTSIBinder.isVoiceMailNumber(TEL_PA_HANDLE_CURRENT,
+                vmNumber, DEFAULT_DIALER_PACKAGE));
+    }
+
+    @SmallTest
+    public void testIsVoicemailNumberAccountNotVisibleFailure() throws Exception {
+        String vmNumber = "010";
+
+        doReturn(true).when(mFakePhoneAccountRegistrar).isVoiceMailNumber(TEL_PA_HANDLE_CURRENT,
+                vmNumber);
+
+        when(mFakePhoneAccountRegistrar.getPhoneAccount(TEL_PA_HANDLE_CURRENT,
+                Binder.getCallingUserHandle())).thenReturn(null);
+        assertFalse(mTSIBinder
+                .isVoiceMailNumber(TEL_PA_HANDLE_CURRENT, vmNumber, DEFAULT_DIALER_PACKAGE));
+    }
+
+    @SmallTest
+    public void testGetVoicemailNumberWithNullAccountHandle() throws Exception {
+        when(mFakePhoneAccountRegistrar.getPhoneAccount(isNull(PhoneAccountHandle.class),
+                eq(Binder.getCallingUserHandle())))
+                .thenReturn(makePhoneAccount(TEL_PA_HANDLE_CURRENT).build());
+        int subId = 58374;
+        String vmNumber = "543";
+        doReturn(subId).when(mSubscriptionManagerAdapter).getDefaultVoiceSubId();
+
+        TelephonyManager mockTelephonyManager =
+                (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+        when(mockTelephonyManager.getVoiceMailNumber(subId)).thenReturn(vmNumber);
+
+        assertEquals(vmNumber, mTSIBinder.getVoiceMailNumber(null, DEFAULT_DIALER_PACKAGE));
+    }
+
+    @SmallTest
+    public void testGetVoicemailNumberWithNonNullAccountHandle() throws Exception {
+        when(mFakePhoneAccountRegistrar.getPhoneAccount(eq(TEL_PA_HANDLE_CURRENT),
+                eq(Binder.getCallingUserHandle())))
+                .thenReturn(makePhoneAccount(TEL_PA_HANDLE_CURRENT).build());
+        int subId = 58374;
+        String vmNumber = "543";
+
+        TelephonyManager mockTelephonyManager =
+                (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+        when(mockTelephonyManager.getVoiceMailNumber(subId)).thenReturn(vmNumber);
+        when(mFakePhoneAccountRegistrar.getSubscriptionIdForPhoneAccount(TEL_PA_HANDLE_CURRENT))
+                .thenReturn(subId);
+
+        assertEquals(vmNumber,
+                mTSIBinder.getVoiceMailNumber(TEL_PA_HANDLE_CURRENT, DEFAULT_DIALER_PACKAGE));
+    }
+
+    @SmallTest
+    public void testGetLine1Number() throws Exception {
+        int subId = 58374;
+        String line1Number = "9482752023479";
+        makeAccountsVisibleToAllUsers(TEL_PA_HANDLE_CURRENT);
+        when(mFakePhoneAccountRegistrar.getSubscriptionIdForPhoneAccount(TEL_PA_HANDLE_CURRENT))
+                .thenReturn(subId);
+        TelephonyManager mockTelephonyManager =
+                (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+        when(mockTelephonyManager.getLine1Number(subId)).thenReturn(line1Number);
+
+        assertEquals(line1Number,
+                mTSIBinder.getLine1Number(TEL_PA_HANDLE_CURRENT, DEFAULT_DIALER_PACKAGE));
+    }
+
+    @SmallTest
+    public void testEndCallWithRingingForegroundCall() throws Exception {
+        Call call = mock(Call.class);
+        when(call.getState()).thenReturn(CallState.RINGING);
+        when(mFakeCallsManager.getForegroundCall()).thenReturn(call);
+        assertTrue(mTSIBinder.endCall());
+        verify(call).reject(false, null);
+    }
+
+    @SmallTest
+    public void testEndCallWithNonRingingForegroundCall() throws Exception {
+        Call call = mock(Call.class);
+        when(call.getState()).thenReturn(CallState.ACTIVE);
+        when(mFakeCallsManager.getForegroundCall()).thenReturn(call);
+        assertTrue(mTSIBinder.endCall());
+        verify(call).disconnect();
+    }
+
+    @SmallTest
+    public void testEndCallWithNoForegroundCall() throws Exception {
+        Call call = mock(Call.class);
+        when(call.getState()).thenReturn(CallState.ACTIVE);
+        when(mFakeCallsManager.getFirstCallWithState(anyInt(), anyInt(), anyInt(), anyInt()))
+                .thenReturn(call);
+        assertTrue(mTSIBinder.endCall());
+        verify(call).disconnect();
+    }
+
+    @SmallTest
+    public void testEndCallWithNoCalls() throws Exception {
+        assertFalse(mTSIBinder.endCall());
+    }
+
+    @SmallTest
+    public void testAcceptRingingCall() throws Exception {
+        Call call = mock(Call.class);
+        when(mFakeCallsManager.getFirstCallWithState(any(int[].class)))
+                .thenReturn(call);
+        // Not intended to be a real video state. Here to ensure that the call will be answered
+        // with whatever video state it's currently in.
+        int fakeVideoState = 29578215;
+        when(call.getVideoState()).thenReturn(fakeVideoState);
+        mTSIBinder.acceptRingingCall();
+        verify(call).answer(fakeVideoState);
+    }
+
+    @SmallTest
+    public void testAcceptRingingCallWithValidVideoState() throws Exception {
+        Call call = mock(Call.class);
+        when(mFakeCallsManager.getFirstCallWithState(any(int[].class)))
+                .thenReturn(call);
+        // Not intended to be a real video state. Here to ensure that the call will be answered
+        // with the video state passed in to acceptRingingCallWithVideoState
+        int fakeVideoState = 29578215;
+        int realVideoState = VideoProfile.STATE_RX_ENABLED | VideoProfile.STATE_TX_ENABLED;
+        when(call.getVideoState()).thenReturn(fakeVideoState);
+        mTSIBinder.acceptRingingCallWithVideoState(realVideoState);
+        verify(call).answer(realVideoState);
+    }
+
+    /**
+     * Register phone accounts for the supplied PhoneAccountHandles to make them
+     * visible to all users (via the isVisibleToCaller method in TelecomServiceImpl.
+     * @param handles the handles for which phone accounts should be created for.
+     */
+    private void makeAccountsVisibleToAllUsers(PhoneAccountHandle... handles) {
+        for (PhoneAccountHandle ph : handles) {
+            when(mFakePhoneAccountRegistrar.getPhoneAccountUnchecked(eq(ph))).thenReturn(
+                    makeMultiUserPhoneAccount(ph).build());
+            when(mFakePhoneAccountRegistrar
+                    .getPhoneAccount(eq(ph), any(UserHandle.class), anyBoolean()))
+                    .thenReturn(makeMultiUserPhoneAccount(ph).build());
+            when(mFakePhoneAccountRegistrar
+                    .getPhoneAccount(eq(ph), any(UserHandle.class)))
+                    .thenReturn(makeMultiUserPhoneAccount(ph).build());
+        }
+    }
+
+    private PhoneAccount.Builder makeMultiUserPhoneAccount(PhoneAccountHandle paHandle) {
+        PhoneAccount.Builder paBuilder = makePhoneAccount(paHandle);
+        paBuilder.setCapabilities(PhoneAccount.CAPABILITY_MULTI_USER);
+        return paBuilder;
+    }
+
+    private PhoneAccount.Builder makePhoneAccount(PhoneAccountHandle paHandle) {
+        return new PhoneAccount.Builder(paHandle, "testLabel");
+    }
+
+    private Bundle createSampleExtras() {
+        Bundle extras = new Bundle();
+        extras.putString("test_key", "test_value");
+        return extras;
+    }
+
+    private static boolean areBundlesEqual(Bundle b1, Bundle b2) {
+        for (String key1 : b1.keySet()) {
+            if (!b1.get(key1).equals(b2.get(key1))) {
+                return false;
+            }
+        }
+
+        for (String key2 : b2.keySet()) {
+            if (!b2.get(key2).equals(b1.get(key2))) {
+                return false;
+            }
+        }
+        return true;
+    }
+}
diff --git a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
index 0e35f01..20f62f2 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
@@ -16,68 +16,126 @@
 
 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.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.Handler;
+import android.os.Process;
 import android.os.UserHandle;
 import android.telecom.Call;
-import android.telecom.CallAudioState;
-import android.telecom.Connection;
 import android.telecom.ConnectionRequest;
-import android.telecom.DisconnectCause;
 import android.telecom.ParcelableCall;
 import android.telecom.PhoneAccount;
 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.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.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.UserCallIntentProcessor;
+import com.android.server.telecom.ui.MissedCallNotifierImpl.MissedCallNotifierImplFactory;
+
+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.util.concurrent.BrokenBarrierException;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.CyclicBarrier;
-
+/**
+ * Implements mocks and functionality required to implement telecom system tests.
+ */
 public class TelecomSystemTest extends TelecomTestCase {
 
     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(UserHandle userHandle) {
+
+        }
+
+        @Override
+        public void showMissedCallNotification(com.android.server.telecom.Call call) {
+
+        }
+
+        @Override
+        public void reloadFromDatabase(TelecomSystem.SyncRoot lock, CallsManager callsManager,
+                ContactsAsyncHelper contactsAsyncHelper,
+                CallerInfoAsyncQueryFactory callerInfoAsyncQueryFactory, UserHandle userHandle) {
+
+        }
+
+        @Override
+        public void setCurrentUserHandle(UserHandle userHandle) {
+
+        }
+    }
+
+    MissedCallNotifier mMissedCallNotifier = new MissedCallNotifierFakeImpl();
     @Mock HeadsetMediaButton mHeadsetMediaButton;
     @Mock ProximitySensorManager mProximitySensorManager;
     @Mock InCallWakeLockController mInCallWakeLockController;
+    @Mock BluetoothPhoneServiceImpl mBluetoothPhoneServiceImpl;
 
     final ComponentName mInCallServiceComponentNameX =
             new ComponentName(
@@ -139,8 +197,14 @@
 
     CallerInfoAsyncQueryFactoryFixture mCallerInfoAsyncQueryFactoryFixture;
 
+    IAudioService mAudioService;
+
     TelecomSystem mTelecomSystem;
 
+    Context mSpyContext;
+
+    private int mNumOutgoingCallsMade;
+
     class IdPair {
         final String mConnectionId;
         final String mCallId;
@@ -154,6 +218,10 @@
     @Override
     public void setUp() throws Exception {
         super.setUp();
+        mSpyContext = mComponentContextFixture.getTestDouble().getApplicationContext();
+        doReturn(mSpyContext).when(mSpyContext).getApplicationContext();
+
+        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 +242,48 @@
     }
 
     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,
+                new MissedCallNotifierImplFactory() {
+                    @Override
+                    public MissedCallNotifier makeMissedCallNotifierImpl(Context context,
+                            PhoneAccountRegistrar phoneAccountRegistrar) {
+                        return 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()),
@@ -221,11 +301,9 @@
         mConnectionServiceFixtureA = new ConnectionServiceFixture();
         mConnectionServiceFixtureB = new ConnectionServiceFixture();
 
-        mComponentContextFixture.addConnectionService(
-                mConnectionServiceComponentNameA,
+        mComponentContextFixture.addConnectionService(mConnectionServiceComponentNameA,
                 mConnectionServiceFixtureA.getTestDouble());
-        mComponentContextFixture.addConnectionService(
-                mConnectionServiceComponentNameB,
+        mComponentContextFixture.addConnectionService(mConnectionServiceComponentNameB,
                 mConnectionServiceFixtureB.getTestDouble());
 
         mTelecomSystem.getPhoneAccountRegistrar().registerPhoneAccount(mPhoneAccountA0);
@@ -233,7 +311,7 @@
         mTelecomSystem.getPhoneAccountRegistrar().registerPhoneAccount(mPhoneAccountB0);
 
         mTelecomSystem.getPhoneAccountRegistrar().setUserSelectedOutgoingPhoneAccount(
-                mPhoneAccountA0.getAccountHandle());
+                mPhoneAccountA0.getAccountHandle(), Process.myUserHandle());
     }
 
     private void setupInCallServices() throws Exception {
@@ -243,34 +321,67 @@
         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();
 
-        mComponentContextFixture.addInCallService(
-                mInCallServiceComponentNameX,
+        mComponentContextFixture.addInCallService(mInCallServiceComponentNameX,
                 mInCallServiceFixtureX.getTestDouble());
-        mComponentContextFixture.addInCallService(
-                mInCallServiceComponentNameY,
+        mComponentContextFixture.addInCallService(mInCallServiceComponentNameY,
                 mInCallServiceFixtureY.getTestDouble());
     }
 
-    private IdPair startOutgoingPhoneCall(
-            String number,
-            PhoneAccountHandle phoneAccountHandle,
-            ConnectionServiceFixture connectionServiceFixture) throws Exception {
-        reset(
-                connectionServiceFixture.getTestDouble(),
-                mInCallServiceFixtureX.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;
+    }
+
+    protected IdPair startOutgoingPhoneCall(String number, PhoneAccountHandle phoneAccountHandle,
+            ConnectionServiceFixture connectionServiceFixture, UserHandle initiatingUser)
+            throws Exception {
+        return startOutgoingPhoneCall(number, phoneAccountHandle, connectionServiceFixture,
+                initiatingUser, VideoProfile.STATE_AUDIO_ONLY);
+    }
+
+    protected IdPair startOutgoingPhoneCall(String number, PhoneAccountHandle phoneAccountHandle,
+            ConnectionServiceFixture connectionServiceFixture, UserHandle initiatingUser,
+            int videoState) throws Exception {
+        reset(connectionServiceFixture.getTestDouble(), mInCallServiceFixtureX.getTestDouble(),
                 mInCallServiceFixtureY.getTestDouble());
 
-        assertEquals(
-                mInCallServiceFixtureX.mCallById.size(),
+        assertEquals(mInCallServiceFixtureX.mCallById.size(),
                 mInCallServiceFixtureY.mCallById.size());
-        assertEquals(
-                (mInCallServiceFixtureX.mInCallAdapter != null),
+        assertEquals((mInCallServiceFixtureX.mInCallAdapter != null),
                 (mInCallServiceFixtureY.mInCallAdapter != null));
 
+        mNumOutgoingCallsMade++;
         int startingNumConnections = connectionServiceFixture.mConnectionById.size();
         int startingNumCalls = mInCallServiceFixtureX.mCallById.size();
         boolean hasInCallAdapter = mInCallServiceFixtureX.mInCallAdapter != null;
@@ -284,7 +395,18 @@
                     TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
                     phoneAccountHandle);
         }
+        if (videoState != VideoProfile.STATE_AUDIO_ONLY) {
+            actionCallIntent.putExtra(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, videoState);
+        }
 
+        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);
 
         if (!hasInCallAdapter) {
@@ -301,7 +423,8 @@
         ArgumentCaptor<BroadcastReceiver> newOutgoingCallReceiver =
                 ArgumentCaptor.forClass(BroadcastReceiver.class);
 
-        verify(mComponentContextFixture.getTestDouble().getApplicationContext())
+        verify(mComponentContextFixture.getTestDouble().getApplicationContext(),
+                times(mNumOutgoingCallsMade))
                 .sendOrderedBroadcastAsUser(
                         newOutgoingCallIntent.capture(),
                         any(UserHandle.class),
@@ -319,35 +442,27 @@
                 new BroadcastReceiver.PendingResult(0, "", null, 0, true, false, null, 0, 0));
         newOutgoingCallReceiver.getValue().setResultData(
                 newOutgoingCallIntent.getValue().getStringExtra(Intent.EXTRA_PHONE_NUMBER));
-        newOutgoingCallReceiver.getValue().onReceive(
-                mComponentContextFixture.getTestDouble(),
+        newOutgoingCallReceiver.getValue().onReceive(mComponentContextFixture.getTestDouble(),
                 newOutgoingCallIntent.getValue());
 
         assertEquals(startingNumConnections + 1, connectionServiceFixture.mConnectionById.size());
 
-        verify(connectionServiceFixture.getTestDouble()).createConnection(
-                eq(phoneAccountHandle),
-                anyString(),
-                any(ConnectionRequest.class),
-                anyBoolean(),
-                anyBoolean());
-
+        verify(connectionServiceFixture.getTestDouble())
+                .createConnection(eq(phoneAccountHandle), anyString(), any(ConnectionRequest.class),
+                        anyBoolean(), anyBoolean());
         connectionServiceFixture.sendHandleCreateConnectionComplete(
                 connectionServiceFixture.mLatestConnectionId);
 
         assertEquals(startingNumCalls + 1, mInCallServiceFixtureX.mCallById.size());
         assertEquals(startingNumCalls + 1, mInCallServiceFixtureY.mCallById.size());
 
-        assertEquals(
-                mInCallServiceFixtureX.mLatestCallId,
-                mInCallServiceFixtureY.mLatestCallId);
+        assertEquals(mInCallServiceFixtureX.mLatestCallId, mInCallServiceFixtureY.mLatestCallId);
 
-        return new IdPair(
-                connectionServiceFixture.mLatestConnectionId,
+        return new IdPair(connectionServiceFixture.mLatestConnectionId,
                 mInCallServiceFixtureX.mLatestCallId);
     }
 
-    private IdPair startIncomingPhoneCall(
+    protected IdPair startIncomingPhoneCall(
             String number,
             PhoneAccountHandle phoneAccountHandle,
             final ConnectionServiceFixture connectionServiceFixture) throws Exception {
@@ -355,26 +470,22 @@
                 connectionServiceFixture);
     }
 
-    private IdPair startIncomingPhoneCall(
+    protected IdPair startIncomingPhoneCall(
             String number,
             PhoneAccountHandle phoneAccountHandle,
             int videoState,
             final ConnectionServiceFixture connectionServiceFixture) throws Exception {
-        reset(
-                connectionServiceFixture.getTestDouble(),
-                mInCallServiceFixtureX.getTestDouble(),
+        reset(connectionServiceFixture.getTestDouble(), mInCallServiceFixtureX.getTestDouble(),
                 mInCallServiceFixtureY.getTestDouble());
 
-        assertEquals(
-                mInCallServiceFixtureX.mCallById.size(),
+        assertEquals(mInCallServiceFixtureX.mCallById.size(),
                 mInCallServiceFixtureY.mCallById.size());
-        assertEquals(
-                (mInCallServiceFixtureX.mInCallAdapter != null),
+        assertEquals((mInCallServiceFixtureX.mInCallAdapter != null),
                 (mInCallServiceFixtureY.mInCallAdapter != null));
-
         final int startingNumConnections = connectionServiceFixture.mConnectionById.size();
         final int startingNumCalls = mInCallServiceFixtureX.mCallById.size();
         boolean hasInCallAdapter = mInCallServiceFixtureX.mInCallAdapter != null;
+        connectionServiceFixture.mConnectionServiceDelegate.mVideoState = videoState;
 
         Bundle extras = new Bundle();
         extras.putParcelable(
@@ -383,22 +494,16 @@
         mTelecomSystem.getTelecomServiceImpl().getBinder()
                 .addNewIncomingCall(phoneAccountHandle, extras);
 
-        verify(connectionServiceFixture.getTestDouble()).createConnection(
-                any(PhoneAccountHandle.class),
-                anyString(),
-                any(ConnectionRequest.class),
-                eq(true),
-                eq(false));
+        verify(connectionServiceFixture.getTestDouble())
+                .createConnection(any(PhoneAccountHandle.class), anyString(),
+                        any(ConnectionRequest.class), eq(true), eq(false));
 
-        mConnectionServiceFixtureA.mConnectionById.get(
-                connectionServiceFixture.mLatestConnectionId).videoState = videoState;
+        connectionServiceFixture.sendSetRinging(connectionServiceFixture.mLatestConnectionId);
 
-        connectionServiceFixture.sendHandleCreateConnectionComplete(
-                connectionServiceFixture.mLatestConnectionId);
-        connectionServiceFixture.sendSetRinging(
-                connectionServiceFixture.mLatestConnectionId);
-        connectionServiceFixture.sendSetVideoState(
-                connectionServiceFixture.mLatestConnectionId);
+        for (CallerInfoAsyncQueryFactoryFixture.Request request :
+                mCallerInfoAsyncQueryFactoryFixture.mRequests) {
+            request.reply();
+        }
 
         // 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
@@ -406,16 +511,10 @@
         // test fixtures, will be synchronous.
 
         if (!hasInCallAdapter) {
-            verify(
-                    mInCallServiceFixtureX.getTestDouble(),
-                    timeout(TEST_TIMEOUT))
-                    .setInCallAdapter(
-                            any(IInCallAdapter.class));
-            verify(
-                    mInCallServiceFixtureY.getTestDouble(),
-                    timeout(TEST_TIMEOUT))
-                    .setInCallAdapter(
-                            any(IInCallAdapter.class));
+            verify(mInCallServiceFixtureX.getTestDouble(), timeout(TEST_TIMEOUT))
+                    .setInCallAdapter(any(IInCallAdapter.class));
+            verify(mInCallServiceFixtureY.getTestDouble(), timeout(TEST_TIMEOUT))
+                    .setInCallAdapter(any(IInCallAdapter.class));
         }
 
         // Give the InCallService time to respond
@@ -434,16 +533,10 @@
             }
         });
 
-        verify(
-                mInCallServiceFixtureX.getTestDouble(),
-                timeout(TEST_TIMEOUT))
-                .addCall(
-                        any(ParcelableCall.class));
-        verify(
-                mInCallServiceFixtureY.getTestDouble(),
-                timeout(TEST_TIMEOUT))
-                .addCall(
-                        any(ParcelableCall.class));
+        verify(mInCallServiceFixtureX.getTestDouble(), timeout(TEST_TIMEOUT))
+                .addCall(any(ParcelableCall.class));
+        verify(mInCallServiceFixtureY.getTestDouble(), timeout(TEST_TIMEOUT))
+                .addCall(any(ParcelableCall.class));
 
         // Give the InCallService time to respond
 
@@ -467,53 +560,35 @@
             }
         });
 
-        assertEquals(
-                mInCallServiceFixtureX.mLatestCallId,
-                mInCallServiceFixtureY.mLatestCallId);
+        assertEquals(mInCallServiceFixtureX.mLatestCallId, mInCallServiceFixtureY.mLatestCallId);
 
-        return new IdPair(
-                connectionServiceFixture.mLatestConnectionId,
+        return new IdPair(connectionServiceFixture.mLatestConnectionId,
                 mInCallServiceFixtureX.mLatestCallId);
     }
 
-    private void rapidFire(Runnable... tasks) {
-        final CyclicBarrier barrier = new CyclicBarrier(tasks.length);
-        final CountDownLatch latch = new CountDownLatch(tasks.length);
-        for (int i = 0; i < tasks.length; i++) {
-            final Runnable task = tasks[i];
-            new Thread(new Runnable() {
-                @Override
-                public void run() {
-                    try {
-                        barrier.await();
-                        task.run();
-                    } catch (InterruptedException | BrokenBarrierException e){
-                        Log.e(TelecomSystemTest.this, e, "Unexpectedly interrupted");
-                    } finally {
-                        latch.countDown();
-                    }
-                }
-            }).start();
-        }
-        try {
-            latch.await();
-        } catch (InterruptedException e) {
-            Log.e(TelecomSystemTest.this, e, "Unexpectedly interrupted");
-        }
+    protected IdPair startAndMakeActiveOutgoingCall(
+            String number,
+            PhoneAccountHandle phoneAccountHandle,
+            ConnectionServiceFixture connectionServiceFixture) throws Exception {
+        return startAndMakeActiveOutgoingCall(number, phoneAccountHandle, connectionServiceFixture,
+                VideoProfile.STATE_AUDIO_ONLY);
     }
 
     // A simple outgoing call, verifying that the appropriate connection service is contacted,
     // the proper lifecycle is followed, and both In-Call Services are updated correctly.
-    private IdPair startAndMakeActiveOutgoingCall(
+    protected IdPair startAndMakeActiveOutgoingCall(
             String number,
             PhoneAccountHandle phoneAccountHandle,
-            ConnectionServiceFixture connectionServiceFixture) throws Exception {
-        IdPair ids = startOutgoingPhoneCall(number, phoneAccountHandle, connectionServiceFixture);
+            ConnectionServiceFixture connectionServiceFixture, int videoState) throws Exception {
+        IdPair ids = startOutgoingPhoneCall(number, phoneAccountHandle, connectionServiceFixture,
+                Process.myUserHandle(), videoState);
 
         connectionServiceFixture.sendSetDialing(ids.mConnectionId);
         assertEquals(Call.STATE_DIALING, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
         assertEquals(Call.STATE_DIALING, mInCallServiceFixtureY.getCall(ids.mCallId).getState());
 
+        connectionServiceFixture.sendSetVideoState(ids.mConnectionId);
+
         connectionServiceFixture.sendSetActive(ids.mConnectionId);
         assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
         assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureY.getCall(ids.mCallId).getState());
@@ -521,144 +596,35 @@
         return ids;
     }
 
-    public void testSingleOutgoingCallLocalDisconnect() throws Exception {
-        IdPair ids = startAndMakeActiveOutgoingCall(
-                "650-555-1212",
-                mPhoneAccountA0.getAccountHandle(),
-                mConnectionServiceFixtureA);
-
-        mInCallServiceFixtureX.mInCallAdapter.disconnectCall(ids.mCallId);
-        assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
-        assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureY.getCall(ids.mCallId).getState());
-
-        mConnectionServiceFixtureA.sendSetDisconnected(ids.mConnectionId, DisconnectCause.LOCAL);
-        assertEquals(Call.STATE_DISCONNECTED,
-                mInCallServiceFixtureX.getCall(ids.mCallId).getState());
-        assertEquals(Call.STATE_DISCONNECTED,
-                mInCallServiceFixtureY.getCall(ids.mCallId).getState());
-    }
-
-    public void testSingleOutgoingCallRemoteDisconnect() throws Exception {
-        IdPair ids = startAndMakeActiveOutgoingCall(
-                "650-555-1212",
-                mPhoneAccountA0.getAccountHandle(),
-                mConnectionServiceFixtureA);
-
-        mConnectionServiceFixtureA.sendSetDisconnected(ids.mConnectionId, DisconnectCause.LOCAL);
-        assertEquals(Call.STATE_DISCONNECTED,
-                mInCallServiceFixtureX.getCall(ids.mCallId).getState());
-        assertEquals(Call.STATE_DISCONNECTED,
-                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(
+    protected IdPair startAndMakeActiveIncomingCall(
             String number,
             PhoneAccountHandle phoneAccountHandle,
             ConnectionServiceFixture connectionServiceFixture) throws Exception {
+        return startAndMakeActiveIncomingCall(number, phoneAccountHandle, connectionServiceFixture,
+                VideoProfile.STATE_AUDIO_ONLY);
+    }
+
+    // A simple incoming call, similar in scope to the previous test
+    protected IdPair startAndMakeActiveIncomingCall(
+            String number,
+            PhoneAccountHandle phoneAccountHandle,
+            ConnectionServiceFixture connectionServiceFixture,
+            int videoState) throws Exception {
         IdPair ids = startIncomingPhoneCall(number, phoneAccountHandle, connectionServiceFixture);
 
         assertEquals(Call.STATE_RINGING, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
         assertEquals(Call.STATE_RINGING, mInCallServiceFixtureY.getCall(ids.mCallId).getState());
 
         mInCallServiceFixtureX.mInCallAdapter
-                .answerCall(ids.mCallId, VideoProfile.STATE_AUDIO_ONLY);
+                .answerCall(ids.mCallId, videoState);
 
-        verify(connectionServiceFixture.getTestDouble())
-                .answer(ids.mConnectionId);
+        if (!VideoProfile.isVideo(videoState)) {
+            verify(connectionServiceFixture.getTestDouble())
+                    .answer(ids.mConnectionId);
+        } else {
+            verify(connectionServiceFixture.getTestDouble())
+                    .answerVideo(ids.mConnectionId, videoState);
+        }
 
         connectionServiceFixture.sendSetActive(ids.mConnectionId);
         assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
@@ -667,146 +633,6 @@
         return ids;
     }
 
-    public void testSingleIncomingCallLocalDisconnect() throws Exception {
-        IdPair ids = startAndMakeActiveIncomingCall(
-                "650-555-1212",
-                mPhoneAccountA0.getAccountHandle(),
-                mConnectionServiceFixtureA);
-
-        mInCallServiceFixtureX.mInCallAdapter.disconnectCall(ids.mCallId);
-        assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
-        assertEquals(Call.STATE_ACTIVE, mInCallServiceFixtureY.getCall(ids.mCallId).getState());
-
-        mConnectionServiceFixtureA.sendSetDisconnected(ids.mConnectionId, DisconnectCause.LOCAL);
-        assertEquals(Call.STATE_DISCONNECTED,
-                mInCallServiceFixtureX.getCall(ids.mCallId).getState());
-        assertEquals(Call.STATE_DISCONNECTED,
-                mInCallServiceFixtureY.getCall(ids.mCallId).getState());
-    }
-
-    public void testSingleIncomingCallRemoteDisconnect() throws Exception {
-        IdPair ids = startAndMakeActiveIncomingCall(
-                "650-555-1212",
-                mPhoneAccountA0.getAccountHandle(),
-                mConnectionServiceFixtureA);
-
-        mConnectionServiceFixtureA.sendSetDisconnected(ids.mConnectionId, DisconnectCause.LOCAL);
-        assertEquals(Call.STATE_DISCONNECTED,
-                mInCallServiceFixtureX.getCall(ids.mCallId).getState());
-        assertEquals(Call.STATE_DISCONNECTED,
-                mInCallServiceFixtureY.getCall(ids.mCallId).getState());
-    }
-
-    public void do_testDeadlockOnOutgoingCall() throws Exception {
-        final IdPair ids = startOutgoingPhoneCall(
-                "650-555-1212",
-                mPhoneAccountA0.getAccountHandle(),
-                mConnectionServiceFixtureA);
-        rapidFire(
-                new Runnable() {
-                    @Override
-                    public void run() {
-                        while (mCallerInfoAsyncQueryFactoryFixture.mRequests.size() > 0) {
-                            mCallerInfoAsyncQueryFactoryFixture.mRequests.remove(0).reply();
-                        }
-                    }
-                },
-                new Runnable() {
-                    @Override
-                    public void run() {
-                        try {
-                            mConnectionServiceFixtureA.sendSetActive(ids.mConnectionId);
-                        } catch (Exception e) {
-                            Log.e(this, e, "");
-                        }
-                    }
-                });
-    }
-
-    public void testDeadlockOnOutgoingCall() throws Exception {
-        for (int i = 0; i < 100; i++) {
-            TelecomSystemTest test = new TelecomSystemTest();
-            test.setContext(getContext());
-            test.setTestContext(getTestContext());
-            test.setName(getName());
-            test.setUp();
-            test.do_testDeadlockOnOutgoingCall();
-            test.tearDown();
-        }
-    }
-
-    public void testIncomingThenOutgoingCalls() throws Exception {
-        // TODO: We have to use the same PhoneAccount for both; see http://b/18461539
-        IdPair incoming = startAndMakeActiveIncomingCall(
-                "650-555-2323",
-                mPhoneAccountA0.getAccountHandle(),
-                mConnectionServiceFixtureA);
-        IdPair outgoing = startAndMakeActiveOutgoingCall(
-                "650-555-1212",
-                mPhoneAccountA0.getAccountHandle(),
-                mConnectionServiceFixtureA);
-    }
-
-    public void testOutgoingThenIncomingCalls() throws Exception {
-        // TODO: We have to use the same PhoneAccount for both; see http://b/18461539
-        IdPair outgoing = startAndMakeActiveOutgoingCall(
-                "650-555-1212",
-                mPhoneAccountA0.getAccountHandle(),
-                mConnectionServiceFixtureA);
-        IdPair incoming = startAndMakeActiveIncomingCall(
-                "650-555-2323",
-                mPhoneAccountA0.getAccountHandle(),
-                mConnectionServiceFixtureA);
-        verify(mConnectionServiceFixtureA.getTestDouble())
-                .hold(outgoing.mConnectionId);
-        mConnectionServiceFixtureA.mConnectionById.get(outgoing.mConnectionId).state =
-                Connection.STATE_HOLDING;
-        mConnectionServiceFixtureA.sendSetOnHold(outgoing.mConnectionId);
-        assertEquals(
-                Call.STATE_HOLDING,
-                mInCallServiceFixtureX.getCall(outgoing.mCallId).getState());
-        assertEquals(
-                Call.STATE_HOLDING,
-                mInCallServiceFixtureY.getCall(outgoing.mCallId).getState());
-    }
-
-    public void testAudioManagerOperations() throws Exception {
-        AudioManager audioManager = (AudioManager) mComponentContextFixture.getTestDouble()
-                .getApplicationContext().getSystemService(Context.AUDIO_SERVICE);
-
-        IdPair outgoing = startAndMakeActiveOutgoingCall(
-                "650-555-1212",
-                mPhoneAccountA0.getAccountHandle(),
-                mConnectionServiceFixtureA);
-
-        verify(audioManager, timeout(TEST_TIMEOUT))
-                .requestAudioFocusForCall(anyInt(), anyInt());
-        verify(audioManager, timeout(TEST_TIMEOUT).atLeastOnce())
-                .setMode(AudioManager.MODE_IN_CALL);
-
-        mInCallServiceFixtureX.mInCallAdapter.mute(true);
-        verify(audioManager, timeout(TEST_TIMEOUT))
-                .setMicrophoneMute(true);
-        mInCallServiceFixtureX.mInCallAdapter.mute(false);
-        verify(audioManager, timeout(TEST_TIMEOUT))
-                .setMicrophoneMute(false);
-
-        mInCallServiceFixtureX.mInCallAdapter.setAudioRoute(CallAudioState.ROUTE_SPEAKER);
-        verify(audioManager, timeout(TEST_TIMEOUT))
-                .setSpeakerphoneOn(true);
-        mInCallServiceFixtureX.mInCallAdapter.setAudioRoute(CallAudioState.ROUTE_EARPIECE);
-        verify(audioManager, timeout(TEST_TIMEOUT))
-                .setSpeakerphoneOn(false);
-
-        mConnectionServiceFixtureA.
-                sendSetDisconnected(outgoing.mConnectionId, DisconnectCause.REMOTE);
-
-        verify(audioManager, timeout(TEST_TIMEOUT))
-                .abandonAudioFocusForCall();
-        verify(audioManager, timeout(TEST_TIMEOUT).atLeastOnce())
-                .setMode(AudioManager.MODE_NORMAL);
-    }
-
     protected static void assertTrueWithTimeout(Predicate<Void> predicate) {
         int elapsed = 0;
         while (elapsed < 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..ee4b72e 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;
@@ -33,6 +33,7 @@
         Log.setTag(TESTING_TAG);
         mMockitoHelper.setUp(getContext(), getClass());
         mComponentContextFixture = new ComponentContextFixture();
+        Log.setContext(mComponentContextFixture.getTestDouble().getApplicationContext());
         MockitoAnnotations.initMocks(this);
     }
 
diff --git a/tests/src/com/android/server/telecom/tests/VideoCallTests.java b/tests/src/com/android/server/telecom/tests/VideoCallTests.java
new file mode 100644
index 0000000..2a54034
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/VideoCallTests.java
@@ -0,0 +1,140 @@
+/*
+ * 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 org.mockito.ArgumentCaptor;
+
+import android.telecom.CallAudioState;
+import android.telecom.VideoProfile;
+
+import java.util.List;
+
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+/**
+ * System tests for video-specific behavior in telecom.
+ * TODO: Add unit tests which ensure that auto-speakerphone does not occur when using a wired
+ * headset or a bluetooth headset.
+ */
+public class VideoCallTests extends TelecomSystemTest {
+
+    /**
+     * Tests to ensure an incoming video-call is automatically routed to the speakerphone when
+     * the call is answered and neither a wired headset nor bluetooth headset are connected.
+     */
+    public void testAutoSpeakerphoneIncomingBidirectional() throws Exception {
+        // Start an incoming video call.
+        IdPair ids = startAndMakeActiveIncomingCall("650-555-1212",
+                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA,
+                VideoProfile.STATE_BIDIRECTIONAL);
+
+        verifyAudioRoute(CallAudioState.ROUTE_SPEAKER, 2);
+    }
+
+    /**
+     * Tests to ensure an incoming receive-only video-call is answered in speakerphone mode.  Note
+     * that this is not a scenario we would expect normally with the default dialer as it will
+     * always answer incoming video calls as bi-directional.  It is, however, possible for a third
+     * party dialer to answer an incoming video call a a one-way video call.
+     */
+    public void testAutoSpeakerphoneIncomingReceiveOnly() throws Exception {
+        // Start an incoming video call.
+        IdPair ids = startAndMakeActiveIncomingCall("650-555-1212",
+                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA,
+                VideoProfile.STATE_RX_ENABLED);
+
+        verifyAudioRoute(CallAudioState.ROUTE_SPEAKER, 2);
+    }
+
+    /**
+     * Tests audio routing for an outgoing video call made with bidirectional video.  Expect to be
+     * in speaker mode.
+     */
+    public void testAutoSpeakerphoneOutgoingBidirectional() throws Exception {
+        // Start an incoming video call.
+        IdPair ids = startAndMakeActiveOutgoingCall("650-555-1212",
+                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA,
+                VideoProfile.STATE_BIDIRECTIONAL);
+
+        verifyAudioRoute(CallAudioState.ROUTE_SPEAKER, 2);
+    }
+
+    /**
+     * Tests audio routing for an outgoing video call made with transmit only video.  Expect to be
+     * in speaker mode.  Note: The default UI does not support making one-way video calls, but the
+     * APIs do and a third party incall UI could choose to support that.
+     */
+    public void testAutoSpeakerphoneOutgoingTransmitOnly() throws Exception {
+        // Start an incoming video call.
+        IdPair ids = startAndMakeActiveOutgoingCall("650-555-1212",
+                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA,
+                VideoProfile.STATE_TX_ENABLED);
+
+        verifyAudioRoute(CallAudioState.ROUTE_SPEAKER, 2);
+    }
+
+    /**
+     * Tests audio routing for an outgoing video call made with transmit only video.  Expect to be
+     * in speaker mode.  Note: The default UI does not support making one-way video calls, but the
+     * APIs do and a third party incall UI could choose to support that.
+     */
+    public void testNoAutoSpeakerphoneOnOutgoing() throws Exception {
+        // Start an incoming video call.
+        IdPair ids = startAndMakeActiveOutgoingCall("650-555-1212",
+                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA,
+                VideoProfile.STATE_AUDIO_ONLY);
+
+        verifyAudioRoute(CallAudioState.ROUTE_EARPIECE, 1);
+    }
+
+    /**
+     * Tests to ensure an incoming audio-only call is routed to the earpiece.
+     */
+    public void testNoAutoSpeakerphoneOnIncoming() throws Exception {
+
+        // Start an incoming video call.
+        IdPair ids = startAndMakeActiveIncomingCall("650-555-1212",
+                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA,
+                VideoProfile.STATE_AUDIO_ONLY);
+
+        verifyAudioRoute(CallAudioState.ROUTE_EARPIECE, 1);
+    }
+
+    /**
+     * Verifies that the
+     * {@link android.telecom.InCallService#onCallAudioStateChanged(CallAudioState)} change is
+     * called with an expected route and number of changes.
+     *
+     * @param expectedRoute The expected audio route on the latest change.
+     * @param audioStateChangeCount The number of audio state changes expected.  This is set based
+     *                              on how many times we expect the audio route to change when
+     *                              setting up a call.  For an audio-only call, we normally expect
+     *                              1 route change, and for a video call we expect an extra change.
+     */
+    private void verifyAudioRoute(int expectedRoute, int audioStateChangeCount) throws Exception {
+        // Capture all onCallAudioStateChanged callbacks to InCall.
+        ArgumentCaptor<CallAudioState> callAudioStateArgumentCaptor = ArgumentCaptor.forClass(
+                CallAudioState.class);
+        verify(mInCallServiceFixtureX.getTestDouble(),
+                timeout(TEST_TIMEOUT).times(audioStateChangeCount)).
+                onCallAudioStateChanged(callAudioStateArgumentCaptor.capture());
+        List<CallAudioState> changes = callAudioStateArgumentCaptor.getAllValues();
+        assertEquals(expectedRoute, changes.get(changes.size() - 1).getRoute());
+    }
+}
diff --git a/tests/src/com/android/server/telecom/tests/VideoProviderTest.java b/tests/src/com/android/server/telecom/tests/VideoProviderTest.java
new file mode 100644
index 0000000..bc7f9e1
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/VideoProviderTest.java
@@ -0,0 +1,434 @@
+/*
+ * 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 com.android.server.telecom.Log;
+
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.internal.exceptions.ExceptionIncludingMockitoWarnings;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Camera;
+import android.graphics.SurfaceTexture;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.telecom.Call;
+import android.telecom.CallAudioState;
+import android.telecom.Connection;
+import android.telecom.Connection.VideoProvider;
+import android.telecom.InCallService;
+import android.telecom.InCallService.VideoCall;
+import android.telecom.ParcelableCall;
+import android.telecom.TelecomManager;
+import android.telecom.VideoCallImpl;
+import android.telecom.VideoProfile;
+import android.telecom.VideoProfile.CameraCapabilities;
+import android.view.Surface;
+
+import com.google.common.base.Predicate;
+
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.RunnableFuture;
+import java.util.concurrent.TimeUnit;
+
+import static android.test.MoreAsserts.assertEquals;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * Performs tests of the {@link VideoProvider} and {@link VideoCall} APIs.  Ensures that requests
+ * sent from an InCallService are routed through Telecom to a VideoProvider, and that callbacks are
+ * correctly routed.
+ */
+public class VideoProviderTest extends TelecomSystemTest {
+    private static final int ORIENTATION_0 = 0;
+    private static final int ORIENTATION_90 = 90;
+    private static final float ZOOM_LEVEL = 3.0f;
+
+    @Mock private VideoCall.Callback mVideoCallCallback;
+    private IdPair mCallIds;
+    private InCallService.VideoCall mVideoCall;
+    private VideoCallImpl mVideoCallImpl;
+    private ConnectionServiceFixture.ConnectionInfo mConnectionInfo;
+    private CountDownLatch mVerificationLock;
+
+    private Answer mVerification = new Answer() {
+        @Override
+        public Object answer(InvocationOnMock i) {
+            mVerificationLock.countDown();
+            return null;
+        }
+    };
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        mCallIds = startAndMakeActiveOutgoingCall(
+                "650-555-1212",
+                mPhoneAccountA0.getAccountHandle(),
+                mConnectionServiceFixtureA);
+
+        // Set the video provider on the connection.
+        mConnectionServiceFixtureA.sendSetVideoProvider(
+                mConnectionServiceFixtureA.mLatestConnectionId);
+
+        // Provide a mocked VideoCall.Callback to receive callbacks via.
+        mVideoCallCallback = mock(InCallService.VideoCall.Callback.class);
+
+        mVideoCall = mInCallServiceFixtureX.getCall(mCallIds.mCallId).getVideoCallImpl();
+        mVideoCallImpl = (VideoCallImpl) mVideoCall;
+        mVideoCall.registerCallback(mVideoCallCallback);
+
+        mConnectionInfo = mConnectionServiceFixtureA.mConnectionById.get(mCallIds.mConnectionId);
+        mVerificationLock = new CountDownLatch(1);
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    /**
+     * Tests the {@link VideoCall#setCamera(String)}, {@link VideoProvider#onSetCamera(String)},
+     * and {@link VideoCall.Callback#onCameraCapabilitiesChanged(CameraCapabilities)}
+     * APIS.
+     */
+    public void testCameraChange() throws Exception {
+        // Wait until the callback has been received before performing verification.
+        doAnswer(mVerification).when(mVideoCallCallback)
+                .onCameraCapabilitiesChanged(any(CameraCapabilities.class));
+
+        // Make 2 setCamera requests.
+        mVideoCall.setCamera(MockVideoProvider.CAMERA_FRONT);
+        mVideoCall.setCamera(MockVideoProvider.CAMERA_BACK);
+
+        mVerificationLock.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS);
+
+        // Capture the video profile reported via the callback.
+        ArgumentCaptor<CameraCapabilities> cameraCapabilitiesCaptor =
+                ArgumentCaptor.forClass(CameraCapabilities.class);
+
+        // Verify that the callback was called twice and capture the callback arguments.
+        verify(mVideoCallCallback, timeout(TEST_TIMEOUT).times(2))
+                .onCameraCapabilitiesChanged(cameraCapabilitiesCaptor.capture());
+
+        assertEquals(2, cameraCapabilitiesCaptor.getAllValues().size());
+
+        List<CameraCapabilities> cameraCapabilities = cameraCapabilitiesCaptor.getAllValues();
+        // Ensure dimensions are as expected.
+        assertEquals(MockVideoProvider.CAMERA_FRONT_DIMENSIONS,
+                cameraCapabilities.get(0).getHeight());
+        assertEquals(MockVideoProvider.CAMERA_BACK_DIMENSIONS,
+                cameraCapabilities.get(1).getHeight());
+    }
+
+    /**
+     * Tests the {@link VideoCall#setPreviewSurface(Surface)} and
+     * {@link VideoProvider#onSetPreviewSurface(Surface)} APIs.
+     */
+    public void testSetPreviewSurface() throws Exception {
+        final Surface surface = new Surface(new SurfaceTexture(1));
+        mVideoCall.setPreviewSurface(surface);
+
+        assertTrueWithTimeout(new Predicate<Void>() {
+            @Override
+            public boolean apply(Void v) {
+                return mConnectionInfo.mockVideoProvider.getPreviewSurface() == surface;
+            }
+        });
+
+        mVideoCall.setPreviewSurface(null);
+
+        assertTrueWithTimeout(new Predicate<Void>() {
+            @Override
+            public boolean apply(Void v) {
+                return mConnectionInfo.mockVideoProvider.getPreviewSurface() == null;
+            }
+        });
+    }
+
+    /**
+     * Tests the {@link VideoCall#setDisplaySurface(Surface)} and
+     * {@link VideoProvider#onSetDisplaySurface(Surface)} APIs.
+     */
+    public void testSetDisplaySurface() throws Exception {
+        final Surface surface = new Surface(new SurfaceTexture(1));
+        mVideoCall.setDisplaySurface(surface);
+
+        assertTrueWithTimeout(new Predicate<Void>() {
+            @Override
+            public boolean apply(Void v) {
+                return mConnectionInfo.mockVideoProvider.getDisplaySurface() == surface;
+            }
+        });
+
+        mVideoCall.setDisplaySurface(null);
+
+        assertTrueWithTimeout(new Predicate<Void>() {
+            @Override
+            public boolean apply(Void v) {
+                return mConnectionInfo.mockVideoProvider.getDisplaySurface() == null;
+            }
+        });
+    }
+
+    /**
+     * Tests the {@link VideoCall#setDeviceOrientation(int)} and
+     * {@link VideoProvider#onSetDeviceOrientation(int)} APIs.
+     */
+    public void testSetDeviceOrientation() throws Exception {
+        mVideoCall.setDeviceOrientation(ORIENTATION_0);
+
+        assertTrueWithTimeout(new Predicate<Void>() {
+            @Override
+            public boolean apply(Void v) {
+                return mConnectionInfo.mockVideoProvider.getDeviceOrientation() == ORIENTATION_0;
+            }
+        });
+
+        mVideoCall.setDeviceOrientation(ORIENTATION_90);
+
+        assertTrueWithTimeout(new Predicate<Void>() {
+            @Override
+            public boolean apply(Void v) {
+                return mConnectionInfo.mockVideoProvider.getDeviceOrientation() == ORIENTATION_90;
+            }
+        });
+    }
+
+    /**
+     * Tests the {@link VideoCall#setZoom(float)} and {@link VideoProvider#onSetZoom(float)} APIs.
+     */
+    public void testSetZoom() throws Exception {
+        mVideoCall.setZoom(ZOOM_LEVEL);
+
+        assertTrueWithTimeout(new Predicate<Void>() {
+            @Override
+            public boolean apply(Void v) {
+                return mConnectionInfo.mockVideoProvider.getZoom() == ZOOM_LEVEL;
+            }
+        });
+    }
+
+    /**
+     * Tests the {@link VideoCall#sendSessionModifyRequest(VideoProfile)},
+     * {@link VideoProvider#onSendSessionModifyRequest(VideoProfile, VideoProfile)},
+     * {@link VideoProvider#receiveSessionModifyResponse(int, VideoProfile, VideoProfile)}, and
+     * {@link VideoCall.Callback#onSessionModifyResponseReceived(int, VideoProfile, VideoProfile)}
+     * APIs.
+     *
+     * Emulates a scenario where an InCallService sends a request to upgrade to video, which the
+     * peer accepts as-is.
+     */
+    public void testSessionModifyRequest() throws Exception {
+        VideoProfile requestProfile = new VideoProfile(VideoProfile.STATE_BIDIRECTIONAL);
+
+        // Set the starting video state on the video call impl; normally this would be set based on
+        // the original android.telecom.Call instance.
+        mVideoCallImpl.setVideoState(VideoProfile.STATE_RX_ENABLED);
+
+        doAnswer(mVerification).when(mVideoCallCallback)
+                .onSessionModifyResponseReceived(anyInt(), any(VideoProfile.class),
+                        any(VideoProfile.class));
+
+        // Send the request.
+        mVideoCall.sendSessionModifyRequest(requestProfile);
+
+        mVerificationLock.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS);
+
+        // Capture the video profiles from the callback.
+        ArgumentCaptor<VideoProfile> fromVideoProfileCaptor =
+                ArgumentCaptor.forClass(VideoProfile.class);
+        ArgumentCaptor<VideoProfile> toVideoProfileCaptor =
+                ArgumentCaptor.forClass(VideoProfile.class);
+
+        // Verify we got a response and capture the profiles.
+        verify(mVideoCallCallback, timeout(TEST_TIMEOUT))
+                .onSessionModifyResponseReceived(eq(VideoProvider.SESSION_MODIFY_REQUEST_SUCCESS),
+                        fromVideoProfileCaptor.capture(), toVideoProfileCaptor.capture());
+
+        assertEquals(VideoProfile.STATE_RX_ENABLED,
+                fromVideoProfileCaptor.getValue().getVideoState());
+        assertEquals(VideoProfile.STATE_BIDIRECTIONAL,
+                toVideoProfileCaptor.getValue().getVideoState());
+    }
+
+    /**
+     * Tests the {@link VideoCall#sendSessionModifyResponse(VideoProfile)},
+     * and {@link VideoProvider#onSendSessionModifyResponse(VideoProfile)} APIs.
+     */
+    public void testSessionModifyResponse() throws Exception {
+        VideoProfile sessionModifyResponse = new VideoProfile(VideoProfile.STATE_TX_ENABLED);
+
+        mVideoCall.sendSessionModifyResponse(sessionModifyResponse);
+
+        assertTrueWithTimeout(new Predicate<Void>() {
+            @Override
+            public boolean apply(Void v) {
+                VideoProfile response = mConnectionInfo.mockVideoProvider
+                        .getSessionModifyResponse();
+                return response != null && response.getVideoState() == VideoProfile.STATE_TX_ENABLED;
+            }
+        });
+    }
+
+    /**
+     * Tests the {@link VideoCall#requestCameraCapabilities()} ()},
+     * {@link VideoProvider#onRequestCameraCapabilities()} ()}, and
+     * {@link VideoCall.Callback#onCameraCapabilitiesChanged(CameraCapabilities)} APIs.
+     */
+    public void testRequestCameraCapabilities() throws Exception {
+        // Wait until the callback has been received before performing verification.
+        doAnswer(mVerification).when(mVideoCallCallback)
+                .onCameraCapabilitiesChanged(any(CameraCapabilities.class));
+
+        mVideoCall.requestCameraCapabilities();
+
+        mVerificationLock.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS);
+
+        verify(mVideoCallCallback, timeout(TEST_TIMEOUT))
+                .onCameraCapabilitiesChanged(any(CameraCapabilities.class));
+    }
+
+    /**
+     * Tests the {@link VideoCall#setPauseImage(Uri)}, and
+     * {@link VideoProvider#onSetPauseImage(Uri)} APIs.
+     */
+    public void testSetPauseImage() throws Exception {
+        final Uri testUri = Uri.fromParts("file", "test.jpg", null);
+        mVideoCall.setPauseImage(testUri);
+
+        assertTrueWithTimeout(new Predicate<Void>() {
+            @Override
+            public boolean apply(Void v) {
+                Uri pauseImage = mConnectionInfo.mockVideoProvider.getPauseImage();
+                return pauseImage != null && pauseImage.equals(testUri);
+            }
+        });
+    }
+
+    /**
+     * Tests the {@link VideoCall#requestCallDataUsage()},
+     * {@link VideoProvider#onRequestConnectionDataUsage()}, and
+     * {@link VideoCall.Callback#onCallDataUsageChanged(long)} APIs.
+     */
+    public void testRequestDataUsage() throws Exception {
+        // Wait until the callback has been received before performing verification.
+        doAnswer(mVerification).when(mVideoCallCallback)
+                .onCallDataUsageChanged(anyLong());
+
+        mVideoCall.requestCallDataUsage();
+
+        mVerificationLock.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS);
+
+        verify(mVideoCallCallback, timeout(TEST_TIMEOUT))
+                .onCallDataUsageChanged(eq(MockVideoProvider.DATA_USAGE));
+    }
+
+    /**
+     * Tests the {@link VideoProvider#receiveSessionModifyRequest(VideoProfile)},
+     * {@link VideoCall.Callback#onSessionModifyRequestReceived(VideoProfile)} APIs.
+     */
+    public void testReceiveSessionModifyRequest() throws Exception {
+        // Wait until the callback has been received before performing verification.
+        doAnswer(mVerification).when(mVideoCallCallback)
+                .onSessionModifyRequestReceived(any(VideoProfile.class));
+
+        mConnectionInfo.mockVideoProvider.sendMockSessionModifyRequest();
+
+        mVerificationLock.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS);
+
+        ArgumentCaptor<VideoProfile> requestProfileCaptor =
+                ArgumentCaptor.forClass(VideoProfile.class);
+        verify(mVideoCallCallback, timeout(TEST_TIMEOUT))
+                .onSessionModifyRequestReceived(requestProfileCaptor.capture());
+        assertEquals(VideoProfile.STATE_BIDIRECTIONAL,
+                requestProfileCaptor.getValue().getVideoState());
+    }
+
+
+    /**
+     * Tests the {@link VideoProvider#handleCallSessionEvent(int)}, and
+     * {@link VideoCall.Callback#onCallSessionEvent(int)} APIs.
+     */
+    public void testSessionEvent() throws Exception {
+        // Wait until the callback has been received before performing verification.
+        doAnswer(mVerification).when(mVideoCallCallback)
+                .onCallSessionEvent(anyInt());
+
+        mConnectionInfo.mockVideoProvider.sendMockSessionEvent(
+                VideoProvider.SESSION_EVENT_CAMERA_READY);
+
+        mVerificationLock.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS);
+
+        verify(mVideoCallCallback, timeout(TEST_TIMEOUT))
+                .onCallSessionEvent(eq(VideoProvider.SESSION_EVENT_CAMERA_READY));
+    }
+
+    /**
+     * Tests the {@link VideoProvider#changePeerDimensions(int, int)} and
+     * {@link VideoCall.Callback#onPeerDimensionsChanged(int, int)} APIs.
+     */
+    public void testPeerDimensionChange() throws Exception {
+        // Wait until the callback has been received before performing verification.
+        doAnswer(mVerification).when(mVideoCallCallback)
+                .onPeerDimensionsChanged(anyInt(), anyInt());
+
+        mConnectionInfo.mockVideoProvider.sendMockPeerDimensions(MockVideoProvider.PEER_DIMENSIONS,
+                MockVideoProvider.PEER_DIMENSIONS);
+
+        mVerificationLock.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS);
+
+        verify(mVideoCallCallback, timeout(TEST_TIMEOUT))
+                .onPeerDimensionsChanged(eq(MockVideoProvider.PEER_DIMENSIONS),
+                        eq(MockVideoProvider.PEER_DIMENSIONS));
+    }
+
+    /**
+     * Tests the {@link VideoProvider#changeVideoQuality(int)} and
+     * {@link VideoCall.Callback#onVideoQualityChanged(int)} APIs.
+     */
+    public void testVideoQualityChange() throws Exception {
+        // Wait until the callback has been received before performing verification.
+        doAnswer(mVerification).when(mVideoCallCallback)
+                .onVideoQualityChanged(anyInt());
+
+        mConnectionInfo.mockVideoProvider.sendMockVideoQuality(VideoProfile.QUALITY_HIGH);
+
+        mVerificationLock.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS);
+
+        verify(mVideoCallCallback, timeout(TEST_TIMEOUT))
+                .onVideoQualityChanged(eq(VideoProfile.QUALITY_HIGH));
+    }
+}