DO NOT MERGE - Restrict ability to add call based on device provision status am: 0044d82fde am: b4b8bf90dc  -s ours am: daaa056f33 am: 04b643394e am: aef3fca707  -s ours am: 4826b959b7 am: ab77750fb7 am: 344722dfb5
am: 192ea74d68  -s ours

* commit '192ea74d6818b7081cf4750b3ed1232e034f932e':
  DO NOT MERGE - Restrict ability to add call based on device provision status
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 56a594b..86f6cdc 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
@@ -42,6 +41,8 @@
     <uses-permission android:name="android.permission.STOP_APP_SWITCHES" />
     <uses-permission android:name="android.permission.VIBRATE" />
     <uses-permission android:name="android.permission.WRITE_CALL_LOG" />
+    <uses-permission android:name="android.permission.READ_BLOCKED_NUMBERS" />
+    <uses-permission android:name="android.permission.WRITE_BLOCKED_NUMBERS" />
 
     <permission
             android:name="android.permission.BROADCAST_CALLLOG_INFO"
@@ -63,16 +64,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
@@ -86,6 +85,13 @@
              3) CALL_EMERGENCY - Expected from the emergency dialer app and requires CALL_PRIVILEGED
              permission. Through this intent, an app can call *only* emergency numbers. -->
 
+        <!-- Activity that displays UI for managing blocked numbers. -->
+        <activity android:name=".settings.BlockedNumbersActivity"
+                  android:label="@string/blocked_numbers"
+                  android:configChanges="orientation|screenSize|keyboardHidden"
+                  android:theme="@style/Theme.Telecom.BlockedNumbers"
+                  android:process=":ui"
+                  android:exported="false" />
         <!-- Activity that starts the outgoing call process by listening to CALL intent which
              contain contact information in the intent's data. CallActivity handles any data
              URL with the schemes "tel", "sip", and "voicemail". It also handles URLs linked to
diff --git a/res/drawable-hdpi/ic_close_grey_24dp.png b/res/drawable-hdpi/ic_close_grey_24dp.png
new file mode 100644
index 0000000..9efc2b6
--- /dev/null
+++ b/res/drawable-hdpi/ic_close_grey_24dp.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_close_grey_24dp.png b/res/drawable-mdpi/ic_close_grey_24dp.png
new file mode 100644
index 0000000..6fef704
--- /dev/null
+++ b/res/drawable-mdpi/ic_close_grey_24dp.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_close_grey_24dp.png b/res/drawable-xhdpi/ic_close_grey_24dp.png
new file mode 100644
index 0000000..cb03036
--- /dev/null
+++ b/res/drawable-xhdpi/ic_close_grey_24dp.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_close_grey_24dp.png b/res/drawable-xxhdpi/ic_close_grey_24dp.png
new file mode 100644
index 0000000..8912da1
--- /dev/null
+++ b/res/drawable-xxhdpi/ic_close_grey_24dp.png
Binary files differ
diff --git a/res/drawable-xxxhdpi/ic_close_grey_24dp.png b/res/drawable-xxxhdpi/ic_close_grey_24dp.png
new file mode 100644
index 0000000..0a48d99
--- /dev/null
+++ b/res/drawable-xxxhdpi/ic_close_grey_24dp.png
Binary files differ
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index c7eaf22..b86e6b3 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"Geblokkeerde nommers"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"Oproepe en SMS\'e vanaf nommers op hierdie lys word geblokkeer."</string>
+    <string name="block_number" msgid="1101252256321306179">"Voeg \'n nommer by"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"Deblokkeer <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g>?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"Deblokkeer"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"Blokkeer oproepe en SMS\'e vanaf"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"Nommer of e-posadres"</string>
+    <string name="block_button" msgid="8822290682524373357">"Blokkeer"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"Net die toesteleienaar kan geblokkeerde nommers bekyk en bestuur."</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"Oortjie om te deblokkeer"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"Gebruik tans die persoonlike beller om die oproep te maak"</string>
 </resources>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index d15621d..5ef7e9e 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"የታገዱ ቁጥሮች"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"በዚህ ዝርዝር ላይ ካሉ ቁጥሮች የሚመጡ ጥሪዎች እና ጽሑፎች ታግደዋል።"</string>
+    <string name="block_number" msgid="1101252256321306179">"ቁጥር ያክሉ"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"የ<xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g> እገዳ ይነሳ?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"እገዳውን አንሳ"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"ከሚከተለው የሚመጡ ጥሪዎችን እና ጽሑፎችን አግድ፦"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"ቁጥር ወይም ኢሜይል"</string>
+    <string name="block_button" msgid="8822290682524373357">"አግድ"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"የመሣሪያው ባለቤት ብቻ ነው የታገዱ ቁጥሮችን ማየት እና ማስተዳደር የሚችለው።"</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"እንዳይታገድ ለማድረግ መታ ያድርጉ"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"ጥሪውን ለማድረግ የግል መደወያውን መጠቀም"</string>
 </resources>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index ae8b985..5ea4355 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"الأرقام المحظورة"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"تم حظر المكالمات والرسائل النصية من الأرقام على هذه القائمة."</string>
+    <string name="block_number" msgid="1101252256321306179">"إضافة رقم"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"هل تريد إلغاء حظر <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g>؟"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"إلغاء الحظر"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"حظر المكالمات والرسائل النصية من"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"الرقم أو البريد الإلكتروني"</string>
+    <string name="block_button" msgid="8822290682524373357">"حظر"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"يمكن لمالك الجهاز فقط الاطلاع على الأرقام المحظورة وإدارتها."</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"انقر لإلغاء الحظر"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"يتم استخدام أداة الاتصال الشخصي لإجراء الاتصال"</string>
 </resources>
diff --git a/res/values-az-rAZ/strings.xml b/res/values-az-rAZ/strings.xml
index 2082e05..708ca5c 100644
--- a/res/values-az-rAZ/strings.xml
+++ b/res/values-az-rAZ/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Bu proqram Telefon icazəsi olmadan zəng edə bilməz."</string>
     <string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"Zəngi yerləşdirmək üçün düzgün nömrə daxil edin."</string>
     <string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"Hazırda çağrı edilə bilməz."</string>
-    <string name="video_call_not_allowed_if_tty_enabled" msgid="7593649283571253283">"Video zəng etmək üçün TTY Rejimini deaktiv edin."</string>
     <string name="no_vm_number" msgid="4164780423805688336">"Səsli poçt nömrəsi çatışmır"</string>
     <string name="no_vm_number_msg" msgid="1300729501030053828">"SIM kartda heç bir səsli poçt nömrəsi yoxdur."</string>
     <string name="add_vm_number_str" msgid="4676479471644687453">"Nömrə əlavə edin"</string>
     <string name="change_default_dialer_dialog_title" msgid="4430590714918044425">"Defolt nömrə yığan tətbiqi dəyişdirilsin?"</string>
     <string name="change_default_dialer_with_previous_app_set_text" msgid="3213396537499337949">"Defolt nömrə yığan tətbiqi kimi <xliff:g id="CURRENT_APP">%2$s</xliff:g> əvəzinə <xliff:g id="NEW_APP">%1$s</xliff:g> işlədilsin?"</string>
     <string name="change_default_dialer_no_previous_app_set_text" msgid="7608426684114545221">"Defolt nömrə yığan tətbiqi kimi <xliff:g id="NEW_APP">%s</xliff:g> işlədilsin?"</string>
+    <string name="blocked_numbers" msgid="2751843139572970579">"Bloklanmış nömrələr"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"Bu siyahıdakı nömrələrdən gələn zənglər və mətnlər blok edilib."</string>
+    <string name="block_number" msgid="1101252256321306179">"Nömrə əlavə edin"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"<xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g> nömrəsi blokdan çıxarılsın?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"Blokdan çıxar"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"Zəngləri və mətnləri buradan blok edin"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"Nömrə və ya e-poçt"</string>
+    <string name="block_button" msgid="8822290682524373357">"Blok edin"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"Yalnız cihaz sahibi blok edilmiş nömrələrə baxa və idarə edə bilər."</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"Kiliddən çıxarmaq üçün basın"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"Zəng etmək üçün şəxsi nömrə yığımı istifadə olunur"</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..77793b0
--- /dev/null
+++ b/res/values-b+sr+Latn/strings.xml
@@ -0,0 +1,62 @@
+<?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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"Blokirani brojevi"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"Pozivi i SMS-ovi sa brojeva na ovoj listi su blokirani."</string>
+    <string name="block_number" msgid="1101252256321306179">"Dodaj broj"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"Želite li da deblokirate <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g>?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"Deblokiraj"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"Blokirajte pozive i SMS-ove od"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"Broj ili imejl"</string>
+    <string name="block_button" msgid="8822290682524373357">"Blokiraj"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"Samo vlasnik uređaja može da pregleda blokirane brojeve i upravlja njima."</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"Dodirnite da biste deblokirali"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"Korišćenje ličnog brojčanika za upućivanje poziva"</string>
+</resources>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index 49005ff..ff59be9 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"Блокирани номера"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"Обажданията и текстовите съобщения от номерата в този списък са блокирани."</string>
+    <string name="block_number" msgid="1101252256321306179">"Добавяне на номер"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"Искате ли да отблокирате <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g>?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"Отблокиране"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"Блокиране на обажданията и текстовите съобщения от"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"Номер или имейл адрес"</string>
+    <string name="block_button" msgid="8822290682524373357">"Блокиране"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"Само собственикът на устройството може да преглежда и управлява блокираните номера."</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"Раздел за отблокиране"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"За извършване на обаждането се използва личната клавиатура за набиране"</string>
 </resources>
diff --git a/res/values-bn-rBD/strings.xml b/res/values-bn-rBD/strings.xml
index 384aa35..a4038dd 100644
--- a/res/values-bn-rBD/strings.xml
+++ b/res/values-bn-rBD/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"অবরুদ্ধ নম্বরগুলি"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"এই তালিকার নম্বরগুলি থেকে আসা কল এবং পাঠ্যগুলিকে অবরুদ্ধ করা হয়েছে৷"</string>
+    <string name="block_number" msgid="1101252256321306179">"একটি নম্বর যোগ করুন"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"<xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g> অবরোধ মুক্ত করবেন?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"অবরোধ মুক্ত করুন"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"এর থেকে কল এবং পাঠ্যগুলিকে অবরোধ করুন"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"নম্বর বা ইমেল"</string>
+    <string name="block_button" msgid="8822290682524373357">"অবরোধ করুন"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"শুধুমাত্র ডিভাইসের মালিক এই অবরুদ্ধ নম্বরগুলিকে দেখতে এবং পরিচালনা করতে পারেন৷"</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"অবরোধ মুক্ত করতে আলতো চাপ দিন"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"কল করার জন্য ব্যক্তিগত ডায়ালার ব্যবহার করা হচ্ছে"</string>
 </resources>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index f5a570f..912b0c0 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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 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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"Números bloquejats"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"Les trucades i els missatges de text dels números que hi ha en aquesta llista estan bloquejats."</string>
+    <string name="block_number" msgid="1101252256321306179">"Afegeix un número"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"Vols desbloquejar el número <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g>?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"Desbloqueja"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"Bloqueja les trucades i els missatges de text de"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"Número o adreça electrònica"</string>
+    <string name="block_button" msgid="8822290682524373357">"Bloqueja"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"Només el propietari del dispositiu pot consultar i gestionar els números bloquejats."</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"Toca per desbloquejar-lo"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"S\'està utilitzant el telèfon personal per fer la trucada"</string>
 </resources>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index 4a5c9e0..2c9ff6c 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"Blokovaná čísla"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"Volání a zprávy SMS z čísel v tomto seznamu budou blokovány."</string>
+    <string name="block_number" msgid="1101252256321306179">"Přidat číslo"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"Odblokovat číslo <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g>?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"Odblokovat"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"Blokovat hovory a zprávy SMS od odesílatele"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"Číslo nebo e-mail"</string>
+    <string name="block_button" msgid="8822290682524373357">"Blokovat"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"Prohlížet a spravovat blokovaná čísla může pouze vlastník zařízení."</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"Klepnutím číslo odblokujete"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"Volání se provádí pomocí osobního vytáčení"</string>
 </resources>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index 787cfe0..c978fd1 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"Blokerede telefonnumre"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"Opkald og sms-beskeder fra numre på denne liste er blokeret."</string>
+    <string name="block_number" msgid="1101252256321306179">"Tilføj et nummer"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"Vil du ophæve blokeringen af <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g>?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"Ophæv blokeringen"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"Bloker opkald og sms-beskeder fra"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"Telefonummer eller e-mailadresse"</string>
+    <string name="block_button" msgid="8822290682524373357">"Bloker"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"Det er kun ejeren af en enhed, der kan se og administrere blokerede numre."</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"Tryk for at ophæve blokeringen"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"Opkaldet foretages med det personlige opkaldsprogram"</string>
 </resources>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index 0bb61ac..1135652 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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 deaktiviere 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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"Blockierte Nummern"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"Anrufe und SMS von Nummern in dieser Liste werden blockiert."</string>
+    <string name="block_number" msgid="1101252256321306179">"Nummer hinzufügen"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"Blockierung von <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g> aufheben?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"Blockierung aufheben"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"Anrufe und SMS blockieren von"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"Telefonnummer oder E-Mail-Adresse"</string>
+    <string name="block_button" msgid="8822290682524373357">"Blockieren"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"Nur der Geräteeigentümer kann blockierte Nummern sehen und verwalten."</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"Zum Aufheben der Blockierung tippen"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"Zum Anrufen wird das eigene Telefon genutzt"</string>
 </resources>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index ec44361..6322d1a 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"Αποκλεισμένοι αριθμοί"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"Έχουν αποκλειστεί οι κλήσεις και τα μηνύματα που προέρχονται από τους αριθμούς που περιλαμβάνονται σε αυτήν τη λίστα."</string>
+    <string name="block_number" msgid="1101252256321306179">"Προσθήκη αριθμού"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"Να καταργηθεί ο αποκλεισμός του αριθμού <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g>;"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"Κατάργηση αποκλεισμού"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"Αποκλεισμός κλήσεων και μηνυμάτων από"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"Αριθμός ή διεύθυνση ηλεκτρονικού ταχυδρομείου"</string>
+    <string name="block_button" msgid="8822290682524373357">"Αποκλεισμός"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"Μόνο ο κάτοχος της συσκευής μπορεί να προβάλλει και να διαχειρίζεται αποκλεισμένους αριθμούς."</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"Πατήστε για να καταργήσετε τον αποκλεισμό"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"Χρήση του προσωπικού σας προγράμματος κλήσης για την πραγματοποίηση της κλήσης"</string>
 </resources>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index 78e374e..0c51caf 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"Blocked numbers"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"Calls and texts from numbers on this list are blocked."</string>
+    <string name="block_number" msgid="1101252256321306179">"Add a number"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"Unblock <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g>?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"Unblock"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"Block calls and texts from"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"Number or email"</string>
+    <string name="block_button" msgid="8822290682524373357">"Block"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"Only the device owner can view and manage blocked numbers."</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"Tab to unblock"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"Using the personal dialler to make the call"</string>
 </resources>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index 78e374e..0c51caf 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"Blocked numbers"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"Calls and texts from numbers on this list are blocked."</string>
+    <string name="block_number" msgid="1101252256321306179">"Add a number"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"Unblock <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g>?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"Unblock"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"Block calls and texts from"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"Number or email"</string>
+    <string name="block_button" msgid="8822290682524373357">"Block"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"Only the device owner can view and manage blocked numbers."</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"Tab to unblock"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"Using the personal dialler to make the call"</string>
 </resources>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index 78e374e..0c51caf 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"Blocked numbers"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"Calls and texts from numbers on this list are blocked."</string>
+    <string name="block_number" msgid="1101252256321306179">"Add a number"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"Unblock <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g>?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"Unblock"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"Block calls and texts from"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"Number or email"</string>
+    <string name="block_button" msgid="8822290682524373357">"Block"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"Only the device owner can view and manage blocked numbers."</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"Tab to unblock"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"Using the personal dialler to make the call"</string>
 </resources>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index 0ad7c1b..77986d6 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"Números bloqueados"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"Las llamadas y los mensajes de texto de los números en esta lista están bloqueados."</string>
+    <string name="block_number" msgid="1101252256321306179">"Agregar un número"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"¿Quieres desbloquear <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g>?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"Desbloquear"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"Bloquear llamadas y mensajes de texto de"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"Número o correo electrónico"</string>
+    <string name="block_button" msgid="8822290682524373357">"Bloquear"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"Solo el propietario del dispositivo puede ver y administrar los números bloqueados."</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"Presionar para desbloquear"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"Usando el teléfono personal para realizar la llamada"</string>
 </resources>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index 1fb18a6..c8a66a8 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"Números bloqueados"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"Las llamadas y los mensajes de texto de los números de esta lista están bloqueados."</string>
+    <string name="block_number" msgid="1101252256321306179">"Añadir un número"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"¿Desbloquear el número <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g>?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"Desbloquear"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"Bloquear llamadas y mensajes de texto del número"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"Número o correo electrónico"</string>
+    <string name="block_button" msgid="8822290682524373357">"Bloquear"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"Solo el propietario del dispositivo puede ver y administrar los números bloqueados."</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"Toca para desbloquear"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"Utilizando teléfono personal para llamar"</string>
 </resources>
diff --git a/res/values-et-rEE/strings.xml b/res/values-et-rEE/strings.xml
index 04206c7..46d5af1 100644
--- a/res/values-et-rEE/strings.xml
+++ b/res/values-et-rEE/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"Blokeeritud numbrid"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"Kõned ja tekstsõnumid selles loendis olevatelt numbritelt on blokeeritud."</string>
+    <string name="block_number" msgid="1101252256321306179">"Lisa number"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"Kas deblokeerida number <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g>?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"Deblokeeri"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"Blokeeri kõned ja tekstsõnumid numbrilt"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"Number või e-posti aadress"</string>
+    <string name="block_button" msgid="8822290682524373357">"Blokeeri"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"Ainult seadme omanik saab blokeeritud numbreid vaadata ja hallata."</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"Puudutage deblokeerimiseks"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"Helistamiseks kasutatakse isiklikku helistamisprogrammi"</string>
 </resources>
diff --git a/res/values-eu-rES/strings.xml b/res/values-eu-rES/strings.xml
index 88f8a8e..616ed1d 100644
--- a/res/values-eu-rES/strings.xml
+++ b/res/values-eu-rES/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"Blokeatutako zenbakiak"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"Blokeatuta daude zerrenda honetako zenbakietatik jasotzen diren deiak eta testu-mezuak."</string>
+    <string name="block_number" msgid="1101252256321306179">"Gehitu zenbakia"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"Desblokeatu egin nahi duzu <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g>?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"Desblokeatu"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"Blokeatu zenbaki honetatik jasotzen diren deiak eta testu-mezuak:"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"Zenbakia edo helbide elektronikoa"</string>
+    <string name="block_button" msgid="8822290682524373357">"Blokeatu"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"Gailuaren jabeak soilik ikus eta kudea ditzake blokeatutako zenbakiak."</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"Sakatu desblokeatzeko"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"Telefono pertsonala erabiltzen ari zara deia egiteko"</string>
 </resources>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index fa8028c..2eb1903 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"شماره‌های مسدود‌شده"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"تماس‌ها و پیامک‌های دریافتی از شماره‌های این فهرست مسدود شده‌اند."</string>
+    <string name="block_number" msgid="1101252256321306179">"افزودن شماره"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"<xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g> گشوده شود؟"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"گشودن"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"مسدود کردن تماس و پیامک از"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"شماره تلفن یا رایانامه"</string>
+    <string name="block_button" msgid="8822290682524373357">"مسدود کردن"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"فقط مالک دستگاه می‌تواند شماره‌های مسدودشده را مدیریت کند."</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"ضربه برای گشودن"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"استفاده از شماره‌گیر شخصی برای گرفتن تماس"</string>
 </resources>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index 2f73a26..1f7e161 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"Estetyt numerot"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"Tämän luettelon numeroista saapuvat puhelut ja tekstiviestit estetään."</string>
+    <string name="block_number" msgid="1101252256321306179">"Lisää numero"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"Kumotaanko numeron <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g> esto?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"Kumoa esto"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"Estä puhelut ja tekstiviestit numerosta"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"Numero tai sähköpostiosoite"</string>
+    <string name="block_button" msgid="8822290682524373357">"Estä"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"Vain laitteen omistaja voi katsella ja hallinnoida estettyjä numeroita."</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"Kumoa esto koskettamalla."</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"Puhelun soittaminen henkilökohtaisella numerovalitsimella"</string>
 </resources>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index 4bf6b52..30b83e6 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"Numéros bloqués"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"Les appels et les textos provenant des numéros dans cette liste seront bloqués."</string>
+    <string name="block_number" msgid="1101252256321306179">"Ajouter un numéro"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"Débloquer <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g>?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"Débloquer"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"Bloquer les appels et lers textos de"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"Numéro ou adresse de courriel"</string>
+    <string name="block_button" msgid="8822290682524373357">"Bloquer"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"Seul le propriétaire de l\'appareil peut afficher et gérer les numéros bloqués."</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"Touchez ici pour débloquer"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"Utilisation du clavier personnel pour faire l\'appel…"</string>
 </resources>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index 247b528..7de4c55 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"Numéros bloqués"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"Les appels et les SMS de numéros figurant sur cette liste seront bloqués."</string>
+    <string name="block_number" msgid="1101252256321306179">"Ajouter un numéro"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"Débloquer <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g> ?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"Débloquer"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"Bloquer les appels et les SMS de"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"Numéro ou adresse e-mail"</string>
+    <string name="block_button" msgid="8822290682524373357">"Bloquer"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"Seul le propriétaire de l\'appareil peut afficher et gérer les numéros bloqués."</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"Appuyer pour débloquer"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"Utilisation du clavier personnel pour passer l\'appel…"</string>
 </resources>
diff --git a/res/values-gl-rES/strings.xml b/res/values-gl-rES/strings.xml
index fc726a6..699cc50 100644
--- a/res/values-gl-rES/strings.xml
+++ b/res/values-gl-rES/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"Números bloqueados"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"As chamadas e as mensaxes dos números da lista están bloqueadas."</string>
+    <string name="block_number" msgid="1101252256321306179">"Engadir un número"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"Queres desbloquear o <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g>?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"Desbloquear"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"Bloquear chamadas e mensaxes do"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"Número ou correo electrónico"</string>
+    <string name="block_button" msgid="8822290682524373357">"Bloquear"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"Só o propietario do dispositivo pode ver e xestionar os números bloqueados."</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"Toca para desbloquear"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"Usando o marcador persoal para facer a chamada"</string>
 </resources>
diff --git a/res/values-gu-rIN/strings.xml b/res/values-gu-rIN/strings.xml
index 5c3078e..3d4e684 100644
--- a/res/values-gu-rIN/strings.xml
+++ b/res/values-gu-rIN/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"અવરોધિત નંબરો"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"આ સૂચિ પરના નંબરોના કૉલ્સ અને ટેક્સ્ટ અવરોધિત કરવામાં આવે છે."</string>
+    <string name="block_number" msgid="1101252256321306179">"એક નંબર ઉમેરો"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"<xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g> ને અનાવરોધિત કરીએ?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"અનાવરોધિત કરો"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"આ નંબરના કૉલ્સ અને ટેક્સ્ટને અવરોધિત કરો"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"નંબર અથવા ઇમેઇલ"</string>
+    <string name="block_button" msgid="8822290682524373357">"અવરોધિત કરો"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"ફક્ત ઉપકરણના માલિક અવરોધિત નંબરોને જોઈ અને સંચાલિત કરી શકે છે."</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"અનાવરોધિત કરવા માટે ટૅબ કરો"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"કૉલ કરવા માટે વ્યક્તિગત ડાયલરનો ઉપયોગ કરી રહ્યાં છે"</string>
 </resources>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index 09c654b..ee406ab 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"अवरोधित नंबर"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"इस सूची के नंबर से कॉल और लेख अवरुद्ध किए गए हैं."</string>
+    <string name="block_number" msgid="1101252256321306179">"एक नंबर जोड़ें"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"<xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g> को अनवरोधित करें?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"अनवरोधित करें"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"इसके कॉल और लेख को अवरुद्ध करें"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"नंबर या ईमेल"</string>
+    <string name="block_button" msgid="8822290682524373357">"अवरुद्ध करें"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"केवल डिवाइस स्वामी अवरुद्ध किए गए नंबर देख और प्रबंधित कर सकते हैं."</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"अनवरोधित करने के लिए टैब करें"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"कॉल करने के लिए व्यक्तिगत डायलर का उपयोग करना"</string>
 </resources>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index 2a47df9..a0a1399 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"Blokirani brojevi"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"Pozivi i poruke s brojeva na popisu su blokirani."</string>
+    <string name="block_number" msgid="1101252256321306179">"Dodaj broj"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"Želite li deblokirati <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g>?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"Deblokiraj"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"Blokirajte pozive i poruke koje upućuje"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"Broj ili e-adresa"</string>
+    <string name="block_button" msgid="8822290682524373357">"Blokiraj"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"Samo vlasnik uređaja može pregledavati i kontrolirati blokirane brojeve."</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"Dodirnite da biste deblokirali"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"Za upućivanje poziva upotrebljava se osobni program za biranje"</string>
 </resources>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index 4796321..57ba604 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"Letiltott számok"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"A listán szereplő számokról érkező hívások és SMS-ek le vannak tiltva."</string>
+    <string name="block_number" msgid="1101252256321306179">"Szám hozzáadása"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"Feloldja a következő szám letiltását: <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g>?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"Letiltás feloldása"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"A következő számról érkező hívások és SMS-ek letiltása:"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"Szám vagy e-mail-cím"</string>
+    <string name="block_button" msgid="8822290682524373357">"Letiltás"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"Csak az eszköz tulajdonosa nézheti meg és kezelheti a letiltott számokat."</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"Koppintson a letiltás feloldásához"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"Hívás indítása a személyes tárcsázóval"</string>
 </resources>
diff --git a/res/values-hy-rAM/strings.xml b/res/values-hy-rAM/strings.xml
index 6ff22a7..22e68f5 100644
--- a/res/values-hy-rAM/strings.xml
+++ b/res/values-hy-rAM/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"Արգելափակված համարներ"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"Այս ցանկում պարունակվող համարներից զանգերը և տեքստային հաղորդագրությունները արգելափակված են:"</string>
+    <string name="block_number" msgid="1101252256321306179">"Ավելացնել համար"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"Արգելաբացե՞լ <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g> համարը:"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"Արգելաբացել"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"Արգելափակել այս համարից ստացվող զանգերն ու տեքստային հաղորդագրությունները"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"Համարը կամ էլ. փոստի հասցեն"</string>
+    <string name="block_button" msgid="8822290682524373357">"Արգելափակել"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"Միայն սարքի սեփականատերը կարող է դիտել և կառավարել արգելափակված համարները:"</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"Հպեք՝ արգելաբացելու համար"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"Զանգելու նպատակով անհատական համարհավաքիչի օգտագործում"</string>
 </resources>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index 59c3ca9..df8768c 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"Nomor yang diblokir"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"Panggilan telepon dan SMS dari nomor di daftar ini diblokir."</string>
+    <string name="block_number" msgid="1101252256321306179">"Tambahkan nomor"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"Batalkan pemblokiran <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g>?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"Batalkan pemblokiran"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"Blokir panggilan telepon dan SMS dari"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"Nomor atau email"</string>
+    <string name="block_button" msgid="8822290682524373357">"Blokir"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"Hanya pemilik perangkat yang dapat melihat dan mengelola nomor yang diblokir."</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"Tab untuk membatalkan pemblokiran"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"Menggunakan telepon pribadi untuk melakukan panggilan"</string>
 </resources>
diff --git a/res/values-is-rIS/strings.xml b/res/values-is-rIS/strings.xml
index f9516dc..4da6a04 100644
--- a/res/values-is-rIS/strings.xml
+++ b/res/values-is-rIS/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"Númer á bannlista"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"Lokað er fyrir símtöl og textaskilaboð úr númerum á þessum lista."</string>
+    <string name="block_number" msgid="1101252256321306179">"Bæta við númeri"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"Taka <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g> af bannlista?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"Taka af bannlista"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"Loka fyrir símtöl og skilaboð frá"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"Númer eða netfang"</string>
+    <string name="block_button" msgid="8822290682524373357">"Setja á bannlista"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"Aðeins eigandi tækisins getur skoðað og stjórnað númerum á bannlista."</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"Flipi til að taka númer af bannlista"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"Notar eigin símaforrit til að hringja"</string>
 </resources>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index 3df48c1..c1e2cc3 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"Numeri bloccati"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"Le chiamate e gli SMS dai numeri in questo elenco vengono bloccati."</string>
+    <string name="block_number" msgid="1101252256321306179">"Aggiungi un numero"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"Sbloccare <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g>?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"Sblocca"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"Blocca chiamate e SMS da"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"Numero o indirizzo email"</string>
+    <string name="block_button" msgid="8822290682524373357">"Blocca"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"Soltanto il proprietario del dispositivo può visualizzare e gestire i numeri bloccati."</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"Tocca per sbloccare"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"Utilizzo dell\'app Telefono personale per chiamare"</string>
 </resources>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index cc82904..f9a42d4 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"מספרים חסומים"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"שיחות והודעות טקסט ממספרים ברשימה זו חסומות."</string>
+    <string name="block_number" msgid="1101252256321306179">"הוסף מספר"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"האם לבטל את חסימת המספר <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g>?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"בטל חסימה"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"חסום שיחות והודעות טקסט מ-"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"מספר טלפון או אימייל"</string>
+    <string name="block_button" msgid="8822290682524373357">"חסום"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"רק בעל המכשיר יכול להציג ולנהל מספרים חסומים."</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"הקש כדי לבטל את החסימה"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"משתמש בחייגן האישי כדי להתקשר"</string>
 </resources>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index ae002ff..a4db116 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"ブロックした番号"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"このリストにある番号からの通話とテキスト メッセージがブロックされます。"</string>
+    <string name="block_number" msgid="1101252256321306179">"番号を追加"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"<xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g> のブロックを解除しますか?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"ブロックを解除"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"次の発信元からの通話とテキスト メッセージをブロック"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"番号またはメールアドレス"</string>
+    <string name="block_button" msgid="8822290682524373357">"ブロック"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"ブロックした番号を表示、管理できるのは端末の所有者のみです。"</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"タップするとブロックを解除します"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"個人用の電話アプリで電話する"</string>
 </resources>
diff --git a/res/values-ka-rGE/strings.xml b/res/values-ka-rGE/strings.xml
index 66c7eb0..be1d09f 100644
--- a/res/values-ka-rGE/strings.xml
+++ b/res/values-ka-rGE/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"დაბლოკილი ნომრები"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"ამ სიაში შემავალი ნომრებიდან შემოსული ზარები და ტექსტური შეტყობინებები დაბლოკილია."</string>
+    <string name="block_number" msgid="1101252256321306179">"ნომრის დამატება"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"გსურთ, განბლოკოთ <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g>?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"განბლოკვა"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"ზარებისა და ტექსტური შეტყობინებების დაბლოკვა ნომრიდან:"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"ტელეფონის ნომერი ან ელფოსტა"</string>
+    <string name="block_button" msgid="8822290682524373357">"დაბლოკვა"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"დაბლოკილი ნომრების ნახვა და მართვა მხოლოდ მოწყობილობის მფლობელს შეუძლია."</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"შეეხეთ განსაბლოკად"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"ზარის განსახორციელებლად გამოიყენება პირადი დამრეკი"</string>
 </resources>
diff --git a/res/values-kk-rKZ/strings.xml b/res/values-kk-rKZ/strings.xml
index 7c69704..377c1f5 100644
--- a/res/values-kk-rKZ/strings.xml
+++ b/res/values-kk-rKZ/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"Бөгелген нөмірлер"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"Осы тізімдегі нөмірлерден келген қоңыраулар мән мәтіндік хабарлар бөгеледі."</string>
+    <string name="block_number" msgid="1101252256321306179">"Нөмір қосу"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"<xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g> бұғаудан шығару керек пе?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"Бөгеуден шығару"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"Келесінің қоңыраулары мен мәтіндік хабарларын бөгеу"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"Нөмір немесе электрондық пошта"</string>
+    <string name="block_button" msgid="8822290682524373357">"Бөгеу"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"Бөгелген нөмірлерді тек құрылғы иесі көре және басқара алады."</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"Бөгеуден шығару қойындысы"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"Қоңырау шалу үшін жеке нөмір тергішті пайдалану"</string>
 </resources>
diff --git a/res/values-km-rKH/strings.xml b/res/values-km-rKH/strings.xml
index cc0a951..c7b4958 100644
--- a/res/values-km-rKH/strings.xml
+++ b/res/values-km-rKH/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"លេខបានរារាំង"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"ការហៅ និងការផ្ញើសារពីលេខនៅក្នុងបញ្ជីនេះត្រូវបានរារាំង"</string>
+    <string name="block_number" msgid="1101252256321306179">"បន្ថែមលេខ"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"ឈប់រារាំង <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g> ឬ?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"ឈប់រារាំង"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"រារាំងការហៅ និងការផ្ញើសារពី"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"លេខ ឬអ៊ីមែល"</string>
+    <string name="block_button" msgid="8822290682524373357">"រារាំង"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"មានតែម្ចាស់ឧបករណ៍តែប៉ុណ្ណោះដែលអាចមើល និងគ្រប់គ្រងបញ្ជីរារាំងបាន"</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"ផ្ទាំងដែលត្រូវឈប់រារាំង"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"កំពុងប្រើកម្មវិធីហៅផ្ទាល់ខ្លួនដើម្បីធ្វើការហៅទូរស័ព្ទ"</string>
 </resources>
diff --git a/res/values-kn-rIN/strings.xml b/res/values-kn-rIN/strings.xml
index 165dcf7..2bb5ca4 100644
--- a/res/values-kn-rIN/strings.xml
+++ b/res/values-kn-rIN/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"ನಿರ್ಬಂಧಿಸಲಾದ ಸಂಖ್ಯೆಗಳು"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"ಈ ಪಟ್ಟಿಯಲ್ಲಿರುವ ಸಂಖ್ಯೆಗಳಿಂದ ಬರುವ ಕರೆಗಳು ಮತ್ತು ಪಠ್ಯ ಸಂದೇಶಗಳನ್ನು ನಿರ್ಬಂಧಿಸಲಾಗಿದೆ."</string>
+    <string name="block_number" msgid="1101252256321306179">"ಸಂಖ್ಯೆಯನ್ನು ಸೇರಿಸು"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"<xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g> ನಿರ್ಬಂಧ ತೆಗೆಯುವುದೇ?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"ನಿರ್ಬಂಧ ತೆಗೆಯಿರಿ"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"ಇದರಿಂದ ಬರುವ ಕರೆಗಳು ಮತ್ತು ಪಠ್ಯ ಸಂದೇಶಗಳನ್ನು ನಿರ್ಬಂಧಿಸಿ"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"ಸಂಖ್ಯೆ ಅಥವಾ ಇಮೇಲ್"</string>
+    <string name="block_button" msgid="8822290682524373357">"ನಿರ್ಬಂಧಿಸು"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"ಸಾಧನ ಮಾಲೀಕರು ಮಾತ್ರ ನಿರ್ಬಂಧಿಸಿದ ಸಂಖ್ಯೆಗಳನ್ನು ವೀಕ್ಷಿಸಬಹುದು ಮತ್ತು ನಿರ್ವಹಿಸಬಹುದು."</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"ನಿರ್ಬಂಧ ತೆಗೆಯಲು ಟ್ಯಾಬ್"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"ಕರೆ ಮಾಡಲು ವೈಯಕ್ತಿಕ ಡಯಲರ್ ಬಳಸಲಾಗುತ್ತಿದೆ"</string>
 </resources>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index ff26e46..a33c999 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"차단된 번호"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"이 목록의 번호로부터 수신되는 전화와 문자 메시지가 차단됩니다."</string>
+    <string name="block_number" msgid="1101252256321306179">"번호 추가"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"<xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g>번을 차단 해제하시겠습니까?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"차단 해제"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"전화와 문자 메시지를 차단할 번호"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"번호 또는 이메일 주소"</string>
+    <string name="block_button" msgid="8822290682524373357">"차단"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"기기 소유자만 차단된 번호를 보고 관리할 수 있습니다."</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"차단 해제하려면 탭하세요."</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"전화를 걸 때 개인 다이얼러 사용"</string>
 </resources>
diff --git a/res/values-ky-rKG/strings.xml b/res/values-ky-rKG/strings.xml
index c2d8260..b12e9c0 100644
--- a/res/values-ky-rKG/strings.xml
+++ b/res/values-ky-rKG/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"Бөгөттөлгөн номерлер"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"Бул тизмедеги номерлерден келген чалуулар менен SMS билдирүүлөр бөгөттөлгөн."</string>
+    <string name="block_number" msgid="1101252256321306179">"Номер кошуу"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"<xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g> бөгөттөн чыгарылсынбы?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"Бөгөттөн чыгаруу"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"Төмөнкү номерден келген чалуулар менен SMS билдирүүлөрүн бөгөттөө"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"Телефон номери же электрондук почта"</string>
+    <string name="block_button" msgid="8822290682524373357">"Бөгөттөө"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"Бөгөттөлгөн номерлерди түзмөк ээси гана көрүп жана башкара алат."</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"Бөгөттөн чыгаруучу өтмөк"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"Чалууларды аткаруу үчүн жеке тергич колдонулууда"</string>
 </resources>
diff --git a/res/values-lo-rLA/strings.xml b/res/values-lo-rLA/strings.xml
index effc21f..c1909e8 100644
--- a/res/values-lo-rLA/strings.xml
+++ b/res/values-lo-rLA/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"ເບີໂທລະສັບທີ່ບລັອກໄວ້"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"ການໂທ ແລະ ຂໍ້ຄວາມຈາກເບີໂທທີ່ຢູ່ໃນບັນຊີນີ້ແມ່ນຖືກບລັອກໄວ້."</string>
+    <string name="block_number" msgid="1101252256321306179">"ເພີ່ມເບີໂທລະສັບ"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"ຍົກເລີກການບລັອກ <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g> ບໍ?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"ຍົກເລີກການບລັອກ"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"ບລັອກການໂທ ແລະ ຂໍ້ຄວາມຈາກ"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"ເບີໂທລະສັບ ຫຼື ອີເມວ"</string>
+    <string name="block_button" msgid="8822290682524373357">"ບລັອກ"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"ມີແຕ່ເຈົ້າຂອງອຸປະກອນເທົ່ານັ້ນທີ່ສາມາດເບິ່ງ ແລະ ຈັດການເບີທີ່ຖືກບລັອກໄວ້."</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"ແຕະເພື່ອຍົກເລີກການບລັອກ"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"ໃຊ້ແປ້ນໂທສ່ວນຕົວເພື່ອໂທອອກ"</string>
 </resources>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index d0b47cc..75e38dd 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"Užblokuoti numeriai"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"Skambučiai ir teksto pranešimai iš šio numerio yra užblokuoti."</string>
+    <string name="block_number" msgid="1101252256321306179">"Pridėti numerį"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"Panaikinti <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g> blokavimą?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"Atblokuoti"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"Blokuoti skambučius ir teksto pranešimus nuo"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"Numeris arba el. pašto adresas"</string>
+    <string name="block_button" msgid="8822290682524373357">"Blokuoti"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"Tik įrenginio savininkas gali peržiūrėti ir tvarkyti užblokuotus numerius."</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"Palieskite, kad panaikintumėte blokavimą"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"Asmeninio numerio rinkiklio naudojimas skambinant"</string>
 </resources>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index ccc7270..77e89fe 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"Bloķētie numuri"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"No šajā sarakstā ietvertajiem numuriem saņemtie zvani un ziņojumi tiek bloķēti."</string>
+    <string name="block_number" msgid="1101252256321306179">"Pievienot numuru"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"Vai atbloķēt numuru <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g>?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"Atbloķēt"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"Bloķēt ienākošos zvanus un ziņojumus no numura"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"Tālruņa numurs vai e-pasta adrese"</string>
+    <string name="block_button" msgid="8822290682524373357">"Bloķēt"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"Tikai ierīces īpašnieks var skatīt un pārvaldīt bloķētos numurus."</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"Pieskarieties, lai atbloķētu"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"Zvans tiek veikts, izmantojot personisko numura sastādītāju"</string>
 </resources>
diff --git a/res/values-mk-rMK/strings.xml b/res/values-mk-rMK/strings.xml
index 8d7ebce..ad54fa2 100644
--- a/res/values-mk-rMK/strings.xml
+++ b/res/values-mk-rMK/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"Блокирани броеви"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"Повиците и текстовите од броевите на овој список се блокирани."</string>
+    <string name="block_number" msgid="1101252256321306179">"Додај број"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"Деблокирајте го <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g>?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"Деблокирај"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"Блокирај повици и текстови од"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"Број или е-пошта"</string>
+    <string name="block_button" msgid="8822290682524373357">"Блокирај"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"Само сопственикот на уредот може да ги прикаже и да управува со блокираните броеви."</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"Картичка за деблокирање"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"Користење на личниот бирач за остварување повик"</string>
 </resources>
diff --git a/res/values-ml-rIN/strings.xml b/res/values-ml-rIN/strings.xml
index 7bd5029..c554f4e 100644
--- a/res/values-ml-rIN/strings.xml
+++ b/res/values-ml-rIN/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"ബ്ലോക്കുചെയ്ത നമ്പറുകൾ"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"ഈ ലിസ്റ്റിലെ നമ്പറുകളിൽ നിന്നുള്ള കോളുകളും ടെക്സ്റ്റുകളും ബ്ലോക്കുചെയ്തു."</string>
+    <string name="block_number" msgid="1101252256321306179">"ഒരു നമ്പർ ചേർക്കുക"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"<xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g> അൺബ്ലോക്കുചെയ്യണോ?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"അൺബ്ലോക്കുചെയ്യുക"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"ഇനിപ്പറയുന്നതിൽ നിന്നുള്ള കോളുകളും ടെക്സ്റ്റുകളും ബ്ലോക്കുചെയ്യുക"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"നമ്പർ അല്ലെങ്കിൽ ഇമെയിൽ"</string>
+    <string name="block_button" msgid="8822290682524373357">"ബ്ലോക്കുചെയ്യുക"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"ബ്ലോക്കുചെയ്ത നമ്പറുകൾ ഉപകരണ ഉടമയ്ക്ക് മാത്രമേ കാണാനും മാനേജുചെയ്യാനും കഴിയൂ."</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"അൺബ്ലോക്കുചെയ്യുന്നതിനുള്ള ടാബ്"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"കോൾ ചെയ്യുന്നതിന് സ്വകാര്യ ഡയലർ ഉപയോഗിക്കുന്നു"</string>
 </resources>
diff --git a/res/values-mn-rMN/strings.xml b/res/values-mn-rMN/strings.xml
index f722b92..39af057 100644
--- a/res/values-mn-rMN/strings.xml
+++ b/res/values-mn-rMN/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"Блоклосон дугаар"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"Энэ жагсаалтад байгаа дугаараас ирэх дуудлага, текстийг блоклосон."</string>
+    <string name="block_number" msgid="1101252256321306179">"Дугаар нэмэх"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"<xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g>-г блокоос гаргах уу?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"Блокоос гаргах"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"Дараахаас ирэх дуудлага, текстийг блоклох"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"Дугаар эсвэл имэйл"</string>
+    <string name="block_button" msgid="8822290682524373357">"Блоклох"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"Зөвхөн энэ төхөөрөмжийн эзэн блоклосон дугаарыг харж, өөрчлөх боломжтой."</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"Блокоос гаргахын тулд дарна уу"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"Дуудлага хийхийн тулд хувийн залгагчийг ашиглаж байна"</string>
 </resources>
diff --git a/res/values-mr-rIN/strings.xml b/res/values-mr-rIN/strings.xml
index c361736..3129064 100644
--- a/res/values-mr-rIN/strings.xml
+++ b/res/values-mr-rIN/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"अवरोधित केलेले नंबर"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"या सूचीमधील नंबरवरील कॉल आणि मजकूर अवरोधित केले आहेत."</string>
+    <string name="block_number" msgid="1101252256321306179">"एक नंबर जोडा"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"<xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g> अनावरोधित करायचा?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"अनावरोधित करा"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"यावरील कॉल आणि मजकूर अवरोधित करा"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"नंबर किंवा ईमेल"</string>
+    <string name="block_button" msgid="8822290682524373357">"अवरोधित करा"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"फक्त डिव्हाइस मालक अवरोधित केलेले नंबर पाहू आणि व्यवस्थापित करू शकतो."</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"अनावरोधित करण्यासाठी टॅब"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"कॉल करण्यासाठी वैयक्तिक डायलर वापरणे"</string>
 </resources>
diff --git a/res/values-ms-rMY/strings.xml b/res/values-ms-rMY/strings.xml
index 2d31e88..08c4e9e 100644
--- a/res/values-ms-rMY/strings.xml
+++ b/res/values-ms-rMY/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"Nombor yang disekat"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"Panggilan dan teks daripada nombor dalam senarai ini disekat."</string>
+    <string name="block_number" msgid="1101252256321306179">"Tambahkan nombor"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"Nyahsekat <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g>?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"Nyahsekat"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"Sekat panggilan dan teks daripada"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"Nombor atau e-mel"</string>
+    <string name="block_button" msgid="8822290682524373357">"Sekat"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"Hanya pemilik peranti boleh melihat dan menguruskan nombor yang disekat."</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"Tab untuk nyahsekat"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"Menggunakan pendail peribadi untuk membuat panggilan"</string>
 </resources>
diff --git a/res/values-my-rMM/strings.xml b/res/values-my-rMM/strings.xml
index 4b6a0d3..6ff4b5a 100644
--- a/res/values-my-rMM/strings.xml
+++ b/res/values-my-rMM/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"ပိတ်ဆို့ထားသည့် နံပါတ်များ"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"ဤစာရင်းထဲရှိ နံပါတ်များမှ စာများနှင့် ခေါ်ဆိုမှုများကို ပိတ်ဆို့ထားပါသည်။"</string>
+    <string name="block_number" msgid="1101252256321306179">"နံပါတ်တစ်ခု ထည့်ပါ"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"<xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g> ကို ပိတ်ဆို့မှုပြန်ဖွင့်မလား။"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"ပိတ်ဆို့မှုပြန်ဖွင့်ပါ"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"ဖော်ပြပါပုဂ္ဂိုလ်ထံမှ စာနှင့် ခေါ်ဆိုမှုများကို ပိတ်ဆို့ပါ"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"နံပါတ် သို့မဟုတ် အီးမေးလ်"</string>
+    <string name="block_button" msgid="8822290682524373357">"ပိတ်ဆို့ပါ"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"ပိတ်ဆို့ထားသည့် နံပါတ်များကို စက်ပစ္စည်းပိုင်ရှင်သာလျှင် ကြည့်ရှု၍ စီမံခန့်ခွဲနိုင်ပါသည်။"</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"ပိတ်ဆို့မှုပြန်ဖွင့်ရန် တို့ပါ"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"ဖုန်းခေါ်ဆိုမှုပြုလုပ်ရန် ကိုယ်ရေးကိုယ်တာ ဖုန်းခေါ်ဆိုမှုစနစ်ကို အသုံးပြုခြင်း"</string>
 </resources>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index e37f856..07d12d0 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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 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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"Blokkerte numre"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"Anrop og meldinger fra numrene på denne listen er blokkerte."</string>
+    <string name="block_number" msgid="1101252256321306179">"Legg til et nummer"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"Vil du oppheve blokkeringen av <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g>?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"Opphev blokkering"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"Blokkér anrop og meldinger fra"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"Nummer eller e-post"</string>
+    <string name="block_button" msgid="8822290682524373357">"Blokkér"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"Bare enhetseieren kan se og administrere blokkerte numre."</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"Trykk for å oppheve blokkeringen"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"Ring via den personlige ringeappen"</string>
 </resources>
diff --git a/res/values-ne-rNP/strings.xml b/res/values-ne-rNP/strings.xml
index 2fc35ed..3b54140 100644
--- a/res/values-ne-rNP/strings.xml
+++ b/res/values-ne-rNP/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"रोकिएका नम्बरहरू"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"यस सूचीका नम्बरहरूबाट आउने कल र पाठ सन्देशहरू रोकिएका छन्।"</string>
+    <string name="block_number" msgid="1101252256321306179">"नम्बर थप्नुहोस्"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"<xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g> लाई अनब्लक गर्ने हो?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"अनब्लक गर्नुहोस्"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"यस नम्बरबाट कल र पाठ सन्देशहरूलाई रोक्नुहोस्"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"नम्बर वा इमेल"</string>
+    <string name="block_button" msgid="8822290682524373357">"रोक्नुहोस्"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"यन्त्रको मालिकले रोकिएका नम्बरहरूलाई हेर्न र व्यवस्थापन गर्न सक्छ।"</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"अनब्लक गर्ने ट्याब"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"कल गर्न व्यक्तिगत डायलर प्रयोग गर्नुहोस्"</string>
 </resources>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index 01f7810..2a53f56 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"Geblokkeerde nummers"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"Oproepen en sms\'jes van nummers op deze lijst worden geblokkeerd."</string>
+    <string name="block_number" msgid="1101252256321306179">"Een nummer toevoegen"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"Blokkering van <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g> opheffen?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"Blokkering opheffen"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"Oproepen en sms\'jes blokkeren van"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"Nummer of e-mailadres"</string>
+    <string name="block_button" msgid="8822290682524373357">"Blokkeren"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"Alleen de eigenaar van het apparaat kan geblokkeerd nummers bekijken en beheren."</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"Tik om blokkering op te heffen"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"De persoonlijke kiezer gebruiken om te bellen"</string>
 </resources>
diff --git a/res/values-pa-rIN/strings.xml b/res/values-pa-rIN/strings.xml
index ce7c88c..39d8560 100644
--- a/res/values-pa-rIN/strings.xml
+++ b/res/values-pa-rIN/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"ਬਲੌਕ ਕੀਤੇ ਗਏ ਨੰਬਰ"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"ਇਸ ਸੂਚੀ ਵਿੱਚ ਦਿੱਤੇ ਨੰਬਰਾਂ ਤੋਂ ਕਾਲਾਂ ਅਤੇ ਟੈਕਸਟ ਬਲੌਕ ਕੀਤੇ ਗਏ ਹਨ।"</string>
+    <string name="block_number" msgid="1101252256321306179">"ਇੱਕ ਨੰਬਰ ਸ਼ਾਮਲ ਕਰੋ"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"ਕੀ <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g> ਨੂੰ ਅਣਬਲੌਕ ਕਰਨਾ ਹੈ?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"ਅਣਬਲੌਕ ਕਰੋ"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"ਇਸ ਨੰਬਰ ਤੋਂ ਕਾਲਾਂ ਅਤੇ ਟੈਕਸਟ ਨੂੰ ਬਲੌਕ ਕਰੋ"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"ਨੰਬਰ ਜਾਂ ਈਮੇਲ"</string>
+    <string name="block_button" msgid="8822290682524373357">"ਬਲੌਕ ਕਰੋ"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"ਸਿਰਫ਼ ਡੀਵਾਈਸ ਮਾਲਕ ਹੀ ਬਲੌਕ ਕੀਤੇ ਗਏ ਨੰਬਰਾਂ ਨੂੰ ਵੇਖ ਅਤੇ ਪ੍ਰਬੰਧਿਤ ਕਰ ਸਕਦਾ ਹੈ।"</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"ਅਣਬਲੌਕ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"ਕਾਲ ਕਰਨ ਲਈ ਨਿੱਜੀ ਡਾਇਲਰ ਦੀ ਵਰਤੋਂ ਕਰਨੀ"</string>
 </resources>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index a2243bc..b75c3ca 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"Zablokowane numery"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"Połączenia i SMS-y z numerów na tej liście są blokowane."</string>
+    <string name="block_number" msgid="1101252256321306179">"Dodaj numer"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"Odblokować <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g>?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"Odblokuj"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"Blokuj połączenia i SMS-y z:"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"Numer lub e-mail"</string>
+    <string name="block_button" msgid="8822290682524373357">"Zablokuj"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"Tylko właściciel urządzenia może przeglądać zablokowane numery i nimi zarządzać."</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"Kliknij, by odblokować"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"Wykonuję połączenie z osobistego telefonu"</string>
 </resources>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index f3ec4f8..9912d56 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"Números bloqueados"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"As chamadas e mensagens de texto dos números nesta lista são bloqueadas."</string>
+    <string name="block_number" msgid="1101252256321306179">"Adicionar um número"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"Pretende desbloquear <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g>?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"Desbloquear"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"Bloquear chamadas e mensagens de texto de"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"Número ou email"</string>
+    <string name="block_button" msgid="8822290682524373357">"Bloquear"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"Apenas o proprietário do dispositivo pode ver e gerir os números bloqueados."</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"Tocar para desbloquear"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"A utilizar o telefone pessoal para efetuar a chamada"</string>
 </resources>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index 1727b2a..701b7cc 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"Números bloqueados"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"As chamadas e mensagens de texto de números desta lista estão bloqueadas."</string>
+    <string name="block_number" msgid="1101252256321306179">"Adicionar um número"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"Desbloquear <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g>?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"Desbloquear"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"Bloquear chamadas e mensagens de texto de"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"Número ou e-mail"</string>
+    <string name="block_button" msgid="8822290682524373357">"Bloquear"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"Apenas o proprietário do dispositivo pode ver e gerenciar os números bloqueados."</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"Toque para desbloquear"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"Usando o discador pessoal para fazer a chamada"</string>
 </resources>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index 79cc111..c1a36b3 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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" 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="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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"Numere blocate"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"Apelurile și mesajele text de la numerele din această listă sunt blocate."</string>
+    <string name="block_number" msgid="1101252256321306179">"Adăugați un număr"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"Deblocați <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g>?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"Deblocați"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"Blocați apelurile și mesajele text de la"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"Număr sau adresă de e-mail"</string>
+    <string name="block_button" msgid="8822290682524373357">"Blocați"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"Numai proprietarul dispozitivului poate vedea și gestiona numerele blocate."</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"Atingeți pentru a debloca"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"Utilizarea telefonului personal pentru a apela"</string>
 </resources>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index 4d53bc0..f721c5f 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"Заблокированные номера"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"Звонки и сообщения с этих номеров блокируются."</string>
+    <string name="block_number" msgid="1101252256321306179">"Добавить номер"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"Разблокировать номер <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g>?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"Разблокировать"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"Блокировать звонки и сообщения с"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"Телефон или адрес эл. почты"</string>
+    <string name="block_button" msgid="8822290682524373357">"Заблокировать"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"Просматривать и изменять список заблокированных номеров может только владелец устройства."</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"Нажмите, чтобы разблокировать"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"Набор номера с помощью персонализированной панели"</string>
 </resources>
diff --git a/res/values-si-rLK/strings.xml b/res/values-si-rLK/strings.xml
index 39d2298..b8a9b69 100644
--- a/res/values-si-rLK/strings.xml
+++ b/res/values-si-rLK/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"අවහිර කළ අංක"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"මෙම ලැයිස්තුව වෙතින් වන අංක හා පෙළ අවහිර කර ඇත."</string>
+    <string name="block_number" msgid="1101252256321306179">"අංකයක් එක් කරන්න"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"<xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g> අවහිර නොකරන්නද?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"අවහිර නොකරන්න"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"ඇමතුම් හා පෙළ අවහිර කරන්න"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"අංකය හෝ ඊ-තැපැල්"</string>
+    <string name="block_button" msgid="8822290682524373357">"අවහිර කරන්න"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"උපාංගය හිමිකරුට පමණක් අවහිර අංක බැලීමට සහ කළමනාකරණය කිරීමට හැකිය."</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"අවහිර නොකිරීමේ කිරීමේ ටැබය"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"ඇමතුම ගැනීමට පුද්ගලික ඩයල්කරු භාවිත කරමින්"</string>
 </resources>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index 4f1a354..1bea5e2 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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 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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"Blokované čísla"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"Hovory aj textové správy z čísel v tomto zozname sú blokované."</string>
+    <string name="block_number" msgid="1101252256321306179">"Pridať číslo"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"Odblokovať číslo <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g>?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"Odblokovať"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"Blokovať hovory a textové správy z čísla"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"Číslo alebo e-mailová adresa"</string>
+    <string name="block_button" msgid="8822290682524373357">"Blokovať"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"Iba vlastník zariadenia môže zobrazovať a spravovať blokované čísla."</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"Klepnutím odblokujete"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"Na zavolanie sa používa osobné vytáčanie"</string>
 </resources>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index fcaaea1..440ca83 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"Blokirane številke"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"Klici in SMS-ji s številk na tem seznamu so blokirani."</string>
+    <string name="block_number" msgid="1101252256321306179">"Dodaj številko"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"Želite odblokirati številko <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g>?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"Odblokiraj"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"Blokiranje klicev in SMS-jev s številke"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"Številka ali e-poštni naslov"</string>
+    <string name="block_button" msgid="8822290682524373357">"Blokiraj"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"Samo lastnik naprave si lahko ogleda blokirane številke in jih upravlja."</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"Dotaknite se, če želite odblokirati številko"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"Uporaba osebnega klicalnika za klic"</string>
 </resources>
diff --git a/res/values-sq-rAL/strings.xml b/res/values-sq-rAL/strings.xml
index e684eff..f825e03 100644
--- a/res/values-sq-rAL/strings.xml
+++ b/res/values-sq-rAL/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"Numrat e bllokuar"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"Telefonatat dhe mesazhet me tekst nga numrat në këtë listë janë të bllokuara."</string>
+    <string name="block_number" msgid="1101252256321306179">"Shto një numër"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"Zhblloko <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g>?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"Zhblloko"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"Blloko telefonatat dhe mesazhet me tekst nga"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"Numri ose maili"</string>
+    <string name="block_button" msgid="8822290682524373357">"Blloko"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"Vetëm zotëruesi i pajisjes mund të shikojë dhe të menaxhojë numrat e bllokuar."</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"Skeda për zhbllokimin"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"Po përdor formuesin personal të numrave për të kryer telefonatën"</string>
 </resources>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index 07b1327..82cfb29 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"Блокирани бројеви"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"Позиви и SMS-ови са бројева на овој листи су блокирани."</string>
+    <string name="block_number" msgid="1101252256321306179">"Додај број"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"Желите ли да деблокирате <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g>?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"Деблокирај"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"Блокирајте позиве и SMS-ове од"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"Број или имејл"</string>
+    <string name="block_button" msgid="8822290682524373357">"Блокирај"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"Само власник уређаја може да прегледа блокиране бројеве и управља њима."</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"Додирните да бисте деблокирали"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"Коришћење личног бројчаника за упућивање позива"</string>
 </resources>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index 6bbdeff..40cb5bb 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"Blockerade nummer"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"Samtal och sms från telefonnummer i den här listan blockeras."</string>
+    <string name="block_number" msgid="1101252256321306179">"Lägg till ett telefonnummer"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"Vill du häva blockeringen av <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g>?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"Häv blockering"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"Blockera samtal och sms från"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"Telefonnummer eller e-postadress"</string>
+    <string name="block_button" msgid="8822290682524373357">"Blockera"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"Bara enhetens ägare kan se och hantera blockerade nummer."</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"Tryck här om du vill häva blockeringen"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"Ringer med den egna uppringningsfunktionen"</string>
 </resources>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index 4d5cff7..df27bb0 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"Nambari zilizozuiwa"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"Simu na SMS kutoka nambari zilizo kwenye orodha hii zimezuiwa."</string>
+    <string name="block_number" msgid="1101252256321306179">"Ongeza nambari"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"Ungependa kuondoa kizuizi kwenye <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g>?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"Ondoa kizuizi"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"Zuia simu na SMS kutoka kwa"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"Nambari au anwani ya barua pepe"</string>
+    <string name="block_button" msgid="8822290682524373357">"Zuia"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"Ni mmiliki wa kifaa pekee anayeweza kuangalia na kuthibiti nambari zilizozuiwa."</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"Kichupo cha kuondoa kizuizi"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"Kupiga simu kwa kutumia kipiga simu cha binafsi"</string>
 </resources>
diff --git a/res/values-ta-rIN/strings.xml b/res/values-ta-rIN/strings.xml
index 28f0b25..2db66cf 100644
--- a/res/values-ta-rIN/strings.xml
+++ b/res/values-ta-rIN/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"தடுக்கப்பட்ட எண்கள்"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"இந்தப் பட்டியலில் உள்ள எண்களின் அழைப்புகளும் உரைச்செய்திகளும் தடுக்கப்படும்."</string>
+    <string name="block_number" msgid="1101252256321306179">"எண்ணைச் சேர்"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"<xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g>ஐ அனுமதிக்கவா?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"அனுமதி"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"இந்த எண்ணிலிருந்து வரும் அழைப்புகளையும் உரைச்செய்திகளையும் தடு:"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"எண் அல்லது மின்னஞ்சல்"</string>
+    <string name="block_button" msgid="8822290682524373357">"தடு"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"சாதன உரிமையாளர் மட்டுமே தடுக்கப்பட்ட எண்களைப் பார்க்கவும் நிர்வகிக்கவும் முடியும்."</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"அனுமதிக்க, தட்டவும்"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"தனிப்பட்ட டயலரைப் பயன்படுத்தி அழைக்கவும்"</string>
 </resources>
diff --git a/res/values-te-rIN/strings.xml b/res/values-te-rIN/strings.xml
index 32d88d7..d4ea3ff 100644
--- a/res/values-te-rIN/strings.xml
+++ b/res/values-te-rIN/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"బ్లాక్ చేయబడిన నంబర్‌లు"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"ఈ జాబితాలోని నంబర్‌ల నుండి వచ్చే కాల్‌లు మరియు సందేశాలు బ్లాక్ చేయబడతాయి."</string>
+    <string name="block_number" msgid="1101252256321306179">"నంబర్‌ను జోడించు"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"<xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g>ని అన్‌బ్లాక్ చేయాలా?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"అన్‌బ్లాక్ చేయి"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"దీని నుండి కాల్‌లు మరియు సందేశాలను బ్లాక్ చేయండి"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"నంబర్ లేదా ఇమెయిల్"</string>
+    <string name="block_button" msgid="8822290682524373357">"బ్లాక్ చేయి"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"కేవలం పరికర యజమాని మాత్రమే బ్లాక్ చేసిన నంబర్‌లను వీక్షించగలరు మరియు నిర్వహించగలరు."</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"అన్‌బ్లాక్ చేయడానికి నొక్కండి"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"కాల్ చేయడానికి వ్యక్తిగత డయలర్‌ను ఉపయోగిస్తోంది"</string>
 </resources>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index 117d656..4afcffc 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"หมายเลขที่ถูกบล็อก"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"สายเรียกเข้าและข้อความจากหมายเลขในรายการนี้ถูกบล็อก"</string>
+    <string name="block_number" msgid="1101252256321306179">"เพิ่มหมายเลข"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"เลิกบล็อก <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g> ใช่ไหม"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"เลิกบล็อก"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"บล็อกสายเรียกเข้าและข้อความจาก"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"หมายเลขหรืออีเมล"</string>
+    <string name="block_button" msgid="8822290682524373357">"บล็อก"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"มีเพียงเจ้าของอุปกรณ์เท่านั้นที่สามารถดูและจัดการหมายเลขที่ถูกบล็อก"</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"แตะเพื่อเลิกบล็อก"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"การใช้โทรศัพท์ส่วนตัวเพื่อทำการโทร"</string>
 </resources>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index 728ad6b..9346357 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"Mga naka-block na numero"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"Bina-block ang mga tawag at text mula sa mga numerong nasa listahang ito."</string>
+    <string name="block_number" msgid="1101252256321306179">"Magdagdag ng numero"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"Aalisin ba sa pagkaka-block ang <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g>?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"Alisin sa pagkaka-block"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"I-block ang mga tawag at text mula sa"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"Numero o email"</string>
+    <string name="block_button" msgid="8822290682524373357">"I-block"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"Ang may-ari ng device lang ang makakakita at makakapamahala sa mga naka-block na numero."</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"Tab upang alisin sa pagkaka-block"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"Ginagamit ang personal na dialer upang tumawag"</string>
 </resources>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index dd5c6bd..1c3dc4d 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"Engellenen numaralar"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"Bu listedeki numaralardan gelen çağrılar ve kısa mesajlar engelleniyor."</string>
+    <string name="block_number" msgid="1101252256321306179">"Numara ekle"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"<xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g> numaralı telefonun engellemesi kaldırılsın mı?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"Engellemeyi kaldır"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"Şu numaradan gelen çağrıları ve kısa mesajları engelle:"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"Numara veya e-posta"</string>
+    <string name="block_button" msgid="8822290682524373357">"Engelle"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"Yalnızca cihaz sahibi engellenen numaraları görüntüleyebilir ve yönetebilir."</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"Engellemeyi kaldırmak için hafifçe dokunun"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"Telefon etmek için kişisel numara çeviriciyi kullanma"</string>
 </resources>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index 6182002..1508452 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"Заблоковані номери"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"Виклики та SMS із цих номерів блокуватимуться."</string>
+    <string name="block_number" msgid="1101252256321306179">"Додати номер"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"Розблокувати <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g>?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"Розблокувати"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"Заблокувати виклики та SMS із"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"Номер чи електронна адреса"</string>
+    <string name="block_button" msgid="8822290682524373357">"Заблокувати"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"Лише власник пристрою може переглядати заблоковані номери та керувати ними."</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"Торкніться, щоб розблокувати"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"Для дзвінків використовується особистий додаток набору номерів"</string>
 </resources>
diff --git a/res/values-ur-rPK/strings.xml b/res/values-ur-rPK/strings.xml
index 9774c7e..18ee6ea 100644
--- a/res/values-ur-rPK/strings.xml
+++ b/res/values-ur-rPK/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"مسدود کردہ نمبرز"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"اس فہرست پر موجود نمبرز سے کالز اور پیغامات مسدود ہیں۔"</string>
+    <string name="block_number" msgid="1101252256321306179">"ایک نمبر کا اضافہ کریں"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"<xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g> غیر مسدود کریں؟"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"غیر مسدود کریں"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"ان کی کالز اور پیغامات مدسود کریں"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"نمبر یا ای میل"</string>
+    <string name="block_button" msgid="8822290682524373357">"مسدود کریں"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"صرف آلہ کا مالک مسدود کردہ نمبرز کو دیکھ سکتا ہے اور ان کا نظم کر سکتا ہے۔"</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"غیر مسدود کرنے کا ٹیب"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"کال کرنے کیلئے ذاتی ڈائلر استعمال ہو رہا ہے"</string>
 </resources>
diff --git a/res/values-uz-rUZ/strings.xml b/res/values-uz-rUZ/strings.xml
index 1a84603..4f9fb0c 100644
--- a/res/values-uz-rUZ/strings.xml
+++ b/res/values-uz-rUZ/strings.xml
@@ -20,6 +20,7 @@
     <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"Telefon"</string>
     <string name="unknown" msgid="6878797917991465859">"Noma’lum"</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> qo‘ng‘irog‘i javobsiz qoldirildi"</string>
@@ -41,11 +42,21 @@
     <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">"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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"Bloklangan raqamlar"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"Bu ro‘yxatdagi raqamlardan keladigan qo‘ng‘iroqlar va SMS xabarlar bloklanadi."</string>
+    <string name="block_number" msgid="1101252256321306179">"Telefon raqamini qo‘shish"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"<xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g> raqami blokdan chiqarilsinmi?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"Blokdan chiqarish"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"Bu raqamdan kelgan qo‘ng‘iroqlar va SMS xabarlar bloklansin"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"Telefon raqami yoki e-pochta manzili"</string>
+    <string name="block_button" msgid="8822290682524373357">"Bloklash"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"Faqat qurilma egasi bloklangan raqamlarni ko‘rishi va boshqarishi mumkin."</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"Blokdan chiqarish uchun bosing"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"Qo‘ng‘iroq qilish uchun shaxsiy raqam tergichdan foydalanilmoqda"</string>
 </resources>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index 6f5ccc5..0b99443 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"Số bị chặn"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"Cuộc gọi và tin nhắn từ các số trên danh sách này bị chặn."</string>
+    <string name="block_number" msgid="1101252256321306179">"Thêm số điện thoại"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"Bỏ chặn <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g>?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"Bỏ chặn"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"Chặn cuộc gọi và tin nhắn từ"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"Số điện thoại hoặc email"</string>
+    <string name="block_button" msgid="8822290682524373357">"Chặn"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"Chỉ chủ sở hữu thiết bị mới có thể xem và quản lý các số bị chặn."</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"Tab để bỏ chặn"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"Sử dụng trình quay số cá nhân để gọi điện"</string>
 </resources>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index 4c30166..3172ce8 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,22 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"已屏蔽的号码"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"系统已屏蔽此列表中所列号码的来电和短信。"</string>
+    <string name="block_number" msgid="1101252256321306179">"添加电话号码"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"要取消屏蔽 <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g> 吗?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"取消屏蔽"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"屏蔽以下号码的来电和短信:"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"电话号码或电子邮件地址"</string>
+    <string name="block_button" msgid="8822290682524373357">"屏蔽"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"只有机主才能查看和管理已屏蔽的号码。"</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"点按即可取消屏蔽"</string>
+    <!-- no translation found for toast_personal_call_msg (5115361633476779723) -->
+    <skip />
 </resources>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index d00073d..8ce1473 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"已封鎖的號碼"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"系統已封鎖此清單中所有號碼的來電和短訊。"</string>
+    <string name="block_number" msgid="1101252256321306179">"新增號碼"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"要解除封鎖 <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g> 嗎?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"解除封鎖"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"封鎖下列號碼的來電和短訊:"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"號碼或電郵"</string>
+    <string name="block_button" msgid="8822290682524373357">"封鎖"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"只有裝置擁有者可查看和管理已封鎖的號碼。"</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"輕按即可解除封鎖"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"使用個人撥號器撥打電話"</string>
 </resources>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index 6468859..f5a88a9 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"已封鎖的號碼"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"系統已將這份清單所列號碼的來電和簡訊封鎖。"</string>
+    <string name="block_number" msgid="1101252256321306179">"新增號碼"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"要解除封鎖 <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g> 嗎?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"解除封鎖"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"封鎖以下號碼的來電和簡訊:"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"電話號碼或電子郵件地址"</string>
+    <string name="block_button" msgid="8822290682524373357">"封鎖"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"只有裝置擁有者能夠查看及管理已封鎖的號碼。"</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"輕按即可解除封鎖"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"使用個人撥號程式撥打電話"</string>
 </resources>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index d8e5757..dc53179 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -20,6 +20,7 @@
     <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>
@@ -41,11 +42,21 @@
     <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>
+    <string name="blocked_numbers" msgid="2751843139572970579">"Izinombolo ezivinjiwe"</string>
+    <string name="blocked_numbers_msg" msgid="8210089024274925462">"Amakholi nemibhalo evela kuzinombolo ezikulo luhlu kuvinjelwe."</string>
+    <string name="block_number" msgid="1101252256321306179">"Engeza inombolo"</string>
+    <string name="unblock_dialog_body" msgid="1614238499771862793">"Vulela i-<xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g>?"</string>
+    <string name="unblock_button" msgid="3078048901972674170">"Vulela"</string>
+    <string name="add_blocked_dialog_body" msgid="9030243212265516828">"Vimba amakholi nemibhalo kusukela ku-"</string>
+    <string name="add_blocked_number_hint" msgid="1601214698916175149">"Inombolo noma i-imeyili"</string>
+    <string name="block_button" msgid="8822290682524373357">"Vimba"</string>
+    <string name="non_primary_user" msgid="5180129233352533459">"Umnikazi wedivayisi kuphela ongabuka aphinde aphathe izinombolo ezivinjelwe."</string>
+    <string name="delete_icon_description" msgid="1828583824185681368">"Ithebhu lokuvulela"</string>
+    <string name="toast_personal_call_msg" msgid="5115361633476779723">"Ukusebenzisa okokudayila komuntu siqu ukwenza ikholi"</string>
 </resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index f7ad003..f2b1f5f 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -20,4 +20,11 @@
     <dimen name="notification_icon_size">64dp</dimen>
 
     <dimen name="dialer_settings_actionbar_elevation">2dp</dimen>
+
+    <dimen name="blocked_numbers_large_padding">16dp</dimen>
+    <dimen name="blocked_numbers_extra_large_padding">32dp</dimen>
+    <dimen name="blocked_numbers_delete_icon_size">24dp</dimen>
+    <dimen name="blocked_numbers_progress_bar_padding">100dp</dimen>
+    <dimen name="blocked_numbers_font_size">16sp</dimen>
+    <dimen name="blocked_numbers_line_spacing">8sp</dimen>
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index cdae003..a85faf0 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 -->
@@ -97,9 +99,6 @@
     <!-- Message shown when the user tries to make a video call when already in a video call. -->
     <string name ="duplicate_video_call_not_allowed">Call cannot be added at this time.</string>
 
-    <!-- Message indicating video calls not allowed if user enabled TTY Mode -->
-    <string name="video_call_not_allowed_if_tty_enabled">Please disable TTY Mode to make video calls.</string>
-
     <!-- missing voicemail number -->
     <!-- Title of the "Missing voicemail number" dialog -->
     <string name="no_vm_number">Missing voicemail number</string>
@@ -117,6 +116,28 @@
     <string name="change_default_dialer_no_previous_app_set_text">Use <xliff:g id="new_app">%s</xliff:g> as your default dialer app?</string>
 
 
+    <!-- Blocked numbers -->
+    <string name="blocked_numbers">Blocked numbers</string>
+    <!-- Text shown at the beginning of the blocked numbers screen to explain what the screen is about. -->
+    <string name="blocked_numbers_msg">Calls and texts from numbers on this list are blocked.</string>
+    <!-- Button to add a blocked number. -->
+    <string name="block_number">Add a number</string>
+    <!-- Body of dialog to confirm unblocking a number. -->
+    <string name="unblock_dialog_body">Unblock <xliff:g id="number_to_block">%1$s</xliff:g>?</string>
+    <!-- Button to unblock a number. -->
+    <string name="unblock_button">Unblock</string>
+    <!-- Body of dialog to block a number.  -->
+    <string name="add_blocked_dialog_body">Block calls and texts from</string>
+    <!-- Hint shown in the edit text box for adding a blocked number -->
+    <string name="add_blocked_number_hint">Number or email</string>
+    <!-- Button to block a number. -->
+    <string name="block_button">Block</string>
+    <!-- String shown to users unable to manage blocked numbers because they are not owners of the
+        device. -->
+    <string name="non_primary_user">Only the device owner can view and manage blocked numbers.</string>
+    <!-- Description of icon to delete blocked number. -->
+    <string name="delete_icon_description">Tab to unblock</string>
+
     <!-- DO NOT TRANSLATE. Label for test Subscription 0. -->
     <string name="test_account_0_label">Q Mobile</string>
     <!-- DO NOT TRANSLATE. Label for test Subscription 1. -->
@@ -137,4 +158,6 @@
 
     <!-- DO NOT TRANSLATE. Hardcoded number used for restricted incoming phone numbers. -->
     <string name="handle_restricted">RESTRICTED</string>
+
+    <string name="toast_personal_call_msg">Using the personal dialer to make the call</string>
 </resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index d846cda..a7eb2fd 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -33,6 +33,13 @@
         <item name="android:windowContentOverlay">@null</item>
     </style>
 
+    <style name="Theme.Telecom.BlockedNumbers" parent="@android:style/Theme.Material.Light">
+        <item name="android:actionBarStyle">@style/TelecomDialerSettingsActionBarStyle</item>
+        <item name="android:colorPrimaryDark">@color/dialer_settings_color_dark</item>
+        <item name="android:homeAsUpIndicator">@drawable/ic_back_arrow</item>
+        <item name="android:windowContentOverlay">@null</item>
+    </style>
+
     <style name="TelecomDialerSettingsActionBarStyle" parent="android:Widget.Material.ActionBar">
         <item name="android:background">@color/dialer_settings_actionbar_background_color</item>
         <item name="android:titleTextStyle">@style/TelecomDialerSettingsActionBarTitleText</item>
@@ -50,4 +57,15 @@
             parent="@android:style/Widget.Material.Light.ActionButton.Overflow">
         <item name="android:src">@drawable/ic_more_vert_24dp</item>
     </style>
+
+    <style name="BlockedNumbersAddButton">
+        <item name="android:textColor">@color/theme_color</item>
+        <item name="android:textSize">@dimen/blocked_numbers_font_size</item>
+        <item name="android:textAllCaps">true</item>
+    </style>
+
+    <style name="BlockedNumbersText">
+        <item name="android:textSize">@dimen/blocked_numbers_font_size</item>
+        <item name="android:lineSpacingExtra">@dimen/blocked_numbers_line_spacing</item>
+    </style>
 </resources>
diff --git a/res/xml/activity_blocked_numbers.xml b/res/xml/activity_blocked_numbers.xml
new file mode 100644
index 0000000..a79078b
--- /dev/null
+++ b/res/xml/activity_blocked_numbers.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+
+<FrameLayout
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingLeft="@dimen/blocked_numbers_large_padding"
+        android:paddingRight="@dimen/blocked_numbers_large_padding">
+
+    <TextView
+            android:id="@+id/non_primary_user"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/non_primary_user"
+            android:paddingTop="@dimen/blocked_numbers_large_padding"
+            style="@style/BlockedNumbersText"
+            android:visibility="gone" />
+
+    <LinearLayout
+            android:id="@+id/manage_blocked_ui"
+            android:orientation="vertical"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:paddingTop="@dimen/blocked_numbers_large_padding">
+
+        <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/blocked_numbers_msg"
+                android:paddingBottom="@dimen/blocked_numbers_extra_large_padding"
+                style="@style/BlockedNumbersText" />
+
+        <TextView
+                android:id="@+id/add_blocked"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/block_number"
+                android:paddingBottom="@dimen/blocked_numbers_extra_large_padding"
+                style="@style/BlockedNumbersAddButton" />
+
+        <ProgressBar
+                android:id="@+id/progress_bar"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:indeterminate="true"
+                android:layout_marginTop="@dimen/blocked_numbers_progress_bar_padding"
+                style="@android:style/Widget.ProgressBar.Large" />
+
+        <ListView
+                android:id="@android:id/list"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent" />
+    </LinearLayout>
+</FrameLayout>
diff --git a/res/xml/add_blocked_number_dialog.xml b/res/xml/add_blocked_number_dialog.xml
new file mode 100644
index 0000000..b71f78f
--- /dev/null
+++ b/res/xml/add_blocked_number_dialog.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+
+<LinearLayout
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical"
+        android:paddingLeft="@dimen/blocked_numbers_extra_large_padding"
+        android:paddingRight="@dimen/blocked_numbers_extra_large_padding"
+        android:focusable="true"
+        android:focusableInTouchMode="true">
+    <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/add_blocked_dialog_body"
+            android:paddingTop="@dimen/blocked_numbers_large_padding"
+            android:paddingBottom="@dimen/blocked_numbers_large_padding"
+            style="@style/BlockedNumbersText" />
+    <EditText
+            android:id="@+id/add_blocked_number"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:paddingTop="@dimen/blocked_numbers_large_padding"
+            android:paddingBottom="@dimen/blocked_numbers_large_padding"
+            android:hint="@string/add_blocked_number_hint"
+            android:inputType="text" />
+</LinearLayout>
\ No newline at end of file
diff --git a/res/xml/layout_blocked_number.xml b/res/xml/layout_blocked_number.xml
new file mode 100644
index 0000000..e6475a4
--- /dev/null
+++ b/res/xml/layout_blocked_number.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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
+  -->
+
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:focusable="true"
+    style="@style/BlockedNumbersText" >
+
+    <TextView
+        android:id="@+id/blocked_number"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentLeft="true"
+        android:paddingBottom="@dimen/blocked_numbers_extra_large_padding"
+        android:layout_toLeftOf="@+id/delete" />
+
+    <ImageButton
+        android:id="@+id/delete_blocked_number"
+        android:layout_width="@dimen/blocked_numbers_delete_icon_size"
+        android:layout_height="@dimen/blocked_numbers_delete_icon_size"
+        android:src="@drawable/ic_close_grey_24dp"
+        android:layout_alignParentRight="true"
+        android:contentDescription="@string/delete_icon_description"
+        android:background="?android:attr/selectableItemBackgroundBorderless" />
+</RelativeLayout>
\ No newline at end of file
diff --git a/scripts/telecom_testing.sh b/scripts/telecom_testing.sh
new file mode 100644
index 0000000..105ec8b
--- /dev/null
+++ b/scripts/telecom_testing.sh
@@ -0,0 +1,99 @@
+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
+      mmma -j40 "packages/services/Telecomm/tests" ${emma_opt}
+    else
+      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/
+    if [ ! -d "$T/coverage" ] ; then
+      mkdir -p "$T/coverage"
+    fi
+    java -jar "$T/prebuilts/sdk/tools/jack-jacoco-reporter.jar" \
+      --report-dir "$T/coverage/" \
+      --metadata-file "$T/out/target/common/obj/APPS/TelecomUnitTests_intermediates/coverage.em" \
+      --coverage-file "/tmp/coverage.ec" \
+      --source-dir "$T/packages/services/Telecomm/src/"
+  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 4d7bdb6..8dd5a7b 100644
--- a/src/com/android/server/telecom/BluetoothManager.java
+++ b/src/com/android/server/telecom/BluetoothManager.java
@@ -28,6 +28,7 @@
 import android.os.Looper;
 import android.os.SystemClock;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.IndentingPrintWriter;
 
 import java.util.List;
@@ -37,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();
+                    }
                 }
            };
 
@@ -61,21 +75,26 @@
     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();
             }
         }
     };
@@ -83,14 +102,14 @@
     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() {
+    private final Runnable mBluetoothConnectionTimeout = new Runnable("BM.cBA") {
         @Override
-        public void run() {
+        public void loggedRun() {
             if (!isBluetoothAudioConnected()) {
                 Log.v(this, "Bluetooth audio inexplicably disconnected within 5 seconds of " +
                         "connection. Updating UI.");
@@ -102,9 +121,8 @@
     private final Context mContext;
 
 
-    public BluetoothManager(Context context, CallAudioManager callAudioManager) {
+    public BluetoothManager(Context context) {
         mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
-        mCallAudioManager = callAudioManager;
         mContext = context;
 
         if (mBluetoothAdapter != null) {
@@ -119,6 +137,10 @@
         context.registerReceiver(mReceiver, intentFilter);
     }
 
+    public void setBluetoothStateListener(BluetoothStateListener bluetoothStateListener) {
+        mBluetoothStateListener = bluetoothStateListener;
+    }
+
     //
     // Bluetooth helper methods.
     //
@@ -138,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:
@@ -208,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;
@@ -233,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();
@@ -249,17 +274,22 @@
         // instantly. (See isBluetoothAudioConnectedOrPending() above.)
         mBluetoothConnectionPending = true;
         mBluetoothConnectionRequestTime = SystemClock.elapsedRealtime();
-        mHandler.removeCallbacks(mBluetoothConnectionTimeout);
-        mHandler.postDelayed(mBluetoothConnectionTimeout,
+        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);
+        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 cee5332..fc124a4 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -29,7 +29,6 @@
 import android.telecom.DisconnectCause;
 import android.telecom.Connection;
 import android.telecom.GatewayInfo;
-import android.telecom.InCallService.VideoCall;
 import android.telecom.ParcelableConnection;
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
@@ -39,10 +38,12 @@
 import android.telecom.VideoProfile;
 import android.telephony.PhoneNumberUtils;
 import android.text.TextUtils;
+import android.os.UserHandle;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telecom.IVideoProvider;
 import com.android.internal.telephony.CallerInfo;
+import com.android.internal.telephony.CallerInfoAsyncQuery;
 import com.android.internal.telephony.CallerInfoAsyncQuery.OnQueryCompleteListener;
 import com.android.internal.telephony.SmsApplication;
 import com.android.server.telecom.ContactsAsyncHelper.OnImageLoadCompleteListener;
@@ -65,13 +66,21 @@
  */
 @VisibleForTesting
 public class Call implements CreateConnectionResponse {
+    public final static String CALL_ID_UNKNOWN = "-1";
+    public final static long DATA_USAGE_NOT_SET = -1;
+
+    public static final int CALL_DIRECTION_UNDEFINED = 0;
+    public static final int CALL_DIRECTION_OUTGOING = 1;
+    public static final int CALL_DIRECTION_INCOMING = 2;
+    public static final int CALL_DIRECTION_UNKNOWN = 3;
+
     /**
      * Listener for events on the call.
      */
     interface Listener {
         void onSuccessfulOutgoingCall(Call call, int callState);
         void onFailedOutgoingCall(Call call, DisconnectCause disconnectCause);
-        void onSuccessfulIncomingCall(Call call);
+        void onSuccessfulIncomingCall(Call call, boolean shouldSendToVoicemail);
         void onFailedIncomingCall(Call call);
         void onSuccessfulUnknownCall(Call call, int callState);
         void onFailedUnknownCall(Call call);
@@ -95,6 +104,7 @@
         void onPhoneAccountChanged(Call call);
         void onConferenceableCallsChanged(Call call);
         boolean onCanceledViaNewOutgoingCallBroadcast(Call call);
+        void onHoldToneRequested(Call call);
     }
 
     public abstract static class ListenerBase implements Listener {
@@ -103,7 +113,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
@@ -152,6 +162,9 @@
         public boolean onCanceledViaNewOutgoingCallBroadcast(Call call) {
             return false;
         }
+
+        @Override
+        public void onHoldToneRequested(Call call) {}
     }
 
     private final OnQueryCompleteListener mCallerInfoQueryListener =
@@ -161,7 +174,10 @@
                 public void onQueryComplete(int token, Object cookie, CallerInfo callerInfo) {
                     synchronized (mLock) {
                         if (cookie != null) {
-                            ((Call) cookie).setCallerInfo(callerInfo, token);
+                            CallSessionCookie callSession = (CallSessionCookie) cookie;
+                            Log.continueSession(callSession.mSession, "OQCL.oQC");
+                            callSession.mSessionCall.setCallerInfo(callerInfo, token);
+                            Log.endSession();
                         }
                     }
                 }
@@ -175,28 +191,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
@@ -220,6 +242,8 @@
 
     private PhoneAccountHandle mTargetPhoneAccountHandle;
 
+    private UserHandle mInitiatingUser;
+
     private final Handler mHandler = new Handler(Looper.getMainLooper());
 
     private final List<Call> mConferenceableCalls = new ArrayList<>();
@@ -298,6 +322,8 @@
 
     private boolean mIsConference = false;
 
+    private final boolean mShouldAttachToExistingConnection;
+
     private Call mParentCall = null;
 
     private List<Call> mChildCalls = new LinkedList<>();
@@ -320,6 +346,8 @@
     private final CallsManager mCallsManager;
     private final TelecomSystem.SyncRoot mLock;
     private final CallerInfoAsyncQueryFactory mCallerInfoAsyncQueryFactory;
+    private final String mId;
+    private Analytics.CallInfo mAnalytics;
 
     private boolean mWasConferencePreviouslyMerged = false;
 
@@ -332,6 +360,24 @@
     private boolean mIsLocallyDisconnecting = false;
 
     /**
+     * Tracks the current call data usage as reported by the video provider.
+     */
+    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;
+
+
+    /**
+     * Indicates whether the call is remotely held.  A call is considered remotely held when
+     * {@link #onConnectionEvent(String)} receives the {@link Connection#EVENT_ON_HOLD_TONE_START}
+     * event.
+     */
+    private boolean mIsRemotelyHeld = false;
+
+    /**
      * Persists the specified parameters and initializes the new instance.
      *
      * @param context The context.
@@ -343,9 +389,13 @@
      *         {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER} flag.
      * @param targetPhoneAccountHandle Account information to use for the call. This account must be
      *         one that was registered with the {@link PhoneAccount#CAPABILITY_CALL_PROVIDER} flag.
-     * @param isIncoming True if this is an incoming call.
+     * @param callDirection one of CALL_DIRECTION_INCOMING, CALL_DIRECTION_OUTGOING,
+     *         or CALL_DIRECTION_UNKNOWN.
+     * @param shouldAttachToExistingConnection Set to true to attach the call to an existing
+     *         connection, regardless of whether it's incoming or outgoing.
      */
     public Call(
+            String callId,
             Context context,
             CallsManager callsManager,
             TelecomSystem.SyncRoot lock,
@@ -356,8 +406,10 @@
             GatewayInfo gatewayInfo,
             PhoneAccountHandle connectionManagerPhoneAccountHandle,
             PhoneAccountHandle targetPhoneAccountHandle,
-            boolean isIncoming,
+            int callDirection,
+            boolean shouldAttachToExistingConnection,
             boolean isConference) {
+        mId = callId;
         mState = isConference ? CallState.ACTIVE : CallState.NEW;
         mContext = context;
         mCallsManager = callsManager;
@@ -366,12 +418,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);
     }
@@ -388,10 +445,14 @@
      *         {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER} flag.
      * @param targetPhoneAccountHandle Account information to use for the call. This account must be
      *         one that was registered with the {@link PhoneAccount#CAPABILITY_CALL_PROVIDER} flag.
-     * @param isIncoming True if this is an incoming call.
+     * @param callDirection one of CALL_DIRECTION_INCOMING, CALL_DIRECTION_OUTGOING,
+     *         or CALL_DIRECTION_UNKNOWN
+     * @param shouldAttachToExistingConnection Set to true to attach the call to an existing
+     *         connection, regardless of whether it's incoming or outgoing.
      * @param connectTimeMillis The connection time of the call.
      */
     Call(
+            String callId,
             Context context,
             CallsManager callsManager,
             TelecomSystem.SyncRoot lock,
@@ -402,15 +463,17 @@
             GatewayInfo gatewayInfo,
             PhoneAccountHandle connectionManagerPhoneAccountHandle,
             PhoneAccountHandle targetPhoneAccountHandle,
-            boolean isIncoming,
+            int callDirection,
+            boolean shouldAttachToExistingConnection,
             boolean isConference,
             long connectTimeMillis) {
-        this(context, callsManager, lock, repository, contactsAsyncHelper,
+        this(callId, context, callsManager, lock, repository, contactsAsyncHelper,
                 callerInfoAsyncQueryFactory, handle, gatewayInfo,
-                connectionManagerPhoneAccountHandle, targetPhoneAccountHandle, isIncoming,
-                isConference);
+                connectionManagerPhoneAccountHandle, targetPhoneAccountHandle, callDirection,
+                shouldAttachToExistingConnection, isConference);
 
         mConnectTimeMillis = connectTimeMillis;
+        mAnalytics.setCallStartTime(connectTimeMillis);
     }
 
     public void addListener(Listener listener) {
@@ -423,6 +486,27 @@
         }
     }
 
+    public void initAnalytics() {
+        int analyticsDirection;
+        switch (mCallDirection) {
+            case CALL_DIRECTION_OUTGOING:
+                analyticsDirection = Analytics.OUTGOING_DIRECTION;
+                break;
+            case CALL_DIRECTION_INCOMING:
+                analyticsDirection = Analytics.INCOMING_DIRECTION;
+                break;
+            case CALL_DIRECTION_UNKNOWN:
+            case CALL_DIRECTION_UNDEFINED:
+            default:
+                analyticsDirection = Analytics.UNKNOWN_DIRECTION;
+        }
+        mAnalytics = Analytics.initiateCallAnalytics(mId, analyticsDirection);
+    }
+
+    public Analytics.CallInfo getAnalytics() {
+        return mAnalytics;
+    }
+
     public void destroy() {
         Log.event(this, Log.Events.DESTROYED);
     }
@@ -438,7 +522,7 @@
 
 
         return String.format(Locale.US, "[%s, %s, %s, %s, %s, childs(%d), has_parent(%b), [%s]]",
-                System.identityHashCode(this),
+                mId,
                 CallState.toString(mState),
                 component,
                 Log.piiHandle(mHandle),
@@ -476,7 +560,8 @@
         return sb.toString();
     }
 
-    int getState() {
+    @VisibleForTesting
+    public int getState() {
         return mState;
     }
 
@@ -503,6 +588,14 @@
     }
 
     /**
+     * Returns the unique ID for this call as it exists in Telecom.
+     * @return The call ID.
+     */
+    public String getId() {
+        return mId;
+    }
+
+    /**
      * Sets the call state. Although there exists the notion of appropriate state transitions
      * (see {@link CallState}), in practice those expectations break down when cellular systems
      * misbehave and they do this very often. The result is that we do not enforce state transitions
@@ -527,6 +620,7 @@
                     // call from resetting active time when it goes in and out of
                     // ACTIVE/ON_HOLD
                     mConnectTimeMillis = System.currentTimeMillis();
+                    mAnalytics.setCallStartTime(mConnectTimeMillis);
                 }
 
                 // Video state changes are normally tracked against history when a call is active.
@@ -539,6 +633,7 @@
                 mDisconnectTimeMillis = 0;
             } else if (mState == CallState.DISCONNECTED) {
                 mDisconnectTimeMillis = System.currentTimeMillis();
+                mAnalytics.setCallEndTime(mDisconnectTimeMillis);
                 setLocallyDisconnecting(false);
                 fixParentAfterDisconnect();
             }
@@ -601,7 +696,8 @@
         return mRingbackRequested;
     }
 
-    boolean isConference() {
+    @VisibleForTesting
+    public boolean isConference() {
         return mIsConference;
     }
 
@@ -609,6 +705,10 @@
         return mHandle;
     }
 
+    public String getPostDialDigits() {
+        return mPostDialDigits;
+    }
+
     int getHandlePresentation() {
         return mHandlePresentation;
     }
@@ -689,6 +789,7 @@
     public void setDisconnectCause(DisconnectCause disconnectCause) {
         // TODO: Consider combining this method with a setDisconnected() method that is totally
         // separate from setState.
+        mAnalytics.setCallDisconnectCause(disconnectCause);
         mDisconnectCause = disconnectCause;
     }
 
@@ -696,7 +797,8 @@
         return mDisconnectCause;
     }
 
-    boolean isEmergencyCall() {
+    @VisibleForTesting
+    public boolean isEmergencyCall() {
         return mIsEmergencyCall;
     }
 
@@ -711,7 +813,8 @@
         return getHandle();
     }
 
-    GatewayInfo getGatewayInfo() {
+    @VisibleForTesting
+    public GatewayInfo getGatewayInfo() {
         return mGatewayInfo;
     }
 
@@ -719,11 +822,13 @@
         mGatewayInfo = gatewayInfo;
     }
 
-    PhoneAccountHandle getConnectionManagerPhoneAccount() {
+    @VisibleForTesting
+    public PhoneAccountHandle getConnectionManagerPhoneAccount() {
         return mConnectionManagerPhoneAccountHandle;
     }
 
-    void setConnectionManagerPhoneAccount(PhoneAccountHandle accountHandle) {
+    @VisibleForTesting
+    public void setConnectionManagerPhoneAccount(PhoneAccountHandle accountHandle) {
         if (!Objects.equals(mConnectionManagerPhoneAccountHandle, accountHandle)) {
             mConnectionManagerPhoneAccountHandle = accountHandle;
             for (Listener l : mListeners) {
@@ -733,21 +838,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;
     }
 
     /**
@@ -755,7 +891,8 @@
      *     period since this call was added to the set pending outgoing calls, see
      *     mCreationTimeMillis.
      */
-    long getAgeMillis() {
+    @VisibleForTesting
+    public long getAgeMillis() {
         if (mState == CallState.DISCONNECTED &&
                 (mDisconnectCause.getCode() == DisconnectCause.REJECTED ||
                  mDisconnectCause.getCode() == DisconnectCause.MISSED)) {
@@ -808,23 +945,28 @@
         }
     }
 
-    Call getParentCall() {
+    @VisibleForTesting
+    public Call getParentCall() {
         return mParentCall;
     }
 
-    List<Call> getChildCalls() {
+    @VisibleForTesting
+    public List<Call> getChildCalls() {
         return mChildCalls;
     }
 
-    boolean wasConferencePreviouslyMerged() {
+    @VisibleForTesting
+    public boolean wasConferencePreviouslyMerged() {
         return mWasConferencePreviouslyMerged;
     }
 
-    Call getConferenceLevelActiveCall() {
+    @VisibleForTesting
+    public Call getConferenceLevelActiveCall() {
         return mConferenceLevelActiveCall;
     }
 
-    ConnectionServiceWrapper getConnectionService() {
+    @VisibleForTesting
+    public ConnectionServiceWrapper getConnectionService() {
         return mConnectionService;
     }
 
@@ -837,13 +979,15 @@
         return mContext;
     }
 
-    void setConnectionService(ConnectionServiceWrapper service) {
+    @VisibleForTesting
+    public void setConnectionService(ConnectionServiceWrapper service) {
         Preconditions.checkNotNull(service);
 
         clearConnectionService();
 
         service.incrementAssociatedCallCount();
         mConnectionService = service;
+        mAnalytics.setCallConnectionService(service.getComponentName().flattenToShortString());
         mConnectionService.addCall(this);
     }
 
@@ -868,20 +1012,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;
@@ -895,7 +1039,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();
@@ -923,26 +1073,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;
         }
     }
 
@@ -952,18 +1113,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;
         }
     }
 
@@ -1006,14 +1171,16 @@
         }
     }
 
-    void disconnect() {
+    @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.
@@ -1074,7 +1241,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
@@ -1095,7 +1263,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
@@ -1134,7 +1303,8 @@
     }
 
     /** Checks if this is a live call or not. */
-    boolean isAlive() {
+    @VisibleForTesting
+    public boolean isAlive() {
         switch (mState) {
             case CallState.NEW:
             case CallState.RINGING:
@@ -1172,7 +1342,8 @@
     /**
      * @return the uri of the contact associated with this call.
      */
-    Uri getContactUri() {
+    @VisibleForTesting
+    public Uri getContactUri() {
         if (mCallerInfo == null || !mCallerInfo.contactExists) {
             return getHandle();
         }
@@ -1217,7 +1388,8 @@
         }
     }
 
-    void mergeConference() {
+    @VisibleForTesting
+    public void mergeConference() {
         if (mConnectionService == null) {
             Log.w(this, "merging conference calls without a connection service.");
         } else if (can(Connection.CAPABILITY_MERGE_CONFERENCE)) {
@@ -1227,7 +1399,8 @@
         }
     }
 
-    void swapConference() {
+    @VisibleForTesting
+    public void swapConference() {
         if (mConnectionService == null) {
             Log.w(this, "swapping conference calls without a connection service.");
         } else if (can(Connection.CAPABILITY_SWAP_CONFERENCE)) {
@@ -1285,11 +1458,13 @@
         }
     }
 
-    List<Call> getConferenceableCalls() {
+    @VisibleForTesting
+    public List<Call> getConferenceableCalls() {
         return mConferenceableCalls;
     }
 
-    boolean can(int capability) {
+    @VisibleForTesting
+    public boolean can(int capability) {
         return (mConnectionCapabilities & capability) == capability;
     }
 
@@ -1416,17 +1591,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());
         }
     }
 
@@ -1446,15 +1631,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);
@@ -1466,7 +1661,7 @@
         Trace.endSection();
     }
 
-    CallerInfo getCallerInfo() {
+    public CallerInfo getCallerInfo() {
         return mCallerInfo;
     }
 
@@ -1489,7 +1684,9 @@
     }
 
     private void maybeLoadCannedSmsResponses() {
-        if (mIsIncoming && isRespondViaSmsCapable() && !mCannedSmsResponsesLoadingStarted) {
+        if (mCallDirection == CALL_DIRECTION_INCOMING
+                && isRespondViaSmsCapable()
+                && !mCannedSmsResponsesLoadingStarted) {
             Log.d(this, "maybeLoadCannedSmsResponses: starting task to load messages");
             mCannedSmsResponsesLoadingStarted = true;
             mCallsManager.getRespondViaSmsManager().loadCannedTextMessages(
@@ -1641,11 +1838,7 @@
     }
 
     public boolean isUnknown() {
-        return mIsUnknown;
-    }
-
-    public void setIsUnknown(boolean isUnknown) {
-        mIsUnknown = isUnknown;
+        return mCallDirection == CALL_DIRECTION_UNKNOWN;
     }
 
     /**
@@ -1666,6 +1859,22 @@
         mIsLocallyDisconnecting = isLocallyDisconnecting;
     }
 
+    /**
+     * @return user handle of user initiating the outgoing call.
+     */
+    public UserHandle getInitiatingUser() {
+        return mInitiatingUser;
+    }
+
+    /**
+     * Set the user handle of user initiating the outgoing call.
+     * @param initiatingUser
+     */
+    public void setInitiatingUser(UserHandle initiatingUser) {
+        Preconditions.checkNotNull(initiatingUser);
+        mInitiatingUser = initiatingUser;
+    }
+
     static int getStateFromConnectionState(int state) {
         switch (state) {
             case Connection.STATE_INITIALIZING:
@@ -1694,4 +1903,66 @@
     public boolean isDisconnected() {
         return (getState() == CallState.DISCONNECTED || getState() == CallState.ABORTED);
     }
+
+    /**
+     * Sets the call data usage for the call.
+     *
+     * @param callDataUsage The new call data usage (in bytes).
+     */
+    public void setCallDataUsage(long callDataUsage) {
+        mCallDataUsage = callDataUsage;
+    }
+
+    /**
+     * Returns the call data usage for the call.
+     *
+     * @return The call data usage (in bytes).
+     */
+    public long getCallDataUsage() {
+        return mCallDataUsage;
+    }
+
+    /**
+     * 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;
+    }
+
+    /**
+     * Determines if the call has been held by the remote party.
+     *
+     * @return {@code true} if the call is remotely held, {@code false} otherwise.
+     */
+    public boolean isRemotelyHeld() {
+        return mIsRemotelyHeld;
+    }
+
+    /**
+     * Handles Connection events received from a {@link ConnectionService}.
+     *
+     * @param event The event.
+     */
+    public void onConnectionEvent(String event) {
+        if (Connection.EVENT_ON_HOLD_TONE_START.equals(event)) {
+            mIsRemotelyHeld = true;
+            Log.event(this, Log.Events.REMOTELY_HELD);
+            // Inform listeners of the fact that a call hold tone was received.  This will trigger
+            // the CallAudioManager to play a tone via the InCallTonePlayer.
+            for (Listener l : mListeners) {
+                l.onHoldToneRequested(this);
+            }
+        } else if (Connection.EVENT_ON_HOLD_TONE_END.equals(event)) {
+            mIsRemotelyHeld = false;
+            Log.event(this, Log.Events.REMOTELY_UNHELD);
+            for (Listener l : mListeners) {
+                l.onHoldToneRequested(this);
+            }
+        }
+    }
 }
diff --git a/src/com/android/server/telecom/CallAudioManager.java b/src/com/android/server/telecom/CallAudioManager.java
index 23284e3..6f6773e 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,294 @@
  * 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;
+    private InCallTonePlayer mHoldTonePlayer;
 
-    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();
+        }
+    }
+
+    /**
+     * Play or stop a call hold tone for a call.  Triggered via
+     * {@link Connection#sendConnectionEvent(String)} when the
+     * {@link Connection#EVENT_ON_HOLD_TONE_START} event or
+     * {@link Connection#EVENT_ON_HOLD_TONE_STOP} event is passed through to the
+     *
+     * @param call The call which requested the hold tone.
+     */
+    @Override
+    public void onHoldToneRequested(Call call) {
+        maybePlayHoldTone();
     }
 
     @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 getPossiblyHeldForegroundCall() {
+        return mForegroundCall;
+    }
+
+    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 +307,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 +317,310 @@
      * @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);
+            maybePlayHoldTone();
+        }
+    }
+
+    @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);
+    }
+
+    /**
+     * Determines if a hold tone should be played and then starts or stops it accordingly.
+     */
+    private void maybePlayHoldTone() {
+        if (shouldPlayHoldTone()) {
+            if (mHoldTonePlayer == null) {
+                mHoldTonePlayer = mPlayerFactory.createPlayer(InCallTonePlayer.TONE_CALL_WAITING);
+                mHoldTonePlayer.start();
+            }
+        } else {
+            if (mHoldTonePlayer != null) {
+                mHoldTonePlayer.stopTone();
+                mHoldTonePlayer = null;
+            }
+        }
+    }
+
+    /**
+     * Determines if a hold tone should be played.
+     * A hold tone should be played only if foreground call is equals with call which is
+     * remotely held.
+     *
+     * @return {@code true} if the the hold tone should be played, {@code false} otherwise.
+     */
+    private boolean shouldPlayHoldTone() {
+        Call foregroundCall = getForegroundCall();
+        // If there is no foreground call, no hold tone should play.
+        if (foregroundCall == null) {
+            return false;
+        }
+
+        // If another call is ringing, no hold tone should play.
+        if (mCallsManager.hasRingingCall()) {
+            return false;
+        }
+
+        // If the foreground call isn't active, no hold tone should play. This might happen, for
+        // example, if the user puts a remotely held call on hold itself.
+        if (!foregroundCall.isActive()) {
+            return false;
+        }
+
+        return foregroundCall.isRemotelyHeld();
+    }
+
+    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 a6840b9..3a327c8 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;
 
@@ -64,9 +96,6 @@
             Context context,
             CallsManager callsManager,
             Intent intent) {
-        if (shouldPreventDuplicateVideoCall(context, callsManager, intent)) {
-            return;
-        }
 
         Uri handle = intent.getData();
         String scheme = handle.getScheme();
@@ -96,8 +125,17 @@
 
         final boolean isPrivilegedDialer = intent.getBooleanExtra(KEY_IS_PRIVILEGED_DIALER, false);
 
+        boolean fixedInitiatingUser = fixInitiatingUserIfNecessary(context, intent);
+        // Show the toast to warn user that it is a personal call though initiated in work profile.
+        if (fixedInitiatingUser) {
+            Toast.makeText(context, R.string.toast_personal_call_msg, Toast.LENGTH_LONG).show();
+        }
+
+        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
@@ -106,7 +144,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;
 
@@ -116,6 +155,29 @@
         }
     }
 
+    /**
+     * If the call is initiated from managed profile but there is no work dialer installed, treat
+     * the call is initiated from its parent user.
+     *
+     * @return whether the initiating user is fixed.
+     */
+    static boolean 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);
+                return true;
+            }
+        }
+        return false;
+    }
+
     static void processIncomingCallIntent(CallsManager callsManager, Intent intent) {
         PhoneAccountHandle phoneAccountHandle = intent.getParcelableExtra(
                 TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
@@ -178,31 +240,4 @@
             context.startActivityAsUser(errorIntent, UserHandle.CURRENT);
         }
     }
-
-    /**
-     * Whether an outgoing video call should be prevented from going out. Namely, don't allow an
-     * outgoing video call if there is already an ongoing video call. Notify the user if their call
-     * is not sent.
-     *
-     * @return {@code true} if the outgoing call is a video call and should be prevented from going
-     *     out, {@code false} otherwise.
-     */
-    private static boolean shouldPreventDuplicateVideoCall(
-            Context context,
-            CallsManager callsManager,
-            Intent intent) {
-        int intentVideoState = intent.getIntExtra(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
-                VideoProfile.STATE_AUDIO_ONLY);
-        if (VideoProfile.isAudioOnly(intentVideoState)
-                || !callsManager.hasVideoCall()) {
-            return false;
-        } else {
-            // Display an error toast to the user.
-            Toast.makeText(
-                    context,
-                    context.getResources().getString(R.string.duplicate_video_call_not_allowed),
-                    Toast.LENGTH_LONG).show();
-            return true;
-        }
-    }
 }
diff --git a/src/com/android/server/telecom/CallLogManager.java b/src/com/android/server/telecom/CallLogManager.java
index 1fe491e..e808b64 100755
--- a/src/com/android/server/telecom/CallLogManager.java
+++ b/src/com/android/server/telecom/CallLogManager.java
@@ -20,13 +20,18 @@
 import android.content.Intent;
 import android.net.Uri;
 import android.os.AsyncTask;
+import android.os.UserHandle;
+import android.os.PersistableBundle;
 import android.provider.CallLog.Calls;
 import android.telecom.DisconnectCause;
+import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.VideoProfile;
+import android.telephony.CarrierConfigManager;
 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 +39,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 +57,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 +72,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 +87,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 +101,9 @@
     private static final String CALL_TYPE = "callType";
     private static final String CALL_DURATION = "duration";
 
-    public CallLogManager(Context context) {
+    public CallLogManager(Context context, PhoneAccountRegistrar phoneAccountRegistrar) {
         mContext = context;
+        mPhoneAccountRegistrar = phoneAccountRegistrar;
     }
 
     @Override
@@ -145,11 +158,13 @@
             accountHandle = null;
         }
 
-        // TODO(vt): Once data usage is available, wire it up here.
+        Long callDataUsage = call.getCallDataUsage() == Call.DATA_USAGE_NOT_SET ? null :
+                call.getCallDataUsage();
+
         int callFeatures = getCallFeatures(call.getVideoStateHistory());
-        logCall(call.getCallerInfo(), logNumber, call.getHandlePresentation(),
-                callLogType, callFeatures, accountHandle, creationTime, age, null,
-                call.isEmergencyCall());
+        logCall(call.getCallerInfo(), logNumber, call.getPostDialDigits(),
+                call.getHandlePresentation(), callLogType, callFeatures, accountHandle,
+                creationTime, age, callDataUsage, call.isEmergencyCall(), call.getInitiatingUser());
     }
 
     /**
@@ -157,6 +172,8 @@
      *
      * @param callerInfo Caller details.
      * @param number The number the call was made to or from.
+     * @param postDialDigits The post-dial digits that were dialed after the number,
+     *                       if it was an outgoing call. Otherwise ''.
      * @param presentation
      * @param callType The type of call.
      * @param features The features of the call.
@@ -168,6 +185,7 @@
     private void logCall(
             CallerInfo callerInfo,
             String number,
+            String postDialDigits,
             int presentation,
             int callType,
             int features,
@@ -175,13 +193,20 @@
             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
         // on carrier requirements.)
-        final boolean okToLogEmergencyNumber =
-                mContext.getResources().getBoolean(R.bool.allow_emergency_numbers_in_call_log);
+        boolean okToLogEmergencyNumber = false;
+        CarrierConfigManager configManager = (CarrierConfigManager) mContext.getSystemService(
+                Context.CARRIER_CONFIG_SERVICE);
+        PersistableBundle configBundle = configManager.getConfig();
+        if (configBundle != null) {
+            okToLogEmergencyNumber = configBundle.getBoolean(
+                    CarrierConfigManager.KEY_ALLOW_EMERGENCY_NUMBERS_IN_CALL_LOG_BOOL);
+        }
 
         // Don't log emergency numbers if the device doesn't allow it.
         final boolean isOkToLogThisCall = !isEmergency || okToLogEmergencyNumber;
@@ -192,8 +217,9 @@
             Log.d(TAG, "Logging Calllog entry: " + callerInfo + ", "
                     + Log.pii(number) + "," + presentation + ", " + callType
                     + ", " + start + ", " + duration);
-            AddCallArgs args = new AddCallArgs(mContext, callerInfo, number, presentation,
-                    callType, features, accountHandle, start, duration, dataUsage);
+            AddCallArgs args = new AddCallArgs(mContext, callerInfo, number, postDialDigits,
+                    presentation, callType, features, accountHandle, start, duration, dataUsage,
+                    initiatingUser);
             logCallAsync(args);
         } else {
           Log.d(TAG, "Not adding emergency call to call log.");
@@ -257,12 +283,9 @@
             Uri[] result = new Uri[count];
             for (int i = 0; i < count; i++) {
                 AddCallArgs c = callList[i];
-
                 try {
                     // May block.
-                    result[i] = Calls.addCall(c.callerInfo, c.context, c.number, c.presentation,
-                            c.callType, c.features, c.accountHandle, c.timestamp, c.durationInSec,
-                            c.dataUsage, true /* addForAllUsers */);
+                    result[i] = addCall(c);
                 } catch (Exception e) {
                     // This is very rare but may happen in legitimate cases.
                     // E.g. If the phone is encrypted and thus write request fails, it may cause
@@ -278,6 +301,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 2044a2c..e7f68ec 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -17,12 +17,19 @@
 package com.android.server.telecom;
 
 import android.content.Context;
+import android.content.pm.UserInfo;
+import android.content.Intent;
+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;
@@ -44,11 +51,14 @@
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.TelephonyProperties;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.telecom.components.ErrorDialogActivity;
 
 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;
@@ -61,10 +71,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);
@@ -74,7 +86,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);
@@ -82,6 +93,7 @@
         void onVideoStateChanged(Call call);
         void onCanAddCallChanged(boolean canAddCall);
         void onSessionModifyRequestReceived(Call call, VideoProfile videoProfile);
+        void onHoldToneRequested(Call call);
     }
 
     private static final String TAG = "CallsManager";
@@ -98,6 +110,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
@@ -110,6 +135,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;
@@ -123,6 +155,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;
@@ -141,12 +174,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;
 
     /**
@@ -161,7 +188,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;
@@ -169,38 +200,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) {
@@ -242,18 +305,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);
+            }
         }
     }
 
@@ -295,30 +403,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 {
@@ -361,9 +471,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);
@@ -371,7 +481,7 @@
                     }
                 }
             }
-        }, Timeouts.getNewOutgoingCallCancelMillis(mContext.getContentResolver()));
+        }.prepare(), Timeouts.getNewOutgoingCallCancelMillis(mContext.getContentResolver()));
 
         return true;
     }
@@ -415,16 +525,36 @@
         }
     }
 
-    Collection<Call> getCalls() {
+    public Collection<Call> getCalls() {
         return Collections.unmodifiableCollection(mCalls);
     }
 
-    Call getForegroundCall() {
-        return mForegroundCall;
+    /**
+     * Play or stop a call hold tone for a call.  Triggered via
+     * {@link Connection#sendConnectionEvent(String)} when the
+     * {@link Connection#EVENT_ON_HOLD_TONE_START} event or
+     * {@link Connection#EVENT_ON_HOLD_TONE_STOP} event is passed through to the
+     *
+     * @param call The call which requested the hold tone.
+     */
+    @Override
+    public void onHoldToneRequested(Call call) {
+        for (CallsManagerListener listener : mListeners) {
+            listener.onHoldToneRequested(call);
+        }
     }
 
-    Ringer getRinger() {
-        return mRinger;
+    @VisibleForTesting
+    public Call getForegroundCall() {
+        if (mCallAudioManager == null) {
+            // Happens when getForegroundCall is called before full initialization.
+            return null;
+        }
+        return mCallAudioManager.getForegroundCall();
+    }
+
+    CallAudioManager getCallAudioManager() {
+        return mCallAudioManager;
     }
 
     InCallController getInCallController() {
@@ -470,7 +600,8 @@
         return mTtyManager.getCurrentTtyMode();
     }
 
-    void addListener(CallsManagerListener listener) {
+    @VisibleForTesting
+    public void addListener(CallsManagerListener listener) {
         mListeners.add(listener);
     }
 
@@ -493,6 +624,7 @@
             handle = extras.getParcelable(TelephonyManager.EXTRA_INCOMING_NUMBER);
         }
         Call call = new Call(
+                getNextCallId(),
                 mContext,
                 this,
                 mLock,
@@ -503,8 +635,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()
@@ -516,6 +656,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,
@@ -526,11 +667,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);
@@ -550,8 +694,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) {
@@ -564,25 +708,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;
     }
 
     /**
@@ -592,17 +719,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.
@@ -629,7 +783,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);
@@ -637,13 +792,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;
@@ -671,7 +829,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);
         }
 
@@ -687,8 +845,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.");
@@ -706,20 +865,26 @@
 
         call.setHandle(uriHandle);
         call.setGatewayInfo(gatewayInfo);
+
+        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);
@@ -733,8 +898,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"));
@@ -748,7 +913,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);
     }
 
@@ -760,22 +926,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();
@@ -786,8 +954,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.
@@ -801,15 +969,32 @@
             // We do not update the UI until we get confirmation of the answer() through
             // {@link #markCallAsActive}.
             call.answer(videoState);
-            if (VideoProfile.isVideo(videoState) &&
-                !mWiredHeadsetManager.isPluggedIn() &&
-                !mCallAudioManager.isBluetoothDeviceAvailable() &&
-                isSpeakerEnabledForVideoCalls()) {
+            if (isSpeakerphoneAutoEnabled(videoState)) {
                 call.setStartWithSpeakerphoneOn(true);
             }
         }
     }
 
+    /**
+     * Determines if the speakerphone should be automatically enabled for the call.  Speakerphone
+     * should be enabled if the call is a video call and bluetooth or the wired headset are not in
+     * use.
+     *
+     * @param videoState The video state of the call.
+     * @return {@code true} if the speakerphone should be enabled.
+     */
+    private boolean isSpeakerphoneAutoEnabled(int videoState) {
+        return VideoProfile.isVideo(videoState) &&
+            !mWiredHeadsetManager.isPluggedIn() &&
+            !mBluetoothManager.isBluetoothAvailable() &&
+            isSpeakerEnabledForVideoCalls();
+    }
+
+    /**
+     * Determines if the speakerphone should be automatically enabled for video calls.
+     *
+     * @return {@code true} if the speakerphone should automatically be enabled.
+     */
     private static boolean isSpeakerEnabledForVideoCalls() {
         return (SystemProperties.getInt(TelephonyProperties.PROPERTY_VIDEOCALL_AUDIO_OUTPUT,
                 PhoneConstants.AUDIO_OUTPUT_DEFAULT) ==
@@ -821,7 +1006,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 {
@@ -837,7 +1023,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 {
@@ -849,7 +1036,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 {
@@ -874,7 +1062,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)) {
@@ -902,7 +1091,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 {
@@ -916,7 +1106,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 {
@@ -931,6 +1122,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);
@@ -962,14 +1167,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 */)) {
@@ -979,13 +1182,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);
@@ -1028,8 +1234,9 @@
         removeCall(call);
         if (mLocallyDisconnectingCalls.contains(call)) {
             mLocallyDisconnectingCalls.remove(call);
-            if (mForegroundCall != null && mForegroundCall.getState() == CallState.ON_HOLD) {
-                mForegroundCall.unhold();
+            Call foregroundCall = mCallAudioManager.getPossiblyHeldForegroundCall();
+            if (foregroundCall != null && foregroundCall.getState() == CallState.ON_HOLD) {
+                foregroundCall.unhold();
             }
         }
     }
@@ -1138,7 +1345,8 @@
         return getFirstCallWithState(CallState.RINGING);
     }
 
-    Call getActiveCall() {
+    @VisibleForTesting
+    public Call getActiveCall() {
         return getFirstCallWithState(CallState.ACTIVE);
     }
 
@@ -1146,11 +1354,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) {
@@ -1160,11 +1370,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);
     }
 
@@ -1178,8 +1390,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) {
@@ -1201,6 +1414,7 @@
     }
 
     Call createConferenceCall(
+            String callId,
             PhoneAccountHandle phoneAccount,
             ParcelableConference parcelableConference) {
 
@@ -1212,6 +1426,7 @@
                         parcelableConference.getConnectTimeMillis();
 
         Call call = new Call(
+                callId,
                 mContext,
                 this,
                 mLock,
@@ -1222,7 +1437,8 @@
                 null /* gatewayInfo */,
                 null /* connectionManagerPhoneAccount */,
                 phoneAccount,
-                false /* isIncoming */,
+                Call.CALL_DIRECTION_UNDEFINED /* callDirection */,
+                false /* forceAttachToExistingConnection */,
                 true /* isConference */,
                 connectTime);
 
@@ -1265,6 +1481,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.
@@ -1275,7 +1504,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) {
@@ -1286,7 +1515,6 @@
                 Trace.endSection();
             }
         }
-        updateCallsManagerState();
         Trace.endSection();
     }
 
@@ -1308,6 +1536,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");
@@ -1317,7 +1546,6 @@
                     Trace.endSection();
                 }
             }
-            updateCallsManagerState();
         }
         Trace.endSection();
     }
@@ -1344,10 +1572,12 @@
             // TODO: Define expected state transitions here, and log when an
             // unexpected transition occurs.
             call.setState(newState, tag);
+            maybeShowErrorDialogOnDisconnect(call);
 
             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");
@@ -1357,59 +1587,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) {
@@ -1427,7 +1609,6 @@
     }
 
     private void updateCallsManagerState() {
-        updateForegroundCall();
         updateCanAddCall();
     }
 
@@ -1441,15 +1622,14 @@
      * MMI codes which can be dialed when one or more calls are in progress.
      * <P>
      * Checks for numbers formatted similar to the MMI codes defined in:
-     * {@link com.android.internal.telephony.gsm.GSMPhone#handleInCallMmiCommands(String)}
-     * and
-     * {@link com.android.internal.telephony.imsphone.ImsPhone#handleInCallMmiCommands(String)}
+     * {@link com.android.internal.telephony.Phone#handleInCallMmiCommands(String)}
      *
      * @param handle The URI to call.
      * @return {@code True} if the URI represents a number which could be an in-call MMI code.
      */
     private boolean isPotentialInCallMMICode(Uri handle) {
         if (handle != null && handle.getSchemeSpecificPart() != null &&
+                handle.getScheme() != null &&
                 handle.getScheme().equals(PhoneAccount.SCHEME_TEL)) {
 
             String dialedNumber = handle.getSchemeSpecificPart();
@@ -1499,7 +1679,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);
 
@@ -1516,12 +1696,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;
                 }
@@ -1533,6 +1717,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;
                 }
@@ -1562,6 +1748,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.
@@ -1576,6 +1764,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;
             }
@@ -1621,6 +1811,7 @@
      */
     Call createCallForExistingConnection(String callId, ParcelableConnection connection) {
         Call call = new Call(
+                callId,
                 mContext,
                 this,
                 mLock,
@@ -1631,10 +1822,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());
@@ -1648,6 +1843,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.
@@ -1662,7 +1894,6 @@
             }
             pw.decreaseIndent();
         }
-        pw.println("mForegroundCall: " + (mForegroundCall == null ? "none" : mForegroundCall));
 
         if (mCallAudioManager != null) {
             pw.println("mCallAudioManager:");
@@ -1692,4 +1923,24 @@
             pw.decreaseIndent();
         }
     }
+
+    /**
+    * For some disconnected causes, we show a dialog when it's a mmi code or potential mmi code.
+    *
+    * @param call The call.
+    */
+    private void maybeShowErrorDialogOnDisconnect(Call call) {
+        if (call.getState() == CallState.DISCONNECTED && (isPotentialMMICode(call.getHandle())
+                || isPotentialInCallMMICode(call.getHandle()))) {
+            DisconnectCause disconnectCause = call.getDisconnectCause();
+            if (!TextUtils.isEmpty(disconnectCause.getDescription()) && (disconnectCause.getCode()
+                    == DisconnectCause.ERROR)) {
+                Intent errorIntent = new Intent(mContext, ErrorDialogActivity.class);
+                errorIntent.putExtra(ErrorDialogActivity.ERROR_MESSAGE_STRING_EXTRA,
+                        disconnectCause.getDescription());
+                errorIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                mContext.startActivityAsUser(errorIntent, UserHandle.CURRENT);
+            }
+        }
+    }
 }
diff --git a/src/com/android/server/telecom/CallsManagerListenerBase.java b/src/com/android/server/telecom/CallsManagerListenerBase.java
index 58085a0..71d6c53 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) {
     }
@@ -84,4 +80,8 @@
     public void onSessionModifyRequestReceived(Call call, VideoProfile videoProfile) {
 
     }
+
+    @Override
+    public void onHoldToneRequested(Call call) {
+    }
 }
diff --git a/src/com/android/server/telecom/ConnectionServiceRepository.java b/src/com/android/server/telecom/ConnectionServiceRepository.java
index a587b59..4685704 100644
--- a/src/com/android/server/telecom/ConnectionServiceRepository.java
+++ b/src/com/android/server/telecom/ConnectionServiceRepository.java
@@ -21,6 +21,7 @@
 import android.os.UserHandle;
 import android.util.Pair;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.IndentingPrintWriter;
 
 import java.util.HashMap;
@@ -28,7 +29,8 @@
 /**
  * Searches for and returns connection services.
  */
-final class ConnectionServiceRepository {
+@VisibleForTesting
+public class ConnectionServiceRepository {
     private final HashMap<Pair<ComponentName, UserHandle>, ConnectionServiceWrapper> mServiceCache =
             new HashMap<>();
     private final PhoneAccountRegistrar mPhoneAccountRegistrar;
@@ -57,7 +59,8 @@
         mCallsManager = callsManager;
     }
 
-    ConnectionServiceWrapper getService(ComponentName componentName, UserHandle userHandle) {
+    @VisibleForTesting
+    public ConnectionServiceWrapper getService(ComponentName componentName, UserHandle userHandle) {
         Pair<ComponentName, UserHandle> cacheKey = Pair.create(componentName, userHandle);
         ConnectionServiceWrapper service = mServiceCache.get(cacheKey);
         if (service == null) {
diff --git a/src/com/android/server/telecom/ConnectionServiceWrapper.java b/src/com/android/server/telecom/ConnectionServiceWrapper.java
index 8e58f22..1feb356 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,30 @@
                 }
             } finally {
                 Binder.restoreCallingIdentity(token);
+                Log.endSession();
+            }
+        }
+
+        @Override
+        public void onConnectionEvent(String callId, String event) {
+            Log.startSession("CSW.oCE");
+            long token = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    Call call = mCallIdMapper.getCall(callId);
+                    if (call != null) {
+                        call.onConnectionEvent(event);
+                    }
+                }
+            } 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 +658,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 +689,9 @@
                                     call.getTargetPhoneAccount(),
                                     call.getHandle(),
                                     extras,
-                                    call.getVideoState()),
-                            call.isIncoming(),
+                                    call.getVideoState(),
+                                    callId),
+                            call.shouldAttachToExistingConnection(),
                             call.isUnknown());
                 } catch (RemoteException e) {
                     Log.e(this, e, "Failure to createConnection -- %s", getComponentName());
@@ -745,8 +763,9 @@
         }
     }
 
-    /** @see IConnectionService#onCallAudioStateChanged(String,CallAudioState) */
-    void onCallAudioStateChanged(Call activeCall, CallAudioState audioState) {
+    /** @see IConnectionService#onCallAudioStateChanged(String, CallAudioState) */
+    @VisibleForTesting
+    public void onCallAudioStateChanged(Call activeCall, CallAudioState audioState) {
         final String callId = mCallIdMapper.getCallId(activeCall);
         if (callId != null && isServiceValid("onCallAudioStateChanged")) {
             try {
@@ -803,7 +822,7 @@
         }
     }
 
-    /** @see IConnectionService#playDtmfTone(String,char) */
+    /** @see IConnectionService#playDtmfTone(String, char) */
     void playDtmfTone(Call call, char digit) {
         final String callId = mCallIdMapper.getCallId(call);
         if (callId != null && isServiceValid("playDtmfTone")) {
@@ -820,7 +839,7 @@
         final String callId = mCallIdMapper.getCallId(call);
         if (callId != null && isServiceValid("stopDtmfTone")) {
             try {
-                logOutgoing("stopDtmfTone %s",callId);
+                logOutgoing("stopDtmfTone %s", callId);
                 mServiceInterface.stopDtmfTone(callId);
             } catch (RemoteException e) {
             }
@@ -980,10 +999,11 @@
         Log.d(this, "Telecom -> ConnectionService: " + msg, params);
     }
 
-    private void queryRemoteConnectionServices(final RemoteServiceCallback callback) {
+    private void queryRemoteConnectionServices(final UserHandle userHandle,
+            final RemoteServiceCallback callback) {
         // Only give remote connection services to this connection service if it is listed as
         // the connection manager.
-        PhoneAccountHandle simCallManager = mPhoneAccountRegistrar.getSimCallManager();
+        PhoneAccountHandle simCallManager = mPhoneAccountRegistrar.getSimCallManager(userHandle);
         Log.d(this, "queryRemoteConnectionServices finds simCallManager = %s", simCallManager);
         if (simCallManager == null ||
                 !simCallManager.getComponentName().equals(getComponentName())) {
@@ -994,7 +1014,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 9562ea7..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,241 +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
-    };
-
-    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.
      */
@@ -825,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.
@@ -851,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 21fcf89..8daf6c1 100644
--- a/src/com/android/server/telecom/Log.java
+++ b/src/com/android/server/telecom/Log.java
@@ -16,26 +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;
 
 /**
@@ -69,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";
@@ -83,7 +92,17 @@
         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";
+        public static final String REMOTELY_HELD = "REMOTELY_HELD";
+        public static final String REMOTELY_UNHELD = "REMOTELY_UNHELD";
 
         /**
          * Maps from a request to a response.  The same event could be listed as the
@@ -100,16 +119,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;
         }
@@ -120,27 +143,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("]");
@@ -176,7 +197,7 @@
                         // ID instead.
                         CallEventRecord record = mCallEventRecordMap.get(data);
                         if (record != null) {
-                            data = "Call " + record.mId;
+                            data = "Call " + record.mCall.getId();
                         }
                     }
 
@@ -194,6 +215,8 @@
                     pw.print(event.time - requestEvent.time);
                     pw.print(" ms");
                 }
+                pw.print(":");
+                pw.print(event.sessionId);
                 pw.println();
             }
             pw.decreaseIndent();
@@ -201,10 +224,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 */
@@ -215,12 +247,80 @@
     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 static MessageDigest sMessageDigest;
+    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());
+        }
+    };
 
-    private Log() {}
+    // 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>() {
@@ -243,33 +343,309 @@
         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;
         }
     }
 
@@ -278,83 +654,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) {
@@ -444,6 +832,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
@@ -453,6 +851,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 4297234..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,8 +28,8 @@
 import android.graphics.BitmapFactory;
 import android.graphics.drawable.Icon;
 import android.net.Uri;
+import android.os.Bundle;
 import android.os.AsyncTask;
-import android.os.Binder;
 import android.os.PersistableBundle;
 import android.os.Process;
 import android.os.UserHandle;
@@ -75,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;
 
 /**
@@ -96,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");
@@ -122,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";
@@ -172,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 =
@@ -190,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
@@ -216,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;
     }
@@ -232,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);
@@ -258,7 +276,8 @@
                 mSubscriptionManager.setDefaultVoiceSubId(subId);
             }
 
-            mState.defaultOutgoing = accountHandle;
+            mState.defaultOutgoingAccountHandles
+                    .put(userHandle, new DefaultPhoneAccountHandle(userHandle, accountHandle));
         }
 
         write();
@@ -267,27 +286,7 @@
 
     boolean isUserSelectedSmsPhoneAccount(PhoneAccountHandle accountHandle) {
         return getSubscriptionIdForPhoneAccount(accountHandle) ==
-                SubscriptionManager.getDefaultSmsSubId();
-    }
-
-    /**
-     * Returns the {@link PhoneAccountHandle} corresponding to the currently active SIM Call
-     * Manager. SIM Call Manager returned corresponds to the following priority order:
-     * 1. If a SIM Call Manager {@link PhoneAccount} is registered for the same package as the
-     * default dialer, then that one is returned.
-     * 2. If there is a SIM Call Manager {@link PhoneAccount} registered which matches the
-     * carrier configuration's default, then that one is returned.
-     * 3. Otherwise, we return null.
-     */
-    public PhoneAccountHandle getSimCallManager() {
-        long token = Binder.clearCallingIdentity();
-        int user;
-        try {
-            user = ActivityManager.getCurrentUser();
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-        return getSimCallManager(user);
+                SubscriptionManager.getDefaultSmsSubscriptionId();
     }
 
     public ComponentName getSystemSimCallManagerComponent() {
@@ -303,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:
@@ -312,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();
@@ -326,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();
 
@@ -354,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.
@@ -377,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;
@@ -389,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)) {
@@ -412,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,
@@ -465,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);
     }
 
     /**
@@ -481,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
@@ -537,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();
@@ -557,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();
@@ -619,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;
@@ -700,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;
@@ -714,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);
     }
 
     /**
@@ -740,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;
@@ -755,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);
     }
 
     /**
@@ -776,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)) {
@@ -808,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;
             }
@@ -827,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.
@@ -844,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.
@@ -851,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) {
@@ -967,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
@@ -1009,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);
         }
@@ -1055,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;
 
@@ -1065,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);
@@ -1075,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);
@@ -1102,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) {
@@ -1125,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)) {
@@ -1153,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";
@@ -1169,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
@@ -1192,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);
@@ -1216,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)) {
@@ -1261,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);
                     }
                 }
 
@@ -1326,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 b57090d..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,172 +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() {
-        for (Call call : mRingingCalls) {
-            call.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;
         }
     }
 
@@ -255,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();
@@ -300,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 0f0d221..77d9cfe 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,29 @@
 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;
 import android.util.EventLog;
 
 // 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 com.android.server.telecom.settings.BlockedNumbersActivity;
 
 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,37 +65,69 @@
  * 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;
 
     private final ITelecomService.Stub mBinderImpl = new ITelecomService.Stub() {
         @Override
         public PhoneAccountHandle getDefaultOutgoingPhoneAccount(String uriScheme,
                 String callingPackage) {
-            synchronized (mLock) {
-                if (!canReadPhoneState(callingPackage, "getDefaultOutgoingPhoneAccount")) {
-                    return null;
-                }
-
-                long token = Binder.clearCallingIdentity();
-                try {
-                    PhoneAccountHandle defaultOutgoingPhoneAccount =
-                            mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(uriScheme);
-                    // Make sure that the calling user can see this phone account.
-                    // TODO: Does this isVisible check actually work considering we are clearing
-                    // the calling identity?
-                    if (defaultOutgoingPhoneAccount != null
-                            && !isVisibleToCaller(defaultOutgoingPhoneAccount)) {
-                        Log.w(this, "No account found for the calling user");
+            try {
+                Log.startSession("TSI.gDOPA");
+                synchronized (mLock) {
+                    if (!canReadPhoneState(callingPackage, "getDefaultOutgoingPhoneAccount")) {
                         return null;
                     }
-                    return defaultOutgoingPhoneAccount;
-                } catch (Exception e) {
-                    Log.e(this, e, "getDefaultOutgoingPhoneAccount");
-                    throw e;
-                } finally {
-                    Binder.restoreCallingIdentity(token);
+
+                    final UserHandle callingUserHandle = Binder.getCallingUserHandle();
+                    long token = Binder.clearCallingIdentity();
+                    try {
+                        return mPhoneAccountRegistrar
+                                .getOutgoingPhoneAccountForScheme(uriScheme, callingUserHandle);
+                    } catch (Exception e) {
+                        Log.e(this, e, "getDefaultOutgoingPhoneAccount");
+                        throw e;
+                    } finally {
+                        Binder.restoreCallingIdentity(token);
+                    }
                 }
+            } finally {
+                Log.endSession();
             }
         }
 
@@ -99,94 +135,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();
                 }
             }
         }
@@ -194,16 +245,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();
                 }
             }
         }
@@ -212,11 +270,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();
                 }
             }
         }
@@ -224,20 +285,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();
                 }
             }
         }
@@ -245,92 +303,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();
             }
         }
 
@@ -338,6 +408,7 @@
         public void unregisterPhoneAccount(PhoneAccountHandle accountHandle) {
             synchronized (mLock) {
                 try {
+                    Log.startSession("TSI.uPA");
                     enforcePhoneAccountModificationForPackage(
                             accountHandle.getComponentName().getPackageName());
                     enforceUserHandleMatchesCaller(accountHandle);
@@ -348,7 +419,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);
@@ -358,6 +430,8 @@
                 } catch (Exception e) {
                     Log.e(this, e, "unregisterPhoneAccount %s", accountHandle);
                     throw e;
+                } finally {
+                    Log.endSession();
                 }
             }
         }
@@ -366,12 +440,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();
                 }
             }
         }
@@ -382,25 +459,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();
             }
         }
 
@@ -409,27 +491,33 @@
          */
         @Override
         public String getVoiceMailNumber(PhoneAccountHandle accountHandle, String callingPackage) {
-            synchronized (mLock) {
-                if (!canReadPhoneState(callingPackage, "getVoiceMailNumber")) {
-                    return null;
-                }
-
-                try {
-                    if (!isVisibleToCaller(accountHandle)) {
-                        Log.d(this, "%s is not visible for the calling user [gVMN]", accountHandle);
+            try {
+                Log.startSession("TSI.gVMN");
+                synchronized (mLock) {
+                    if (!canReadPhoneState(callingPackage, "getVoiceMailNumber")) {
                         return null;
                     }
-
-                    int subId = SubscriptionManager.getDefaultVoiceSubId();
-                    if (accountHandle != null) {
-                        subId = mPhoneAccountRegistrar
-                                .getSubscriptionIdForPhoneAccount(accountHandle);
+                    try {
+                        final UserHandle callingUserHandle = Binder.getCallingUserHandle();
+                        if (!isPhoneAccountHandleVisibleToCallingUser(accountHandle,
+                                callingUserHandle)) {
+                            Log.d(this, "%s is not visible for the calling user [gVMN]",
+                                    accountHandle);
+                            return null;
+                        }
+                        int subId = 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();
             }
         }
 
@@ -438,27 +526,34 @@
          */
         @Override
         public String getLine1Number(PhoneAccountHandle accountHandle, String callingPackage) {
-            if (!canReadPhoneState(callingPackage, "getLine1Number")) {
-                return null;
-            }
-
-            synchronized (mLock) {
-                if (!isVisibleToCaller(accountHandle)) {
-                    Log.d(this, "%s is not visible for the calling user [gL1N]", accountHandle);
+            try {
+                Log.startSession("getL1N");
+                if (!canReadPhoneState(callingPackage, "getLine1Number")) {
                     return null;
                 }
 
-                long token = Binder.clearCallingIdentity();
-                try {
-                    int subId =
-                            mPhoneAccountRegistrar.getSubscriptionIdForPhoneAccount(accountHandle);
-                    return getTelephonyManager().getLine1NumberForSubscriber(subId);
-                } catch (Exception e) {
-                    Log.e(this, e, "getSubscriptionIdForPhoneAccount");
-                    throw e;
-                } finally {
-                    Binder.restoreCallingIdentity(token);
+                synchronized (mLock) {
+                    final UserHandle callingUserHandle = Binder.getCallingUserHandle();
+                    if (!isPhoneAccountHandleVisibleToCallingUser(accountHandle,
+                            callingUserHandle)) {
+                        Log.d(this, "%s is not visible for the calling user [gL1N]", accountHandle);
+                        return null;
+                    }
+
+                    long token = Binder.clearCallingIdentity();
+                    try {
+                        int subId = mPhoneAccountRegistrar.getSubscriptionIdForPhoneAccount(
+                                accountHandle);
+                        return getTelephonyManager().getLine1Number(subId);
+                    } catch (Exception e) {
+                        Log.e(this, e, "getSubscriptionIdForPhoneAccount");
+                        throw e;
+                    } finally {
+                        Binder.restoreCallingIdentity(token);
+                    }
                 }
+            } finally {
+                Log.endSession();
             }
         }
 
@@ -467,16 +562,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();
             }
         }
 
@@ -487,11 +588,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();
+            }
         }
 
         /**
@@ -502,11 +608,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();
             }
         }
 
@@ -515,7 +626,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();
+            }
         }
 
         /**
@@ -523,14 +639,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();
             }
         }
 
@@ -539,12 +660,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();
             }
         }
 
@@ -553,8 +684,13 @@
          */
         @Override
         public int getCallState() {
-            synchronized (mLock) {
-                return mCallsManager.getCallState();
+            try {
+                Log.startSession("TSI.getCallState");
+                synchronized (mLock) {
+                    return mCallsManager.getCallState();
+                }
+            } finally {
+                Log.endSession();
             }
         }
 
@@ -563,15 +699,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();
             }
         }
 
@@ -580,15 +721,43 @@
          */
         @Override
         public void acceptRingingCall() {
-            synchronized (mLock) {
-                enforceModifyPermission();
+            try {
+                Log.startSession("TSI.aRC");
+                synchronized (mLock) {
+                    enforceModifyPermission();
 
-                long token = Binder.clearCallingIdentity();
-                try {
-                    acceptRingingCallInternal();
-                } finally {
-                    Binder.restoreCallingIdentity(token);
+                    long token = Binder.clearCallingIdentity();
+                    try {
+                        acceptRingingCallInternal(DEFAULT_VIDEO_STATE);
+                    } finally {
+                        Binder.restoreCallingIdentity(token);
+                    }
                 }
+            } finally {
+                Log.endSession();
+            }
+        }
+
+        /**
+         * @see android.telecom.TelecomManager#acceptRingingCall(int)
+         *
+         */
+        @Override
+        public void acceptRingingCallWithVideoState(int videoState) {
+            try {
+                Log.startSession("TSI.aRCWVS");
+                synchronized (mLock) {
+                    enforceModifyPermission();
+
+                    long token = Binder.clearCallingIdentity();
+                    try {
+                        acceptRingingCallInternal(videoState);
+                    } finally {
+                        Binder.restoreCallingIdentity(token);
+                    }
+                }
+            } finally {
+                Log.endSession();
             }
         }
 
@@ -597,18 +766,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();
             }
         }
 
@@ -617,35 +791,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();
             }
         }
 
@@ -653,30 +838,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();
             }
         }
 
@@ -686,26 +876,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();
             }
         }
 
@@ -714,12 +910,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();
             }
         }
 
@@ -728,12 +929,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();
             }
         }
 
@@ -742,40 +948,49 @@
          */
         @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);
-                        enforcePhoneAccountIsRegisteredEnabled(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);
+                            enforcePhoneAccountIsRegisteredEnabled(phoneAccountHandle,
+                                    Binder.getCallingUserHandle());
                         }
-                        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();
             }
         }
 
@@ -784,32 +999,39 @@
          */
         @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);
-                    enforcePhoneAccountIsRegisteredEnabled(phoneAccountHandle);
-                    long token = Binder.clearCallingIdentity();
+                        // Make sure it doesn't cross the UserHandle boundary
+                        enforceUserHandleMatchesCaller(phoneAccountHandle);
+                        enforcePhoneAccountIsRegisteredEnabled(phoneAccountHandle,
+                                Binder.getCallingUserHandle());
+                        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();
             }
         }
 
@@ -818,36 +1040,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();
             }
         }
 
@@ -856,39 +1084,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();
             }
         }
 
@@ -921,10 +1171,23 @@
                 pw.increaseIndent();
                 mPhoneAccountRegistrar.dump(pw);
                 pw.decreaseIndent();
+
+                pw.println("Analytics:");
+                pw.increaseIndent();
+                Analytics.dump(pw);
+                pw.decreaseIndent();
             }
 
             Log.dumpCallEvents(pw);
         }
+
+        /**
+         * @see android.telecom.TelecomManager#launchManageBlockedNumbersActivity
+         */
+        @Override
+        public void launchManageBlockedNumbersActivity(String callingPackageName) {
+            BlockedNumbersActivity.start(mContext);
+        }
     };
 
     private Context mContext;
@@ -933,12 +1196,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);
@@ -949,6 +1220,10 @@
         mCallsManager = callsManager;
         mLock = lock;
         mPhoneAccountRegistrar = phoneAccountRegistrar;
+        mUserCallIntentProcessorFactory = userCallIntentProcessorFactory;
+        mDefaultDialerManagerAdapter = defaultDialerManagerAdapter;
+        mCallIntentProcessorAdapter = callIntentProcessorAdapter;
+        mSubscriptionManagerAdapter = subscriptionManagerAdapter;
     }
 
     public ITelecomService.Stub getBinder() {
@@ -959,63 +1234,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() {
@@ -1041,10 +1262,13 @@
         return false;
     }
 
-    private void acceptRingingCallInternal() {
+    private void acceptRingingCallInternal(int videoState) {
         Call call = mCallsManager.getFirstCallWithState(CallState.RINGING);
         if (call != null) {
-            call.answer(call.getVideoState());
+            if (videoState == DEFAULT_VIDEO_STATE || !isValidAcceptVideoState(videoState)) {
+                videoState = call.getVideoState();
+            }
+            call.answer(videoState);
         }
     }
 
@@ -1074,14 +1298,15 @@
 
     // Enforce that the PhoneAccountHandle being passed in is both registered to the current user
     // and enabled.
-    private void enforcePhoneAccountIsRegisteredEnabled(PhoneAccountHandle phoneAccountHandle) {
-        PhoneAccount phoneAccount = mPhoneAccountRegistrar.getPhoneAccountCheckCallingUser(
-                phoneAccountHandle);
-        if (phoneAccount == null) {
+    private void enforcePhoneAccountIsRegisteredEnabled(PhoneAccountHandle phoneAccountHandle,
+                                                        UserHandle callingUserHandle) {
+        PhoneAccount phoneAccount = mPhoneAccountRegistrar.getPhoneAccount(phoneAccountHandle,
+                callingUserHandle);
+        if(phoneAccount == null) {
             EventLog.writeEvent(0x534e4554, "26864502", Binder.getCallingUid(), "R");
             throw new SecurityException("This PhoneAccountHandle is not registered for this user!");
         }
-        if (!phoneAccount.isEnabled()) {
+        if(!phoneAccount.isEnabled()) {
             EventLog.writeEvent(0x534e4554, "26864502", Binder.getCallingUid(), "E");
             throw new SecurityException("This PhoneAccountHandle is not enabled for this user!");
         }
@@ -1149,6 +1374,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)) {
@@ -1195,10 +1428,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);
         }
@@ -1216,10 +1449,29 @@
 
     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);
+    }
+
+    /**
+     * Determines if a video state is valid for accepting an incoming call.
+     * For the purpose of accepting a call, states {@link VideoProfile#STATE_AUDIO_ONLY}, and
+     * any combination of {@link VideoProfile#STATE_RX_ENABLED} and
+     * {@link VideoProfile#STATE_TX_ENABLED} are considered valid.
+     *
+     * @param videoState The video state.
+     * @return {@code true} if the video state is valid, {@code false} otherwise.
+     */
+    private boolean isValidAcceptVideoState(int videoState) {
+        // Given a video state input, turn off TX and RX so that we can determine if those were the
+        // only bits set.
+        int remainingState = videoState & ~VideoProfile.STATE_TX_ENABLED;
+        remainingState = remainingState & ~VideoProfile.STATE_RX_ENABLED;
+
+        // If only TX or RX were set (or neither), the video state is valid.
+        return remainingState == 0;
     }
 }
diff --git a/src/com/android/server/telecom/TelecomSystem.java b/src/com/android/server/telecom/TelecomSystem.java
index 66f226e..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,17 +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,
@@ -112,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 8fbe91c..9824f88 100644
--- a/src/com/android/server/telecom/Timeouts.java
+++ b/src/com/android/server/telecom/Timeouts.java
@@ -107,4 +107,28 @@
     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 7dcfdfb..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();
             }
         }
 
@@ -206,6 +211,9 @@
          * Proxies a request from the {@link #mConectionServiceVideoProvider} to the
          * {@link InCallService} when the call data usage changes.
          *
+         * Also tracks the current call data usage on the {@link Call} for use when writing to the
+         * call log.
+         *
          * @param dataUsage The data usage.
          */
         @Override
@@ -213,6 +221,7 @@
             synchronized (mLock) {
                 logFromVideoProvider("changeCallDataUsage: " + dataUsage);
                 VideoProviderProxy.this.setCallDataUsage(dataUsage);
+                mCall.setCallDataUsage(dataUsage);
             }
         }
 
diff --git a/src/com/android/server/telecom/WiredHeadsetManager.java b/src/com/android/server/telecom/WiredHeadsetManager.java
index ef5f38c..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/ErrorDialogActivity.java b/src/com/android/server/telecom/components/ErrorDialogActivity.java
index 858682e..473767a 100644
--- a/src/com/android/server/telecom/components/ErrorDialogActivity.java
+++ b/src/com/android/server/telecom/components/ErrorDialogActivity.java
@@ -35,6 +35,7 @@
 
     public static final String SHOW_MISSING_VOICEMAIL_NO_DIALOG_EXTRA = "show_missing_voicemail";
     public static final String ERROR_MESSAGE_ID_EXTRA = "error_message_id";
+    public static final String ERROR_MESSAGE_STRING_EXTRA = "error_message_string";
 
     /**
      * Intent action to bring up Voicemail Provider settings.
@@ -50,6 +51,10 @@
 
         if (showVoicemailDialog) {
             showMissingVoicemailErrorDialog();
+        }  else if (getIntent().getCharSequenceExtra(ERROR_MESSAGE_STRING_EXTRA) != null) {
+            final CharSequence error = getIntent().getCharSequenceExtra(
+                    ERROR_MESSAGE_STRING_EXTRA);
+            showGenericErrorDialog(error);
         } else {
             final int error = getIntent().getIntExtra(ERROR_MESSAGE_ID_EXTRA, -1);
             if (error == -1) {
@@ -61,8 +66,7 @@
         }
     }
 
-    private void showGenericErrorDialog(int resid) {
-        final CharSequence msg = getResources().getText(resid);
+    private void showGenericErrorDialog(CharSequence msg) {
         final DialogInterface.OnClickListener clickListener;
         final DialogInterface.OnCancelListener cancelListener;
 
@@ -87,6 +91,11 @@
         errorDialog.show();
     }
 
+    private void showGenericErrorDialog(int resid) {
+        final CharSequence msg = getResources().getText(resid);
+        showGenericErrorDialog(msg);
+    }
+
     private void showMissingVoicemailErrorDialog() {
         new AlertDialog.Builder(this)
                 .setTitle(R.string.no_vm_number)
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 8f451b5..65258d9 100644
--- a/src/com/android/server/telecom/components/UserCallIntentProcessor.java
+++ b/src/com/android/server/telecom/components/UserCallIntentProcessor.java
@@ -123,31 +123,13 @@
                 VideoProfile.STATE_AUDIO_ONLY);
         Log.d(this, "processOutgoingCallIntent videoState = " + videoState);
 
-        if (VideoProfile.isVideo(videoState)
-                && TelephonyUtil.shouldProcessAsEmergency(mContext, handle)) {
-            Log.d(this, "Emergency call...Converting video call to voice...");
-            videoState = VideoProfile.STATE_AUDIO_ONLY;
-            intent.putExtra(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
-                    videoState);
-        }
-
-        if (VideoProfile.isVideo(videoState) && isTtyModeEnabled()) {
-            Toast.makeText(mContext, mContext.getResources().getString(R.string.
-                    video_call_not_allowed_if_tty_enabled), Toast.LENGTH_SHORT).show();
-            Log.d(this, "Rejecting video calls as tty is enabled");
-            return;
-        }
-
         intent.putExtra(CallIntentProcessor.KEY_IS_PRIVILEGED_DIALER,
                 isDefaultOrSystemDialer(callingPackageName));
-        sendBroadcastToReceiver(intent);
-    }
 
-    private boolean isTtyModeEnabled() {
-        return (android.provider.Settings.Secure.getInt(
-                mContext.getContentResolver(),
-                android.provider.Settings.Secure.PREFERRED_TTY_MODE,
-                TelecomManager.TTY_MODE_OFF) != TelecomManager.TTY_MODE_OFF);
+        // Save the user handle of current user before forwarding the intent to primary user.
+        intent.putExtra(CallIntentProcessor.KEY_INITIATING_USER, mUserHandle);
+
+        sendBroadcastToReceiver(intent);
     }
 
     private boolean isDefaultOrSystemDialer(String callingPackageName) {
@@ -184,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/settings/BlockedNumbersActivity.java b/src/com/android/server/telecom/settings/BlockedNumbersActivity.java
new file mode 100644
index 0000000..c36f55f
--- /dev/null
+++ b/src/com/android/server/telecom/settings/BlockedNumbersActivity.java
@@ -0,0 +1,201 @@
+/*
+ * 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.settings;
+
+import android.annotation.Nullable;
+import android.app.ActionBar;
+import android.app.AlertDialog;
+import android.app.ListActivity;
+import android.app.LoaderManager;
+import android.content.*;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.os.Process;
+import android.os.UserManager;
+import android.provider.BlockedNumberContract;
+import android.text.Editable;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.*;
+import com.android.server.telecom.R;
+
+/**
+ * Activity to manage blocked numbers using {@link BlockedNumberContract}.
+ */
+public class BlockedNumbersActivity extends ListActivity
+        implements LoaderManager.LoaderCallbacks<Cursor>, View.OnClickListener, TextWatcher {
+    private static final String[] PROJECTION = new String[] {
+            BlockedNumberContract.BlockedNumbers.COLUMN_ID,
+            BlockedNumberContract.BlockedNumbers.COLUMN_ORIGINAL_NUMBER
+    };
+
+    private static final String SELECTION = "((" +
+            BlockedNumberContract.BlockedNumbers.COLUMN_ORIGINAL_NUMBER + " NOTNULL) AND (" +
+            BlockedNumberContract.BlockedNumbers.COLUMN_ORIGINAL_NUMBER + " != '' ))";
+
+    private BlockedNumbersAdapter mAdapter;
+    private TextView mAddButton;
+    private ProgressBar mProgressBar;
+    @Nullable private Button mBlockButton;
+
+    public static void start(Context context) {
+        Intent intent = new Intent(context, BlockedNumbersActivity.class);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        context.startActivity(intent);
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.xml.activity_blocked_numbers);
+
+        ActionBar actionBar = getActionBar();
+        if (actionBar != null) {
+            actionBar.setDisplayHomeAsUpEnabled(true);
+        }
+
+        UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
+        if (!userManager.isPrimaryUser()) {
+            TextView nonPrimaryUserText = (TextView) findViewById(R.id.non_primary_user);
+            nonPrimaryUserText.setVisibility(View.VISIBLE);
+
+            LinearLayout manageBlockedNumbersUi =
+                    (LinearLayout) findViewById(R.id.manage_blocked_ui);
+            manageBlockedNumbersUi.setVisibility(View.GONE);
+            return;
+        }
+        mAddButton = (TextView) findViewById(R.id.add_blocked);
+        mAddButton.setOnClickListener(this);
+
+        mProgressBar = (ProgressBar) findViewById(R.id.progress_bar);
+
+        String[] fromColumns = {BlockedNumberContract.BlockedNumbers.COLUMN_ORIGINAL_NUMBER};
+        int[] toViews = {R.id.blocked_number};
+        mAdapter = new BlockedNumbersAdapter(this, R.xml.layout_blocked_number, null, fromColumns,
+                toViews, 0);
+
+        ListView listView = getListView();
+        listView.setAdapter(mAdapter);
+        listView.setDivider(null);
+        listView.setDividerHeight(0);
+
+        getLoaderManager().initLoader(0, null, this);
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case android.R.id.home:
+                this.finish();
+                return true;
+            default:
+                return super.onOptionsItemSelected(item);
+        }
+    }
+
+    @Override
+    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+        return new CursorLoader(this, BlockedNumberContract.BlockedNumbers.CONTENT_URI,
+                PROJECTION, SELECTION, null, null);
+    }
+
+    @Override
+    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+        mAdapter.swapCursor(data);
+        mProgressBar.setVisibility(View.GONE);
+    }
+
+    @Override
+    public void onLoaderReset(Loader<Cursor> loader) {
+        mAdapter.swapCursor(null);
+        mProgressBar.setVisibility(View.VISIBLE);
+    }
+
+    @Override
+    public void onClick(View view) {
+        if (view == mAddButton) {
+            showAddBlockedNumberDialog();
+        }
+    }
+
+    private void showAddBlockedNumberDialog() {
+        LayoutInflater inflater = this.getLayoutInflater();
+        View dialogView = inflater.inflate(R.xml.add_blocked_number_dialog, null);
+        final EditText editText = (EditText) dialogView.findViewById(R.id.add_blocked_number);
+        editText.addTextChangedListener(this);
+        AlertDialog dialog = new AlertDialog.Builder(this)
+                .setView(dialogView)
+                .setPositiveButton(R.string.block_button, new DialogInterface.OnClickListener() {
+                    public void onClick(DialogInterface dialog, int id) {
+                        addBlockedNumber(editText.getText().toString());
+                    }
+                })
+                .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
+                    public void onClick(DialogInterface dialog, int id) {
+                        dialog.dismiss();
+                    }
+                })
+                .create();
+        dialog.setOnShowListener(new AlertDialog.OnShowListener() {
+                    @Override
+                    public void onShow(DialogInterface dialog) {
+                        mBlockButton = ((AlertDialog) dialog)
+                                .getButton(AlertDialog.BUTTON_POSITIVE);
+                        mBlockButton.setEnabled(false);
+                    }
+                });
+        dialog.show();
+    }
+
+    /**
+     * Add blocked number if it does not exist.
+     */
+    private void addBlockedNumber(String number) {
+        ContentResolver contentResolver = getContentResolver();
+        Cursor cursor = contentResolver.query(
+                BlockedNumberContract.BlockedNumbers.CONTENT_URI,
+                PROJECTION,
+                BlockedNumberContract.BlockedNumbers.COLUMN_ORIGINAL_NUMBER + "=?",
+                new String[] {number},
+                null);
+        if (cursor == null || cursor.getCount() == 0) {
+            ContentValues newValues = new ContentValues();
+            newValues.put(BlockedNumberContract.BlockedNumbers.COLUMN_ORIGINAL_NUMBER, number);
+            contentResolver.insert(BlockedNumberContract.BlockedNumbers.CONTENT_URI, newValues);
+        }
+    }
+
+    @Override
+    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+        // no-op
+    }
+
+    @Override
+    public void onTextChanged(CharSequence text, int start, int before, int count) {
+        if (mBlockButton != null) {
+            mBlockButton.setEnabled(!TextUtils.isEmpty(text.toString().trim()));
+        }
+    }
+
+    @Override
+    public void afterTextChanged(Editable s) {
+        // no-op
+    }
+}
diff --git a/src/com/android/server/telecom/settings/BlockedNumbersAdapter.java b/src/com/android/server/telecom/settings/BlockedNumbersAdapter.java
new file mode 100644
index 0000000..3d56459
--- /dev/null
+++ b/src/com/android/server/telecom/settings/BlockedNumbersAdapter.java
@@ -0,0 +1,96 @@
+/*
+ * 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.settings;
+
+import android.app.AlertDialog;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.database.Cursor;
+import android.provider.BlockedNumberContract;
+import android.telephony.PhoneNumberUtils;
+import android.view.View;
+import android.widget.SimpleCursorAdapter;
+import android.widget.TextView;
+import com.android.server.telecom.R;
+
+import java.util.Locale;
+
+public class BlockedNumbersAdapter extends SimpleCursorAdapter {
+    public BlockedNumbersAdapter(Context context, int layout, Cursor c, String[] from, int[] to,
+            int flags) {
+        super(context, layout, c, from, to, flags);
+    }
+
+    @Override
+    public void bindView(View view, final Context context, final Cursor cursor) {
+        super.bindView(view, context, cursor);
+        final String rawNumber = cursor.getString(cursor.getColumnIndex(
+                BlockedNumberContract.BlockedNumbers.COLUMN_ORIGINAL_NUMBER));
+        String formattedNumber = PhoneNumberUtils.formatNumber(rawNumber,
+                getLocaleDefaultToUS());
+        final String finalFormattedNumber = formattedNumber == null ? rawNumber : formattedNumber;
+
+        TextView numberView = (TextView) view.findViewById(R.id.blocked_number);
+        numberView.setText(finalFormattedNumber);
+
+        View deleteButton = view.findViewById(R.id.delete_blocked_number);
+        deleteButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+               showDeleteBlockedNumberDialog(context, rawNumber, finalFormattedNumber);
+            }
+        });
+    }
+
+    private String getLocaleDefaultToUS() {
+        String countryIso = Locale.getDefault().getCountry();
+        if (countryIso == null || countryIso.length() != 2) {
+            countryIso = "US";
+        }
+        return countryIso;
+    }
+
+    private void showDeleteBlockedNumberDialog(final Context context, final String rawNumber,
+            final String formattedNumber) {
+        new AlertDialog.Builder(context)
+                .setMessage(context.getString(R.string.unblock_dialog_body, formattedNumber))
+                .setPositiveButton(R.string.unblock_button,
+                        new DialogInterface.OnClickListener() {
+                            public void onClick(DialogInterface dialog, int id) {
+                                deleteBlockedNumber(context, rawNumber);
+                            }
+                        }
+                )
+                .setNegativeButton(android.R.string.cancel,
+                        new DialogInterface.OnClickListener() {
+                            public void onClick(DialogInterface dialog, int id) {
+                                dialog.dismiss();
+                            }
+                        }
+                )
+                .create()
+                .show();
+    }
+
+    private void deleteBlockedNumber(Context context, String number) {
+        ContentResolver contentResolver = context.getContentResolver();
+        contentResolver.delete(BlockedNumberContract.BlockedNumbers.CONTENT_URI,
+                BlockedNumberContract.BlockedNumbers.COLUMN_ORIGINAL_NUMBER + "=?",
+                new String[] {number});
+    }
+}
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..12edf37 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -39,6 +39,8 @@
     --auto-add-overlay \
     --extra-packages com.android.server.telecom
 
+LOCAL_PROGUARD_ENABLED := disabled
+
 LOCAL_PACKAGE_NAME := TelecomUnitTests
 LOCAL_CERTIFICATE := platform
 
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index 6a08c63..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" />
@@ -27,6 +31,9 @@
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
     <uses-permission android:name="android.permission.MANAGE_USERS" />
 
+    <!-- Used to access TelephonyManager APIs -->
+    <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
+
     <application android:label="@string/app_name"
                  android:debuggable="true">
         <uses-library android:name="android.test.runner" />
@@ -40,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 bdcfb5b..17a4d90 100644
--- a/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
+++ b/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
@@ -27,18 +27,22 @@
 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;
@@ -48,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 {
 
@@ -64,6 +137,7 @@
             if (!mConnectionServiceAdapters.add(adapter)) {
                 throw new RuntimeException("Adapter already added: " + adapter);
             }
+            mConnectionServiceDelegateAdapter.addConnectionServiceAdapter(adapter);
         }
 
         @Override
@@ -72,6 +146,7 @@
             if (!mConnectionServiceAdapters.remove(adapter)) {
                 throw new RuntimeException("Adapter never added: " + adapter);
             }
+            mConnectionServiceDelegateAdapter.removeConnectionServiceAdapter(adapter);
         }
 
         @Override
@@ -92,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
@@ -133,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 { }
@@ -156,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);
@@ -178,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..7db000f
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/LogTest.java
@@ -0,0 +1,546 @@
+/*
+ * 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()" was previously used, but it does not always perform GC, so the
+        // internal method is now called.
+        Runtime.getRuntime().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..4428c34
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/NewOutgoingCallIntentBroadcasterTest.java
@@ -0,0 +1,420 @@
+/*
+ * 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;
+import static org.mockito.Mockito.when;
+
+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());
+        when(mCall.getInitiatingUser()).thenReturn(UserHandle.CURRENT);
+    }
+
+    @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 4f96cc7..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,53 +442,50 @@
                 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 {
-        reset(
-                connectionServiceFixture.getTestDouble(),
-                mInCallServiceFixtureX.getTestDouble(),
+        return startIncomingPhoneCall(number, phoneAccountHandle, VideoProfile.STATE_AUDIO_ONLY,
+                connectionServiceFixture);
+    }
+
+    protected IdPair startIncomingPhoneCall(
+            String number,
+            PhoneAccountHandle phoneAccountHandle,
+            int videoState,
+            final ConnectionServiceFixture connectionServiceFixture) 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));
-
         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(
@@ -374,17 +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));
 
-        connectionServiceFixture.sendHandleCreateConnectionComplete(
-                connectionServiceFixture.mLatestConnectionId);
-        connectionServiceFixture.sendSetRinging(
-                connectionServiceFixture.mLatestConnectionId);
+        connectionServiceFixture.sendSetRinging(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
@@ -392,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
@@ -420,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
 
@@ -453,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());
@@ -507,51 +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());
-    }
-
-    // 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());
@@ -560,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));
+    }
+}