DO NOT MERGE - Skip PPRL.190305.001 into master

Bug: 127812889
Change-Id: I5cbff78fb8a326159d8daba589a580f4b7775b4b
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 588e5c3..6aac49e 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -21,6 +21,8 @@
         android:sharedUserId="android.uid.system">
 
     <protected-broadcast android:name="android.intent.action.SHOW_MISSED_CALLS_NOTIFICATION" />
+    <protected-broadcast android:name="com.android.server.telecom.MESSAGE_SENT" />
+
 
     <!-- Prevents the activity manager from delaying any activity-start
          requests by this package, including requests immediately after
@@ -32,6 +34,7 @@
     <uses-permission android:name="android.permission.BROADCAST_CALLLOG_INFO" />
     <uses-permission android:name="android.permission.BROADCAST_PHONE_ACCOUNT_REGISTRATION" />
     <uses-permission android:name="android.permission.CALL_PRIVILEGED" />
+    <uses-permission android:name="android.permission.HANDLE_CALL_INTENT" />
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
     <uses-permission android:name="android.permission.MANAGE_USERS" />
@@ -69,6 +72,11 @@
             android:label="Process phone account registration"
             android:protectionLevel="signature|system"/>
 
+    <permission
+            android:name="android.permission.HANDLE_CALL_INTENT"
+            android:label="Protects handling the call intent via the TelecomManager API."
+            android:protectionLevel="signature|system"/>
+
     <application android:label="@string/telecommAppLabel"
             android:icon="@mipmap/ic_launcher_phone"
             android:allowBackup="false"
@@ -230,7 +238,7 @@
             </intent-filter>
         </receiver>
 
-        <receiver android:name=".components.PhoneAccountBroadcastReceiver"
+        <receiver android:name=".components.AppUninstallBroadcastReceiver"
                 android:process="system">
             <intent-filter>
                 <action android:name="android.intent.action.PACKAGE_FULLY_REMOVED" />
@@ -289,11 +297,10 @@
             </intent-filter>
         </activity>
 
-        <receiver android:name=".components.PrimaryCallReceiver"
-                android:exported="true"
-                android:permission="android.permission.MODIFY_PHONE_STATE"
-                android:process="system">
-        </receiver>
+        <activity android:name=".ui.TelecomDeveloperMenu"
+                  android:label="@string/developer_title"
+                  android:exported="false"
+                  android:process=":ui" />
 
         <service android:name=".components.BluetoothPhoneService"
                 android:singleUser="true"
diff --git a/OWNERS b/OWNERS
index 2f0bcec..94409ef 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,3 +1,4 @@
 breadley@google.com
 hallliu@google.com
 tgunn@google.com
+paulye@google.com
diff --git a/res/layout/telecom_developer_menu.xml b/res/layout/telecom_developer_menu.xml
new file mode 100644
index 0000000..0df0cdd
--- /dev/null
+++ b/res/layout/telecom_developer_menu.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2018 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:orientation="vertical"
+              android:layout_width="fill_parent"
+              android:layout_height="fill_parent">
+
+    <Switch
+        android:id="@+id/switchEnhancedCallBlocking"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/developer_enhanced_call_blocking"/>
+</LinearLayout>
\ No newline at end of file
diff --git a/res/raw/endcall.ogg b/res/raw/endcall.ogg
new file mode 100644
index 0000000..1af440b
--- /dev/null
+++ b/res/raw/endcall.ogg
Binary files differ
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index 8b488c2..ec4b4dd 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Vinnige antwoord"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Boodskap gestuur na <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Kon nie boodskap aan <xliff:g id="PHONE_NUMBER">%s</xliff:g> stuur nie."</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"Oproeprekeninge"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Net noodoproepe word toegelaat."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Hierdie program kan nie uitgaande oproepe maak sonder die foon se toestemming nie."</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Oproepblokkering is gedeaktiveer"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Noodoproep gemaak"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Oproepblokkering is gedeaktiveer sodat noodeenhede jou kan kontak."</string>
+    <string name="developer_title" msgid="1816273446906554627">"Telecom-ontwikkelaarkieslys"</string>
 </resources>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index 6331a39..3f4e5d2 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"ፈጣን ምላሽ"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"ለ <xliff:g id="PHONE_NUMBER">%s</xliff:g> የተላከ መልዕክት"</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"መልዕክት ወደ <xliff:g id="PHONE_NUMBER">%s</xliff:g> መላክ አልተሳካም።"</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"የመደወያ መለያዎች"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"የድንገተኛ አደጋ ጥሪዎች ብቻ ናቸው የሚፈቀዱት።"</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"ይህ መተግበሪያ ያለስልኩ ፈቃድ ወጪ ጥሪዎችን ማድረግ አይችልም።"</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"ጥሪ ማገድ ተሰናክሏል"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"የአደጋ ጊዜ ጥሪ ተደርጓል"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"የአደጋ ጊዜ ምላሽ ሰጪዎች እርስዎን ለማግኘት እንዲችሉ ጥሪ ማገድ ተሰናክሏል።"</string>
+    <string name="developer_title" msgid="1816273446906554627">"የቴሌኮም ገንቢ ምናሌ"</string>
 </resources>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index 234c836..b0602d8 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"رد سريع"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"تم إرسال الرسالة إلى <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"تعذَّر إرسال الرسالة إلى <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"حسابات الاتصال"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"مسموح بمكالمات الطوارئ فقط."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"يتعذر على هذا التطبيق إجراء مكالمات صادرة بدون إذن من الهاتف."</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"تم إيقاف حظر المكالمات"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"تم إجراء مكالمة طوارئ"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"تم إيقاف حظر المكالمات للسماح لمسؤولي استجابة الطوارئ بالاتصال بك."</string>
+    <string name="developer_title" msgid="1816273446906554627">"قائمة مطوّر برامج الاتصالات"</string>
 </resources>
diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml
deleted file mode 100644
index 13cb1c3..0000000
--- a/res/values-as/strings.xml
+++ /dev/null
@@ -1,100 +0,0 @@
-<?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="382363169988504520">"কল পৰিচালনা"</string>
-    <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"ফ’ন"</string>
-    <string name="unknown" msgid="6878797917991465859">"অজ্ঞাত"</string>
-    <string name="notification_missedCallTitle" msgid="7554385905572364535">"মিছ্ড কল"</string>
-    <string name="notification_missedWorkCallTitle" msgid="6242489980390803090">"মিছ কৰা কৰ্মস্থানৰ কল"</string>
-    <string name="notification_missedCallsTitle" msgid="1361677948941502522">"মিছ্ড কল"</string>
-    <string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g>টা মিছ্ড কল"</string>
-    <string name="notification_missedCallTicker" msgid="504686252427747209">"<xliff:g id="MISSED_CALL_FROM">%s</xliff:g>ৰ পৰা মিছ্ড কল"</string>
-    <string name="notification_missedCall_call_back" msgid="2684890353590890187">"কলবেক কৰক"</string>
-    <string name="notification_missedCall_message" msgid="3049928912736917988">"বাৰ্তা"</string>
-    <string name="accessibility_call_muted" msgid="2776111226185342220">"কল মিউট কৰা হৈছে।"</string>
-    <string name="accessibility_speakerphone_enabled" msgid="1988512040421036359">"স্পীকাৰফ\'ন সক্ষম কৰা হৈছে।"</string>
-    <string name="respond_via_sms_canned_response_1" msgid="2461606462788380215">"এতিয়া কথা পাতিব নোৱাৰোঁ। কি খবৰ?"</string>
-    <string name="respond_via_sms_canned_response_2" msgid="4074450431532859214">"মই আপোনাক লগে লগে কলবেক কৰি আছোঁ।"</string>
-    <string name="respond_via_sms_canned_response_3" msgid="3496079065723960450">"মই আপোনাক পিছত কল কৰিম।"</string>
-    <string name="respond_via_sms_canned_response_4" msgid="1698989243040062190">"এতিয়া কথা পাতিব নোৱাৰোঁ। মোক পিছত কল কৰিবনে?"</string>
-    <string name="respond_via_sms_setting_title" msgid="3754000371039709383">"ক্ষীপ্ৰ উত্তৰসমূহ"</string>
-    <string name="respond_via_sms_setting_title_2" msgid="6104662227299493906">"ক্ষীপ্ৰ উত্তৰসমূহ সম্পাদনা কৰক"</string>
-    <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
-    <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"তাৎক্ষণিক উত্তৰ"</string>
-    <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"<xliff:g id="PHONE_NUMBER">%s</xliff:g>লৈ বাৰ্তা পঠিওৱা হ\'ল।"</string>
-    <string name="enable_account_preference_title" msgid="2021848090086481720">"কলিং একাউণ্ট"</string>
-    <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"কেৱল জৰুৰীকালীন কল কৰাৰ হে অনুমতি আছে।"</string>
-    <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"ফ\'ন অনুমতিটোৰ অবিহনে এই এপ্লিকেশ্বনটোৱে কোনো বহিৰ্গামী কল কৰিব নোৱাৰে।"</string>
-    <string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"কল কৰিবৰ কাৰণে এটা মান্য নম্বৰ দিয়ক।"</string>
-    <string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"এই মুহূৰ্তত কল যোগ কৰিব নোৱাৰি।"</string>
-    <string name="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="9101655962941740507">"<xliff:g id="NEW_APP">%s</xliff:g>ক আপোনাৰ ডিফ\'ল্ট ফ\'ন এপ্ হিচাপে চিহ্নিত কৰেনে?"</string>
-    <string name="change_default_dialer_dialog_affirmative" msgid="8606546663509166276">"ডিফ\'ল্ট ছেট কৰক"</string>
-    <string name="change_default_dialer_dialog_negative" msgid="9078144617060173845">"বাতিল কৰক"</string>
-    <string name="change_default_dialer_warning_message" msgid="1417671460801684999">"<xliff:g id="NEW_APP">%s</xliff:g>এ কল কৰা লগতে কলৰ সকলো দিশ নিয়ন্ত্ৰণ কৰিবলৈ সক্ষম হ\'ব। কেৱল আপুনি সম্পূৰ্ণৰূপে বিশ্বাস কৰা এপক হে আপোনাৰ ডিফ\'ল্ট ফ\'ন এপ্ হিচাপে চিহ্নিত কৰা উচিত।"</string>
-    <string name="blocked_numbers" msgid="2751843139572970579">"অৱৰোধ কৰা নম্বৰসমূহ"</string>
-    <string name="blocked_numbers_msg" msgid="1045015186124965643">"আপুনি অৱৰোধ কৰা নম্বৰসমূহৰ পৰা আৰু কল বা বাৰ্তা লাভ নকৰে।"</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="6847675097085433553">"ফ\'ন নম্বৰ"</string>
-    <string name="block_button" msgid="8822290682524373357">"অৱৰোধ কৰক"</string>
-    <string name="non_primary_user" msgid="5180129233352533459">"কেৱল ডিভাইচটোৰ গৰাকীয়েহে অৱৰোধ কৰা নম্বৰসমূহ চাব আৰু পৰিচালনা কৰিব পাৰে।"</string>
-    <string name="delete_icon_description" msgid="8903995728252556724">"অৱৰোধৰ পৰা আঁতৰাওক"</string>
-    <string name="blocked_numbers_butter_bar_title" msgid="438170866438793182">"সাময়িকভাৱে অৱৰোধৰ সুবিধা বন্ধ কৰি থোৱা হৈছে"</string>
-    <string name="blocked_numbers_butter_bar_body" msgid="2223244484319442431">"আপুনি জৰুৰীকালীন নম্বৰ এটা ডায়েল কৰাৰ পিছত বা সেই নম্বৰটোলৈ পাঠ বাৰ্তা পঠিওৱাৰ পিছত নম্বৰটো অৱৰোধৰ পৰা আঁতৰোৱা হয় যাতে জৰুৰীকালীন সেৱাসমূহে আপোনাৰ সৈতে যোগাযোগ কৰিব পাৰে।"</string>
-    <string name="blocked_numbers_butter_bar_button" msgid="2197943354922010696">"এতিয়াই পুনঃসক্ষম কৰক"</string>
-    <string name="blocked_numbers_number_blocked_message" msgid="7678509606805029540">"<xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g> অৱৰোধ কৰা হৈছে"</string>
-    <string name="blocked_numbers_number_unblocked_message" msgid="977894647366750418">"<xliff:g id="UNBLOCKED_NUMBER">%1$s</xliff:g> অৱৰোধৰ পৰা আঁতৰ কৰা হৈছে"</string>
-    <string name="blocked_numbers_block_emergency_number_message" msgid="917851876780698387">"জৰুৰীকালীন নম্বৰ অৱৰোধ কৰিব পৰা নাই।"</string>
-    <string name="blocked_numbers_number_already_blocked_message" msgid="4392247814500811798">"<xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g>ক ইতিমধ্যে অৱৰোধ কৰা হৈছে।"</string>
-    <string name="toast_personal_call_msg" msgid="5115361633476779723">"কলটো কৰিবলৈ ব্যক্তিগত ডায়েলাৰৰ ব্যৱহাৰ কৰা হৈছে"</string>
-    <string name="notification_incoming_call" msgid="7713197997773986670">"<xliff:g id="CALL_VIA">%1$s</xliff:g> <xliff:g id="CALL_FROM">%2$s</xliff:g>ৰ পৰা অহা কল"</string>
-    <string name="notification_incoming_video_call" msgid="6638486071698373893">"<xliff:g id="CALL_VIA">%1$s</xliff:g> <xliff:g id="CALL_FROM">%2$s</xliff:g>ৰ পৰা অহা ভিডিঅ\' কল"</string>
-    <string name="answering_ends_other_call" msgid="8282145910153766401">"উত্তৰ দিলে <xliff:g id="CALL_VIA">%1$s</xliff:g> কলটোৰ অন্ত পৰিব"</string>
-    <string name="answering_ends_other_calls" msgid="1198589551399049197">"উত্তৰ দিলে <xliff:g id="CALL_VIA">%1$s</xliff:g> কলকেইটাৰ অন্ত পৰিব"</string>
-    <string name="answering_ends_other_video_call" msgid="8510410917384186360">"উত্তৰ দিলে আপোনাৰ বৰ্তমান চলি থকা <xliff:g id="CALL_VIA">%1$s</xliff:g> ভিডিঅ\' কলটোৰ অন্ত পৰিব"</string>
-    <string name="answering_ends_other_managed_call" msgid="5186137550267947785">"উত্তৰ দিলে আপোনাৰ বৰ্তমান চলি থকা কলটোৰ অন্ত পৰিব"</string>
-    <string name="answering_ends_other_managed_calls" msgid="6429838309560397988">"উত্তৰ দিলে আপোনাৰ বৰ্তমান চলি থকা কলসমূহৰ অন্ত পৰিব"</string>
-    <string name="answering_ends_other_managed_video_call" msgid="1585423762458248435">"উত্তৰ দিলে আপোনাৰ বৰ্তমান চলি থকা ভিডিঅ\' কলটোৰ অন্ত পৰিব"</string>
-    <string name="answer_incoming_call" msgid="4140530013111794587">"উত্তৰ"</string>
-    <string name="decline_incoming_call" msgid="806026168661598368">"প্ৰত্যাখ্যান কৰক"</string>
-    <string name="cant_call_due_to_ongoing_call" msgid="4952615196237854748">"আপোনাৰ <xliff:g id="OTHER_CALL">%1$s</xliff:g> কল চলি থকাৰ কাৰণে বেলেগ কল কৰিব নোৱাৰি।"</string>
-    <string name="cant_call_due_to_ongoing_calls" msgid="1380804892363503856">"আপোনাৰ <xliff:g id="OTHER_CALL">%1$s</xliff:g> কলকেইটা চলি থকাৰ কাৰণে বেলেগ কল কৰিব নোৱাৰি।"</string>
-    <string name="cant_call_due_to_ongoing_unknown_call" msgid="149091978697302211">"অইন এটা এপত কল চলি থকাৰ কাৰণে বেলেগ কল কৰিব নোৱাৰি।"</string>
-    <string name="notification_channel_incoming_call" msgid="3513761697082968084">"অন্তৰ্গামী কল"</string>
-    <string name="notification_channel_missed_call" msgid="8727062678632713146">"মিছ্ড কল"</string>
-    <string name="notification_channel_call_blocking" msgid="2943358779746676070">"কল অৱৰোধ"</string>
-    <string name="alert_outgoing_call" msgid="982908156825958001">"এই কলটো কৰিলে আপোনাৰ <xliff:g id="OTHER_APP">%1$s</xliff:g> কলটোৰ অন্ত পৰিব।"</string>
-    <string name="phone_settings_call_blocking_txt" msgid="3976004073043846733">"কল অৱৰোধ"</string>
-    <string name="phone_settings_number_not_in_contact_txt" msgid="3126829421867168652">"আপোনাৰ সর্ম্পকসূচীত নথকা"</string>
-    <string name="phone_settings_number_not_in_contact_summary_txt" msgid="9043147855140079119">"আপোনাৰ সর্ম্পকসূচীত নথকা নম্বৰ অৱৰোধ কৰক"</string>
-    <string name="phone_settings_private_num_txt" msgid="8623574188879134262">"ব্য়ক্তিগত"</string>
-    <string name="phone_settings_private_num_summary_txt" msgid="7516314821207782191">"যিসকল কল কৰোঁতাই তেওঁলোকৰ নম্বৰ প্ৰকাশ নকৰে তেওঁলোকক অৱৰোধ কৰক"</string>
-    <string name="phone_settings_payphone_txt" msgid="2493356957416981318">"পে\'ফ\'ন"</string>
-    <string name="phone_settings_payphone_summary_txt" msgid="6126709946103814653">"পে\'ফ\'নৰ পৰা অহা কল অৱৰোধ কৰক"</string>
-    <string name="phone_settings_unknown_txt" msgid="5836407031508172721">"অজ্ঞাত"</string>
-    <string name="phone_settings_unknown_summary_txt" msgid="3457690230497753233">"অচিনাক্ত কল কৰোঁতাৰ পৰা অহা কল অৱৰোধ কৰক"</string>
-    <string name="phone_strings_call_blocking_turned_off_notification_title_txt" msgid="628536625775266096">"কল অৱৰোধ"</string>
-    <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"কল অৱৰোধ সুবিধাটো অক্ষম কৰি থোৱা আছে"</string>
-    <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"জৰুৰীকালীন কল ম\'ড"</string>
-    <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"আপোনাক যাতে জৰুৰীকালীন সেৱা প্ৰদানকাৰীসকলে যোগাযোগ কৰিব পাৰে তাৰ বাবে কল অৱৰোধ সুবিধাটো অক্ষম কৰি থোৱা হৈছে।"</string>
-</resources>
diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml
index fd42626..da3f1b5 100644
--- a/res/values-az/strings.xml
+++ b/res/values-az/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Tez cavab"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Mesaj <xliff:g id="PHONE_NUMBER">%s</xliff:g> nömrəsinə göndərildi."</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"<xliff:g id="PHONE_NUMBER">%s</xliff:g> nömrəsinə mesaj göndərmək alınmadı."</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"Hesabların çağırılması"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Yalnız təcili zənglərə icazə verilir."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Bu proqram Telefon icazəsi olmadan zəng edə bilməz."</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Zəngi Bloklama deaktivdir"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Təcili zəng edildi"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Təcili zənglərə cavab verənlərin Sizinlə əlaqə saxlamalarına icazə vermək üçün Zəngi Bloklama deaktiv edilib."</string>
+    <string name="developer_title" msgid="1816273446906554627">"Telecom Tərtibatçı Menyusu"</string>
 </resources>
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
index b74041b..a67a34e 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -37,6 +37,7 @@
     <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="respond_via_sms_failure_format" msgid="90791421289769504">"Slanje poruke na <xliff:g id="PHONE_NUMBER">%s</xliff:g> nije uspelo."</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>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Blokiranje poziva je onemogućeno"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Upućen je hitni poziv"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Blokiranje poziva je onemogućeno da bi hitne službe mogle da vas kontaktiraju."</string>
+    <string name="developer_title" msgid="1816273446906554627">"Meni za programere Telecom-a"</string>
 </resources>
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
index 0cfa989..ef3f5ba 100644
--- a/res/values-be/strings.xml
+++ b/res/values-be/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Хуткі адказ"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Паведамленне адпраўлена на нумар <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Не ўдалося адправіць паведамленне на <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"Уліковыя запісы для выклікаў"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Дазволены толькі экстранныя выклікі."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Гэта праграма не можа рабіць выходныя выклікі без дазволу тэлефона."</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Блакіраванне выклікаў адключана"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Зроблены экстранны выклік"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Блакіраванне выклікаў было адключана, каб дазволіць аварыйнай брыгадзе звязацца з вамі."</string>
+    <string name="developer_title" msgid="1816273446906554627">"Меню распрацоўшчыка Telecom"</string>
 </resources>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index 102fb6f..ae4c619 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Бърз отговор"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"До <xliff:g id="PHONE_NUMBER">%s</xliff:g> бе изпратено съобщение."</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Неуспешно изпращане на съобщението до <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"Профили за обаждане"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Разрешени са само спешни обаждания."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Това приложение не може да извършва изходящи обаждания без разрешението за телефон."</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Блокирането на обажданията е деактивирано"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Извършено бе спешно обаждане"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Блокирането на обажданията е деактивирано, за да могат службите за спешни случаи да се свържат с вас."</string>
+    <string name="developer_title" msgid="1816273446906554627">"Меню за програмисти на Telecom"</string>
 </resources>
diff --git a/res/values-bn/strings.xml b/res/values-bn/strings.xml
index 8b1e5f9..a807100 100644
--- a/res/values-bn/strings.xml
+++ b/res/values-bn/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"দ্রুত প্রতিক্রিয়া"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"<xliff:g id="PHONE_NUMBER">%s</xliff:g> এ বার্তা পাঠানো হয়েছে৷"</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"<xliff:g id="PHONE_NUMBER">%s</xliff:g>-এ মেসেজ পাঠানো যায়নি।"</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"কলিং অ্যাকাউন্টগুলি"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"শুধুমাত্র জরুরি কলগুলিকে অনুমোদিত।"</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"এই অ্যাপ্লিকেশানটি ফোনের অনুমতি ছাড়া আউটগোয়িং কলগুলি করতে পারবে না।"</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"কল ব্লক করার বৈশিষ্ট্য বন্ধ করা হয়েছে"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"জরুরি অবস্থার কল করা হয়েছে"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"কল ব্লক করার বৈশিষ্ট্য বন্ধ করা হয়েছে যাতে জরুরি অবস্থার সাহায্যকারী ব্যক্তি আপনার সাথে যোগাযোগ করতে পারেন।"</string>
+    <string name="developer_title" msgid="1816273446906554627">"টেলিকম ডেভেলপার মেনু"</string>
 </resources>
diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml
index f8ad518..badc0a6 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Brzi odgovor"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Poruka poslana na <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Slanje poruke na broj <xliff:g id="PHONE_NUMBER">%s</xliff:g> nije uspjelo."</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"Računi 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 upućivati odlazne pozive bez odobrenja za Telefon."</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Blokiranje poziva je onemogućeno"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Upućen je hitni poziv"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Blokiranje poziva je onemogućeno kako bi se omogućilo osobama koje reagiraju u hitnim slučajevima da vas kontaktiraju."</string>
+    <string name="developer_title" msgid="1816273446906554627">"Meni za programere iz telekoma"</string>
 </resources>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index 9d6adce..31720d5 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Resposta ràpida"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Missatge enviat a <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"No s\'ha pogut enviar el missatge a <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"Comptes de trucades"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Només es permeten les trucades d\'emergència."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Aquesta aplicació no pot fer trucades sortints sense el permís del telèfon."</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"El bloqueig de trucades s\'ha desactivat"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"S\'ha fet una trucada d\'emergència"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"El bloqueig de trucades s\'ha desactivat perquè els serveis d\'emergència puguin contactar amb tu."</string>
+    <string name="developer_title" msgid="1816273446906554627">"Menú per a desenvolupadors de telecomunicacions"</string>
 </resources>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index 76c40e6..bff9807 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Rychlá odpověď"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Zpráva byla odeslána na číslo <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Zprávu se nepodařilo odeslat na číslo <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"Účty pro volání"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Jsou povolena pouze tísňová volání."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Tato aplikace nemůže provádět odchozí hovory bez oprávnění k použití telefonu."</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Blokování hovorů bylo vypnuto"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Uskutečněno tísňové volání"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Blokování hovorů bylo vypnuto, aby vás mohli kontaktovat pracovníci tísňových služeb."</string>
+    <string name="developer_title" msgid="1816273446906554627">"Nabídka pro vývojáře Telecomu"</string>
 </resources>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index 14e08d8..90cc1c1 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Hurtigt svar"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Beskeden er sendt til <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Beskeden kunne ikke sendes til <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"Opkaldskonti"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Kun nødopkald er tilladt."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Denne app kan ikke foretage udgående opkald uden opkaldstilladelse."</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Opkaldsblokering er deaktiveret"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Foretagne nødopkald"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Opkaldsblokering er blevet deaktiveret for at give nødnumre mulighed for at kontakte dig."</string>
+    <string name="developer_title" msgid="1816273446906554627">"Udviklermenu for Telecom"</string>
 </resources>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index 13f7e04..4c34419 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Kurzantwort"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Nachricht an <xliff:g id="PHONE_NUMBER">%s</xliff:g> gesendet"</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Fehler beim Senden der Nachricht an <xliff:g id="PHONE_NUMBER">%s</xliff:g>"</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"Anrufkonten"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Es sind nur Notrufe erlaubt."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Diese App darf ohne die Berechtigung \"Standard-App für Telefonie\" keine ausgehenden Anrufe tätigen."</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Anrufblockierung deaktiviert"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Notruf abgesetzt"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Die Anrufblockierung wurde deaktiviert, damit Ersthelfer und Rettungskräfte dich kontaktieren können."</string>
+    <string name="developer_title" msgid="1816273446906554627">"Telecom-Entwicklermenü"</string>
 </resources>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index cb21b12..486670a 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Γρήγορη απάντηση"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Το μήνυμα εστάλη στο <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Η αποστολή του μηνύματος στο <xliff:g id="PHONE_NUMBER">%s</xliff:g> απέτυχε."</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"Λογαριασμοί κλήσης"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Επιτρέπονται μόνο κλήσεις έκτακτης ανάγκης."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Αυτή η εφαρμογή δεν μπορεί να πραγματοποιήσει εξερχόμενες κλήσεις χωρίς την άδεια \"Τηλέφωνο\"."</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Φραγή κλήσεων απενεργοποιημένη"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Πραγματοποιήθηκε κλήση έκτακτης ανάγκης"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Η φραγή κλήσεων έχει απενεργοποιηθεί, ώστε να επιτρέπεται σε άτομα που ανταποκρίνονται σε έκτακτες ανάγκες να επικοινωνούν μαζί σας."</string>
+    <string name="developer_title" msgid="1816273446906554627">"Μενού προγραμματιστών τηλεπικοινωνιών"</string>
 </resources>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index d62ac55..fbbaafc 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Quick response"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Message sent to <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Message failed to send to <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"Calling accounts"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Only emergency calls are allowed."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"This application cannot make outgoing calls without Phone permission."</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Call Blocking disabled"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Emergency call made"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Call Blocking has been disabled to allow emergency responders to contact you."</string>
+    <string name="developer_title" msgid="1816273446906554627">"Telecom Developer Menu"</string>
 </resources>
diff --git a/res/values-en-rCA/strings.xml b/res/values-en-rCA/strings.xml
index d62ac55..7d344f1 100644
--- a/res/values-en-rCA/strings.xml
+++ b/res/values-en-rCA/strings.xml
@@ -82,19 +82,5 @@
     <string name="cant_call_due_to_ongoing_unknown_call" msgid="149091978697302211">"Call cannot be placed due to a call in another app."</string>
     <string name="notification_channel_incoming_call" msgid="3513761697082968084">"Incoming calls"</string>
     <string name="notification_channel_missed_call" msgid="8727062678632713146">"Missed calls"</string>
-    <string name="notification_channel_call_blocking" msgid="2943358779746676070">"Call Blocking"</string>
     <string name="alert_outgoing_call" msgid="982908156825958001">"Placing this call will end your <xliff:g id="OTHER_APP">%1$s</xliff:g> call."</string>
-    <string name="phone_settings_call_blocking_txt" msgid="3976004073043846733">"Call Blocking"</string>
-    <string name="phone_settings_number_not_in_contact_txt" msgid="3126829421867168652">"Numbers not in Contacts"</string>
-    <string name="phone_settings_number_not_in_contact_summary_txt" msgid="9043147855140079119">"Block numbers that are not listed in your Contacts"</string>
-    <string name="phone_settings_private_num_txt" msgid="8623574188879134262">"Private"</string>
-    <string name="phone_settings_private_num_summary_txt" msgid="7516314821207782191">"Block callers who do not disclose their number"</string>
-    <string name="phone_settings_payphone_txt" msgid="2493356957416981318">"Phonebox"</string>
-    <string name="phone_settings_payphone_summary_txt" msgid="6126709946103814653">"Block calls from pay phones"</string>
-    <string name="phone_settings_unknown_txt" msgid="5836407031508172721">"Unknown"</string>
-    <string name="phone_settings_unknown_summary_txt" msgid="3457690230497753233">"Block calls from unidentified callers"</string>
-    <string name="phone_strings_call_blocking_turned_off_notification_title_txt" msgid="628536625775266096">"Call Blocking"</string>
-    <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Call Blocking disabled"</string>
-    <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Emergency call made"</string>
-    <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Call Blocking has been disabled to allow emergency responders to contact you."</string>
 </resources>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index d62ac55..fbbaafc 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Quick response"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Message sent to <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Message failed to send to <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"Calling accounts"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Only emergency calls are allowed."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"This application cannot make outgoing calls without Phone permission."</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Call Blocking disabled"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Emergency call made"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Call Blocking has been disabled to allow emergency responders to contact you."</string>
+    <string name="developer_title" msgid="1816273446906554627">"Telecom Developer Menu"</string>
 </resources>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index d62ac55..fbbaafc 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Quick response"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Message sent to <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Message failed to send to <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"Calling accounts"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Only emergency calls are allowed."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"This application cannot make outgoing calls without Phone permission."</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Call Blocking disabled"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Emergency call made"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Call Blocking has been disabled to allow emergency responders to contact you."</string>
+    <string name="developer_title" msgid="1816273446906554627">"Telecom Developer Menu"</string>
 </resources>
diff --git a/res/values-en-rXC/strings.xml b/res/values-en-rXC/strings.xml
index 712b6e0..26daa02 100644
--- a/res/values-en-rXC/strings.xml
+++ b/res/values-en-rXC/strings.xml
@@ -82,19 +82,5 @@
     <string name="cant_call_due_to_ongoing_unknown_call" msgid="149091978697302211">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‎‏‎‏‎‎‎‎‏‎‎‎‏‏‎‏‎‏‏‏‎‎‏‎‏‏‏‎‏‎‏‎‏‏‏‎‏‎‎‎‏‏‏‎‎‏‏‏‎‎‎‎‎‏‏‎‎‎‎‏‏‎Call cannot be placed due to a call in another app.‎‏‎‎‏‎"</string>
     <string name="notification_channel_incoming_call" msgid="3513761697082968084">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‎‏‏‎‎‎‎‏‏‎‎‎‎‏‏‎‏‏‎‎‎‏‏‏‎‏‎‎‏‎‏‏‎‎‏‎‏‎‎‎‎‎‎‎‎‎‏‎‎‎‎‏‎‎‎‎‎‎‏‎‏‎‎‎Incoming calls‎‏‎‎‏‎"</string>
     <string name="notification_channel_missed_call" msgid="8727062678632713146">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‎‎‎‏‏‏‎‎‏‏‎‎‎‎‎‏‎‏‎‎‎‏‎‎‏‎‏‎‏‏‎‎‎‏‏‏‏‎‎‎‏‏‎‎‏‎‏‏‏‎‏‏‏‎‏‎‎Missed calls‎‏‎‎‏‎"</string>
-    <string name="notification_channel_call_blocking" msgid="2943358779746676070">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‎‏‎‏‎‎‎‏‏‎‏‏‎‎‎‏‏‏‎‏‎‎‏‎‎‏‏‎‏‏‏‎‎‏‏‎‎‎‏‎‏‎‏‎‎‏‏‏‏‏‎‏‏‎‏‎‏‏‎‎‏‏‎‎Call Blocking‎‏‎‎‏‎"</string>
     <string name="alert_outgoing_call" msgid="982908156825958001">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‎‎‏‏‎‏‏‎‏‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‎‎‎‎‏‎‏‏‎‎‏‏‏‎‎‏‏‏‎‎‎‏‏‏‏‏‎‏‏‎‎‏‏‏‎‎‎‏‎Placing this call will end your ‎‏‎‎‏‏‎<xliff:g id="OTHER_APP">%1$s</xliff:g>‎‏‎‎‏‏‏‎ call.‎‏‎‎‏‎"</string>
-    <string name="phone_settings_call_blocking_txt" msgid="3976004073043846733">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‏‎‎‏‎‏‏‎‏‏‎‎‏‏‎‏‎‏‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‎‎‏‎‎‎‏‏‏‎‎‏‏‎‎‏‎‎‏‏‎‏‎Call Blocking‎‏‎‎‏‎"</string>
-    <string name="phone_settings_number_not_in_contact_txt" msgid="3126829421867168652">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‎‏‎‏‎‏‏‎‏‏‎‎‏‎‎‏‎‏‏‏‎‏‎‏‏‎‎‏‎‏‎‏‏‏‎‎‎‏‏‏‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‎‎‎‏‏‎‎‎Numbers not in Contacts‎‏‎‎‏‎"</string>
-    <string name="phone_settings_number_not_in_contact_summary_txt" msgid="9043147855140079119">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‏‏‏‏‏‏‏‎‏‏‎‏‏‏‎‎‎‎‏‏‏‏‏‏‎‎‎‏‎‎‏‏‏‏‎‏‎‏‏‏‎‎‎‏‏‎‎‎‎‎‏‏‏‏‎Block numbers that are not listed in your Contacts‎‏‎‎‏‎"</string>
-    <string name="phone_settings_private_num_txt" msgid="8623574188879134262">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‏‏‎‏‎‏‏‎‏‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‏‏‏‏‏‏‏‎‎‏‎‏‏‏‏‎‎‎‏‏‏‏‏‎‎‎‏‏‎‏‏‎‎Private‎‏‎‎‏‎"</string>
-    <string name="phone_settings_private_num_summary_txt" msgid="7516314821207782191">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‎‎‏‎‎‏‏‏‏‎‏‎‏‎‎‎‎‏‎‎‎‎‎‎‏‏‎‏‏‏‎‏‎‎‎‎‏‎‏‏‏‏‏‏‏‎‎‏‏‎‎‏‎‏‏‏‏‎Block callers that do not disclose their number‎‏‎‎‏‎"</string>
-    <string name="phone_settings_payphone_txt" msgid="2493356957416981318">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‎‏‎‎‎‏‎‏‎‎‏‏‎‏‎‎‎‏‎‏‏‏‎‏‏‏‏‎‎‎‏‎‏‎‎‎‎‎‏‎‎‎‏‎‏‏‎‏‎‎‎‎‎‏‏‎‏‎‎‎‏‏‎‎Pay phone‎‏‎‎‏‎"</string>
-    <string name="phone_settings_payphone_summary_txt" msgid="6126709946103814653">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‏‏‎‏‎‏‎‏‎‎‎‎‎‏‏‎‎‏‏‏‎‎‏‎‎‎‏‏‏‏‎‎‎‎‏‏‏‎‏‏‎‏‎‏‏‏‏‎‏‏‎‏‏‎‎‏‏‏‏‏‏‏‎‏‎Block calls from pay phones‎‏‎‎‏‎"</string>
-    <string name="phone_settings_unknown_txt" msgid="5836407031508172721">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‏‏‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‎‏‎‏‎‏‎‎‎‏‎‏‏‏‎‎‎‏‎‏‏‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‏‎‏‏‎‎‎‏‎Unknown‎‏‎‎‏‎"</string>
-    <string name="phone_settings_unknown_summary_txt" msgid="3457690230497753233">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‎‏‎‏‏‏‏‏‏‏‏‏‏‎‎‎‎‏‎‏‏‏‎‏‏‏‏‎‎‎‏‏‏‏‎‏‏‎‎‎‎‏‎‏‎‎‎‎‎‏‏‏‎‎‎‏‎‎‏‎‎‎‏‎Block calls from unidentified callers‎‏‎‎‏‎"</string>
-    <string name="phone_strings_call_blocking_turned_off_notification_title_txt" msgid="628536625775266096">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‎‎‏‎‎‎‏‎‏‏‏‎‎‏‎‎‎‎‎‎‏‎‏‎‏‏‏‎‏‏‎‎‏‎‎‎‏‏‏‎‏‎‏‎‎‎‎‏‎‎‏‎‎‏‎‎‏‏‎‎‎‎‎Call Blocking‎‏‎‎‏‎"</string>
-    <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‏‏‎‏‎‏‏‎‏‏‏‎‏‏‏‏‎‎‎‎‎‏‎‎‎‎‎‎‏‎‏‎‎‏‎‎‏‎‏‎‏‏‎‏‏‎‏‎‏‏‏‎‏‎‎‏‎‏‏‏‎‏‎‏‎Call Blocking disabled‎‏‎‎‏‎"</string>
-    <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‎‏‏‏‏‏‏‏‎‏‏‎‏‏‏‎‎‏‎‎‎‏‎‏‎‏‏‏‏‏‎‎‏‎‏‎‏‏‎‎‏‏‏‎‏‎‎‎‎‏‎‏‏‎‏‎‎‎Emergency call made‎‏‎‎‏‎"</string>
-    <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‎‏‎‏‎‏‎‏‎‏‎‏‏‏‏‏‎‎‎‏‎‏‎‎‏‏‎‏‏‏‎‏‏‎‏‏‏‏‏‎‎‎‎‏‏‏‎‎‏‎‏‎‏‏‏‎‎‎Call Blocking has been disabled to allow emergency responders to contact you.‎‏‎‎‏‎"</string>
 </resources>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index 35f2cd7..e072cc2 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Respuesta rápida"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Mensaje enviado a <xliff:g id="PHONE_NUMBER">%s</xliff:g>"</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"No se pudo enviar el mensaje al <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"Cuentas telefónicas"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Solo se permiten llamadas de emergencia."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Esta aplicación no puede realizar llamadas salientes sin permiso del teléfono."</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Se inhabilitó el bloqueo de llamadas"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Se realizó una llamada de emergencia"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Se inhabilitó el bloqueo de llamadas para permitir que los servicios de emergencia se comuniquen contigo."</string>
+    <string name="developer_title" msgid="1816273446906554627">"Menú para desarrolladores de Telecom"</string>
 </resources>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index 43fbb9f..7629edd 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Respuesta rápida"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Mensaje enviado a <xliff:g id="PHONE_NUMBER">%s</xliff:g>"</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"No se ha podido enviar el mensaje al <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"Cuentas de llamadas"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Solo se permiten llamadas de emergencia."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Esta aplicación no puede hacer llamadas sin permiso del teléfono."</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Se ha inhabilitado el bloqueo de llamadas"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Se ha hecho una llamada de emergencia"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Se ha inhabilitado el bloqueo de llamadas para que los servicios de emergencia puedan ponerse en contacto contigo."</string>
+    <string name="developer_title" msgid="1816273446906554627">"Menú para desarrolladores de telecomunicaciones"</string>
 </resources>
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
index 53443ca..2d0f430 100644
--- a/res/values-et/strings.xml
+++ b/res/values-et/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Kiirvastus"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Sõnum on saadetud numbrile <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Sõnumi saatmine numbrile <xliff:g id="PHONE_NUMBER">%s</xliff:g> ebaõnnestus."</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"Kõnekontod"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Lubatud on ainult hädaabikõned."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"See rakendus ei saa ilma telefoni kasutamise loata välja helistada."</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Kõnede blokeerimine on keelatud"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Tehti hädaabikõne"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Kõnede blokeerimine on keelatud, et lubada hädaabiteenustel teiega ühendust võtta."</string>
+    <string name="developer_title" msgid="1816273446906554627">"Teenuse Telecom arendaja menüü"</string>
 </resources>
diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml
index 094e813..e938424 100644
--- a/res/values-eu/strings.xml
+++ b/res/values-eu/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Erantzun bizkorra"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Mezua bidali da <xliff:g id="PHONE_NUMBER">%s</xliff:g> zenbakira."</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Ezin izan da bidali mezua <xliff:g id="PHONE_NUMBER">%s</xliff:g> zenbakira."</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"Deiak egiteko kontuak"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Larrialdi-deiak bakarrik egin daitezke."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Aplikazioak deitu ahal izan dezan, telefonoaren eginbidea erabiltzeko baimena behar du."</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Desgaitu da deiak blokeatzeko aukera"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Larrialdi-deia egin da"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Desgaitu da deiak blokeatzeko aukera, larrialdietako zerbitzuak zurekin harremanetan jarri ahal daitezen."</string>
+    <string name="developer_title" msgid="1816273446906554627">"Telekomunikazioen garatzaileen menua"</string>
 </resources>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index b3f8d1e..94cd288 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"پاسخ سریع"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"پیام به <xliff:g id="PHONE_NUMBER">%s</xliff:g> ارسال شد."</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"پیام به <xliff:g id="PHONE_NUMBER">%s</xliff:g> ارسال نشد."</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"حساب‌های تماس"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"فقط تماس‌های اضطراری مجاز است."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"این برنامه نمی‌تواند بدون اجازه تلفن، تماس‌های خروجی برقرار کند."</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"مسدود کردن تماس غیرفعال شد"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"تماس اضطراری برقرار شد"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"مسدود کردن تماس غیرفعال شده است تا پاسخ‌دهندگان اضطراری بتوانند با شما تماس بگیرند."</string>
+    <string name="developer_title" msgid="1816273446906554627">"‏منوی برنامه‌نویس Telecom"</string>
 </resources>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index 73cb390..de6baf8 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Pikavastaukset"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Viesti lähetetty numeroon <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Viestin lähetys numeroon <xliff:g id="PHONE_NUMBER">%s</xliff:g> epäonnistui."</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"Puhelutilit"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Vain hätäpuhelut sallittu"</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Tämä sovellus ei voi soittaa puheluita ilman Puhelin-lupaa."</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Puhelujen esto poistettu käytöstä"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Hätäpuhelu soitettu"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Puhelujen esto on poistettu käytöstä, jotta pelastusviranomaiset voivat soittaa puhelimeesi."</string>
+    <string name="developer_title" msgid="1816273446906554627">"Televiestinnän kehittäjävalikko"</string>
 </resources>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index b90b2ff..7e739dc 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Réponse rapide"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Message envoyé à <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Échec de l\'envoi du message au <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"Comptes d\'appel"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Seuls les appels d\'urgence sont autorisés."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Cette application ne peut pas faire d\'appels sans l\'autorisation de l\'application Téléphone."</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Blocage des appels désactivé"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Appel d\'urgence effectué"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Le blocage des appels a été désactivé pour permettre aux intervenants d\'urgence de communiquer avec vous."</string>
+    <string name="developer_title" msgid="1816273446906554627">"Menu Telecom Developer"</string>
 </resources>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index f7a5a00..afa7c4a 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Réponse rapide"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Message envoyé à <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Échec de l\'envoi du message au <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"Comptes téléphoniques"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Seuls les appels d\'urgence sont autorisés."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Cette application ne peut pas passer d\'appels sortants sans l\'autorisation de l\'application Téléphone."</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Blocage d\'appels désactivé"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Appel d\'urgence"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Le blocage d\'appels a été désactivé pour que les services d\'urgence puissent vous contacter."</string>
+    <string name="developer_title" msgid="1816273446906554627">"Menu Telecom Developer"</string>
 </resources>
diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml
index b30dbbc..459b637 100644
--- a/res/values-gl/strings.xml
+++ b/res/values-gl/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Resposta rápida"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Mensaxe enviada ao <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Non se puido enviar a mensaxe ao <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"Contas de chamadas"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Só se permiten chamadas de urxencia."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Esta aplicación non pode facer chamadas saíntes sen permiso do teléfono."</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Desactivouse o bloqueo de chamadas"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Realizouse unha chamada de urxencia"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Desactivouse o bloqueo de chamadas parar permitir que os servizos de urxencias se poidan poñer en contacto contigo."</string>
+    <string name="developer_title" msgid="1816273446906554627">"Menú para programadores de telecomunicacións"</string>
 </resources>
diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml
index 950a0ed..dca0714 100644
--- a/res/values-gu/strings.xml
+++ b/res/values-gu/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"ઝડપી પ્રતિસાદ"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"<xliff:g id="PHONE_NUMBER">%s</xliff:g> પર સંદેશ મોકલ્યો."</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"<xliff:g id="PHONE_NUMBER">%s</xliff:g>ને સંદેશ મોકલવામાં નિષ્ફળ રહ્યાં."</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"કૉલિંગ એકાઉન્ટ્સ"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"ફક્ત કટોકટીના કૉલ્સને મંજૂરી છે."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"ફોન પરવાનગી વિના આ ઍપ્લિકેશન આઉટગોઇંગ કૉલ્સ કરી શકતી નથી."</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"કૉલ બ્લૉક કરવાનું બંધ કરવામાં આવ્યું છે"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"કટોકટીનો કૉલ કર્યો"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"કટોકટીમાં પ્રતિસાદ કરનારાઓ તમારો સંપર્ક કરી શકે તે માટે કૉલ બ્લૉક કરવાનું બંધ કરવામાં આવ્યું છે."</string>
+    <string name="developer_title" msgid="1816273446906554627">"ટેલિકોમ ડેવલપર મેનૂ"</string>
 </resources>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index a804a8b..5eed8fa 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"झटपट उत्तर"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"<xliff:g id="PHONE_NUMBER">%s</xliff:g> को संदेश भेजा गया."</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"<xliff:g id="PHONE_NUMBER">%s</xliff:g> पर मैसेज नहीं भेजा जा सका."</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"कॉलिंग खाते"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"केवल आपातकालीन कॉल की अनुमति है."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"यह ऐप्‍लिकेशन फ़ोन अनुमति के बिना आउटगोइंग कॉल नहीं कर सकता."</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"कॉल पर रोक लगाने की सुविधा बंद है"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"आपातकालीन कॉल किया गया"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"आपातकालीन सहायता कर्मचारी आपसे संपर्क कर सकें, इसलिए कॉल पर रोक लगाने की सुविधा बंद कर दी गई है."</string>
+    <string name="developer_title" msgid="1816273446906554627">"टेलीकॉम डेवलपर मेन्यू"</string>
 </resources>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index 1b46287..0aa3743 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Brzi odgovor"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Poruka poslana na broj <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Slanje poruke na <xliff:g id="PHONE_NUMBER">%s</xliff:g> nije uspjelo."</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"Računi za pozivanje"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Dopušteni su samo hitni pozivi."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Ova aplikacija ne može uspostavljati odlazne pozive bez dopuštenja za telefon."</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Blokiranje poziva je onemogućeno"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Hitni je poziv upućen"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Blokiranje poziva onemogućeno je da bi vas mogli kontaktirati djelatnici hitnih službi."</string>
+    <string name="developer_title" msgid="1816273446906554627">"Izbornik Telecom Developer"</string>
 </resources>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index effd923..e0e5c1b 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Gyors válasz"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Üzenet elküldve ide: <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Sikertelen üzenetküldés (szám: <xliff:g id="PHONE_NUMBER">%s</xliff:g>)."</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"Telefonos fiókok"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Csak segélyhívás engedélyezett."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Az alkalmazásból nem lehet kimenő hívást kezdeményezni a Telefon (Phone) engedély nélkül."</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Hívásletiltás kikapcsolva"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Segélyhívás indítva"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"A hívásletiltás ki van kapcsolva, hogy a segélyszolgálatok kapcsolatba léphessenek Önnel."</string>
+    <string name="developer_title" msgid="1816273446906554627">"Telekommunikációs fejlesztői menü"</string>
 </resources>
diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml
index 6782685..e6d3cd5 100644
--- a/res/values-hy/strings.xml
+++ b/res/values-hy/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Արագ պատասխան"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Հաղորդագրությունն ուղարկվել է <xliff:g id="PHONE_NUMBER">%s</xliff:g>-ին:"</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"SMS-ը չհաջողվեց ուղարկել <xliff:g id="PHONE_NUMBER">%s</xliff:g> համարին:"</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"Հաշիվներ զանգերի համար"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Միայն արտակարգ իրավիճակների զանգերն են թույլատրվում:"</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Առանց Հեռախոսի թույլտվության այս ծրագիրը չի կարող ելքային զանգեր կատարել:"</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Զանգերի արգելափակումն անջատած է"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Կատարվեց շտապ կանչ"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Զանգերի արգելափակումն անջատվել է, որպեսզի արտակարգ ծառայությունները կարողանան ձեզ զանգել:"</string>
+    <string name="developer_title" msgid="1816273446906554627">"Telecom-ի մշակողի ընտրացանկ"</string>
 </resources>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index 0d9a31f..2a613da 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Respons cepat"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Pesan dikirim ke <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Pesan gagal dikirim ke <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"Akun pemanggil"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Hanya panggilan darurat yang diizinkan."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Aplikasi ini tidak dapat melakukan panggilan keluar tanpa izin Telepon."</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Pemblokiran Panggilan dinonaktifkan"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Panggilan darurat dibuat"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Pemblokiran Panggilan dinonaktifkan untuk mengizinkan penjawab darurat menghubungi Anda."</string>
+    <string name="developer_title" msgid="1816273446906554627">"Menu Developer Telecom"</string>
 </resources>
diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml
index c41f657..9d1b7c4 100644
--- a/res/values-is/strings.xml
+++ b/res/values-is/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Snarsvar"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Skilaboð send til <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Ekki tókst að senda skilaboðin í <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"Símtalareikningar"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Aðeins neyðarsímöl eru leyfð."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Þetta forrit getur ekki hringt án heimildar í símanum."</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Ekki er lokað fyrir símtöl"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Neyðarsímtal var hringt"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Slökkt hefur verið á „Lokað fyrir símtöl“ svo neyðarþjónustuaðilar geti haft samband við þig."</string>
+    <string name="developer_title" msgid="1816273446906554627">"Forritaravalmynd fyrir fjarskipti"</string>
 </resources>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index e5e63cc..70c4ce5 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Risposta rapida"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Messaggio inviato a <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Impossibile inviare il messaggio a <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"Account di chiamata"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Sono consentite soltanto le chiamate di emergenza."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Non è possibile effettuare chiamate tramite questa applicazione senza l\'autorizzazione sul telefono."</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Blocco delle chiamate disattivato"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Chiamata di emergenza effettuata"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Il blocco delle chiamate è stato disattivato per consentire ai servizi di emergenza di contattarti."</string>
+    <string name="developer_title" msgid="1816273446906554627">"Menu sviluppatore telecomunicazioni"</string>
 </resources>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index 9027561..0bca54e 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"תגובה מהירה"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"הודעה נשלחה אל <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"לא ניתן היה לשלוח את ההודעה ל-<xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"חשבונות לביצוע שיחות"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"ניתן לבצע רק שיחות חירום."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"לא ניתן לבצע שיחות יוצאות באמצעות האפליקציה הזו ללא ההרשאה \'טלפון\'."</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"חסימת השיחות הושבתה"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"בוצעה שיחת חירום"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"חסימת השיחות הושבתה כדי לאפשר לצוותי עזרה ראשונה להתקשר אליך."</string>
+    <string name="developer_title" msgid="1816273446906554627">"תפריט למפתחי מערכות תקשורת"</string>
 </resources>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index 70543e7..4ab68cc 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"クイック返信"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"<xliff:g id="PHONE_NUMBER">%s</xliff:g>にメッセージを送信しました。"</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"<xliff:g id="PHONE_NUMBER">%s</xliff:g> 宛にメッセージを送信できませんでした。"</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"通話アカウント"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"許可されているのは緊急通報のみです。"</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"このアプリは、電話権限がないため発信できません。"</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"着信のブロックを無効にしました"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"緊急通報"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"着信のブロックを無効して、救急隊員などがあなたに連絡できるようにしました。"</string>
+    <string name="developer_title" msgid="1816273446906554627">"Telecom デベロッパー メニュー"</string>
 </resources>
diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml
index 1a39384..d94aa2c 100644
--- a/res/values-ka/strings.xml
+++ b/res/values-ka/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"სწრაფი პასუხი"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"შეტყობინება გაიგზავნა <xliff:g id="PHONE_NUMBER">%s</xliff:g>-თან."</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"შეტყობინება ვერ გაიგზავნა <xliff:g id="PHONE_NUMBER">%s</xliff:g>-ზე."</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"დარეკვის ანგარიშები"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"შესაძლებელია მხოლოდ გადაუდებელი ზარების განხორციელება."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"ეს აპლიკაცია ტელეფონის ნებართვის გარეშე გამავალ ზარებს ვერ განახორციელებს."</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"ზარების დაბლოკვა გათიშულია"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"საგანგებო ზარი შესრულდა"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"ზარების დაბლოკვა გათიშულია, რათა საგანგებო სიტუაციებში მოპასუხეებმა თქვენთან დაკავშირება შეძლონ"</string>
+    <string name="developer_title" msgid="1816273446906554627">"Telecom-ის დეველოპერის მენიუ"</string>
 </resources>
diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml
index 2e9d572..58d0cbf 100644
--- a/res/values-kk/strings.xml
+++ b/res/values-kk/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Жылдам жауап"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Хабар <xliff:g id="PHONE_NUMBER">%s</xliff:g> нөміріне жіберілді."</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"<xliff:g id="PHONE_NUMBER">%s</xliff:g> нөміріне хабар жіберілмеді."</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"Қоңырау шалу есептік жазбалары"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Тек төтенше қоңырауларға рұқсат етілген."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"\"Телефон\" рұқсатынсыз бұл қолданба шығыс қоңырауларды соға алмайды."</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Қоңырау бөгеу функциясы өшірулі"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Төтенше жағдай қоңырауы шалынды"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Төтенше жағдай қызметтері сізге хабарласа алуы үшін, қоңырау бөгеу функциясы өшірілді."</string>
+    <string name="developer_title" msgid="1816273446906554627">"Telecom Developer мәзірі"</string>
 </resources>
diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml
index 4fbf16c..f785707 100644
--- a/res/values-km/strings.xml
+++ b/res/values-km/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"ឆ្លើយតប​រហ័ស"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"បាន​ផ្ញើ​សារ​ទៅ <xliff:g id="PHONE_NUMBER">%s</xliff:g> ។"</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"មិន​អាច​ផ្ញើ​សារ​ទៅ <xliff:g id="PHONE_NUMBER">%s</xliff:g> បាន​ទេ។"</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"គណនីហៅទូរសព្ទ"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"អនុញ្ញាតតែការហៅពេលមានអាសន្នប៉ុណ្ណោះ"</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"កម្មវិធីនេះមិនអាចធ្វើការហៅចេញដោយគ្មានការអនុញ្ញាត ទូរស័ព្ទ បានទេ។"</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"បាន​បិទ​ការទប់ស្កាត់​ការហៅ"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"បាន​ធ្វើការហៅ​បន្ទាន់"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"ការទប់ស្កាត់​ការហៅ​ត្រូវបាន​បិទ ដើម្បី​អនុញ្ញាត​ឲ្យ​អ្នក​ឆ្លើយតប​បន្ទាន់​អាច​ទាក់ទង​អ្នក​បាន។"</string>
+    <string name="developer_title" msgid="1816273446906554627">"ម៉ឺនុយ​អ្នក​អភិវឌ្ឍន៍​ទូរគមនាគមន៍"</string>
 </resources>
diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml
index 1680bd6..e974cef 100644
--- a/res/values-kn/strings.xml
+++ b/res/values-kn/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"ತ್ವರಿತ ಪ್ರತಿಕ್ರಿಯೆ"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"<xliff:g id="PHONE_NUMBER">%s</xliff:g> ಗೆ ಸಂದೇಶ ಕಳುಹಿಸಲಾಗಿದೆ."</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"<xliff:g id="PHONE_NUMBER">%s</xliff:g> ಫೋನ್ ಸಂಖ್ಯೆಗೆ ಸಂದೇಶವನ್ನು ಕಳುಹಿಸುವಲ್ಲಿ ವಿಫಲವಾಗಿದೆ."</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"ಕರೆ ಮಾಡುವ ಖಾತೆಗಳು"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"ತುರ್ತು ಕರೆಗಳನ್ನು ಮಾಡಲು ಮಾತ್ರ ಅವಕಾಶವಿದೆ."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"ಈ ಅಪ್ಲಿಕೇಶನ್ ಫೋನ್ ಅನುಮತಿಯಿಲ್ಲದೆ ಹೊರಹೋಗುವ ಕರೆಗಳನ್ನು ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ."</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"ಕರೆ ನಿರ್ಬಂಧಿಸುವಿಕೆಯನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"ತುರ್ತು ಕರೆ ಮಾಡಲಾಗಿದೆ"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"ನಿಮ್ಮನ್ನು ಸಂಪರ್ಕಿಸುವುದಕ್ಕಾಗಿ ತುರ್ತಾಗಿ ಪ್ರತಿಕ್ರಿಯಿಸುವವರ ಸಂಖ್ಯೆಯನ್ನು ನಿರ್ಬಂಧಿಸುವುದನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ."</string>
+    <string name="developer_title" msgid="1816273446906554627">"ಟೆಲಿಕಾಂ ಡೆವಲಪರ್ ಮೆನು"</string>
 </resources>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index 22fcde6..8f94111 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"빠른 응답"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"<xliff:g id="PHONE_NUMBER">%s</xliff:g>(으)로 메시지를 보냈습니다."</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"<xliff:g id="PHONE_NUMBER">%s</xliff:g>(으)로 메시지를 보내지 못했습니다."</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"통화 계정"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"긴급 전화만 허용됩니다."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"전화 권한이 없으므로 애플리케이션에서 발신 전화를 걸 수 없습니다."</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"통화 차단이 사용 중지됨"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"긴급 통화가 사용됨"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"응급 구조 요원이 연락할 수 있도록 통화 차단이 사용 중지되었습니다."</string>
+    <string name="developer_title" msgid="1816273446906554627">"Telecom 개발자 메뉴"</string>
 </resources>
diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml
index 760fdb1..ed2f143 100644
--- a/res/values-ky/strings.xml
+++ b/res/values-ky/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Тез жооп"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"<xliff:g id="PHONE_NUMBER">%s</xliff:g> номуруна билдирүү жөнөтүлдү."</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Билдирүү <xliff:g id="PHONE_NUMBER">%s</xliff:g> номерине жөнөтүлбөй калды."</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"Чалуу каттоо эсептери"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Шашылыш чалууларга гана уруксат берилген."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Бул колдонмо тийиштүү уруксатсыз чалууларды жасай албайт."</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Чалууну бөгөттөө өчүрүлдү"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Шашылыш чалуу аткарылды"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Өзгөчө кырдаалдардагы кызматчылар сиз менен байланышуусу үчүн чалууну бөгөттөө функциясы өчүрүлгөн."</string>
+    <string name="developer_title" msgid="1816273446906554627">"Telecom иштеп чыгуучусунун менюсу"</string>
 </resources>
diff --git a/res/values-lo/strings.xml b/res/values-lo/strings.xml
index 4c7ec9e..6698e14 100644
--- a/res/values-lo/strings.xml
+++ b/res/values-lo/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"ຕອບກັບດ່ວນ"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"ສົ່ງຂໍ້ຄວາມຫາ <xliff:g id="PHONE_NUMBER">%s</xliff:g> ແລ້ວ."</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"ສົ່ງຂໍ້ຄວາມໄປຫາ <xliff:g id="PHONE_NUMBER">%s</xliff:g> ບໍ່ສຳເລັດ."</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"ບັນ​ຊີ​ໂທ"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"ອະນຸຍາດໃຫ້ໂທສຸກເສີນເທົ່ານັ້ນ."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"ແອັບ​ພ​ລິ​ເຄ​ຊັນ​ນີ້​ບໍ່​ສາ​ມາດ​ໂທ​ອອກ​ໄດ້ ໂດຍ​ບໍ່​ມີ​ການ​ອະ​ນຸ​ຍາດ​ຂອງ​ໂທ​ລະ​ສັບ."</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"ປິດການບລັອກສາຍແລ້ວ"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"ໂທສຸກເສີນແລ້ວ"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"ປິດການບລັອກສາຍແລ້ວເພື່ອອະນຸຍາດໃຫ້ສາຍສຸກເສີນສາມາດຕິດຕໍ່ຫາທ່ານໄດ້."</string>
+    <string name="developer_title" msgid="1816273446906554627">"ເມນູນັກພັດທະນາໂທລະຄົມ"</string>
 </resources>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index 4891114..e00e318 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Greitas atsakas"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Pranešimas išsiųstas numeriu <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Nepavyko išsiųsti pranešimo numeriu <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"Skambinimo paskyros"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Leidžiami tik skambučiai pagalbos numeriu."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Naudojant šią programą negalima skambinti be telefono leidimo."</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Skambučių blokavimas išjungtas"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Atliktas skambutis pagalbos numeriu"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Skambučių blokavimas išjungtas, kad pagalbos numeriu atsiliepusiems žmonėms būtų leidžiama su jumis susisiekti."</string>
+    <string name="developer_title" msgid="1816273446906554627">"Telekomunikacijų kūrėjų meniu"</string>
 </resources>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index 8b8d36a..bfd6c27 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Ātrā atbilde"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Ziņojums nosūt. uz šādu tālr. nr.: <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Neizdevās nosūtīt ziņojumu uz numuru <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"Zvanu konti"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Ir atļauti tikai ārkārtas zvani."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Šajā lietojumprogrammā nevar veikt izejošos zvanus bez tālruņa atļaujas."</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Zvanu bloķēšana atspējota"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Ārkārtas zvans ir veikts"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Zvanu bloķēšana ir atspējota, lai ļautu ar jums sazināties avārijas dienestu darbiniekiem."</string>
+    <string name="developer_title" msgid="1816273446906554627">"Telecom izstrādātāja izvēlne"</string>
 </resources>
diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml
index 6fcfa2a..68337ac 100644
--- a/res/values-mk/strings.xml
+++ b/res/values-mk/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Брз одговор"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Порака е испратена на <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Пораката не можеше да се испрати на <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"Сметки за повици"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Дозволени се само итни повици."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Оваа апликација не може да прави појдовни повици без дозволата Телефон."</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Блокирањето повици е оневозможено"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Воспоставен е итен повик"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Блокирањето повици е оневозможено за да им се овозможи на лицата од службите за итни случаи да контактираат со вас."</string>
+    <string name="developer_title" msgid="1816273446906554627">"Програмерско мени за телекомуникации"</string>
 </resources>
diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml
index 0ce1758..bdd85f8 100644
--- a/res/values-ml/strings.xml
+++ b/res/values-ml/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"ദ്രുത പ്രതികരണം"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"<xliff:g id="PHONE_NUMBER">%s</xliff:g> എന്നതിലേക്ക് സന്ദേശമയച്ചു."</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"<xliff:g id="PHONE_NUMBER">%s</xliff:g> എന്ന നമ്പറിലേക്ക് സന്ദേശം അയക്കുന്നതിൽ പരാജയപ്പെട്ടു."</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"കോളിംഗ് അക്കൗണ്ട്"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"അടിയന്തിര കോളുകൾ മാത്രമേ അനുവദിച്ചിട്ടുള്ളൂ."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"ഫോൺ അനുമതിയില്ലാതെ ഈ അപ്ലിക്കേഷന് ഔട്ട്‌ഗോയിംഗ് കോളുകൾ വിളിക്കാൻ കഴിയില്ല."</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"കോൾ ബ്ലോക്ക് ചെയ്യൽ പ്രവർത്തനരഹിതമാക്കി"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"അടിയന്തര കോൾ ചെയ്തു"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"അടിയന്തരമായി ബന്ധപ്പെടുന്നവരെ അനുവദിക്കാനായി കോൾ ബ്ലോക്ക് ചെയ്യൽ പ്രവർത്തനരഹിതമാക്കി."</string>
+    <string name="developer_title" msgid="1816273446906554627">"ടെലികോം ഡെവലപ്പര്‍ മെനു"</string>
 </resources>
diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml
index 696e954..eee8bda 100644
--- a/res/values-mn/strings.xml
+++ b/res/values-mn/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Шуурхай хариу"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Зурвасыг <xliff:g id="PHONE_NUMBER">%s</xliff:g> руу илгээв."</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"<xliff:g id="PHONE_NUMBER">%s</xliff:g> руу зурвас илгээж чадсангүй."</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"Дуудлагын эрхтэй бүртгэлүүд"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Зөвхөн яаралтай тусламжийн дуудлага хийх боломжтой."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Энэ апп нь утасны зөвшөөрөлгүйгээр дуудлага хийх боломжгүй."</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Дуудлага хориглохыг идэвхгүй болгосон"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Яаралтай тусламжийн дуудлага хийсэн"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Яаралтай тусламжийнханд тантай холбогдохыг зөвшөөрөхийн тулд дуудлага хориглохыг идэвхгүй болгосон."</string>
+    <string name="developer_title" msgid="1816273446906554627">"Телеком хөгжүүлэгчийн цэс"</string>
 </resources>
diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml
index e9845fc..a1b463c 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"द्रुत प्रतिसाद"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"मेसेज <xliff:g id="PHONE_NUMBER">%s</xliff:g> वर पाठविला."</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"<xliff:g id="PHONE_NUMBER">%s</xliff:g> वर मेसेज पाठवता आला नाही."</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"कॉल करण्याची खाती"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"फक्त आणीबाणी कॉल करण्याची परवानगी आहे."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"हा अॅप्लिकेशन फोन परवानगी शिवाय कॉल करू शकत नाही."</string>
@@ -45,10 +46,10 @@
     <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="9101655962941740507">"<xliff:g id="NEW_APP">%s</xliff:g> ला तुमचा डीफॉल्ट अॅप बनवायचा?"</string>
+    <string name="change_default_dialer_dialog_title" msgid="9101655962941740507">"<xliff:g id="NEW_APP">%s</xliff:g> ला आपला डीफॉल्ट अॅप बनवायचा?"</string>
     <string name="change_default_dialer_dialog_affirmative" msgid="8606546663509166276">"डीफॉल्ट म्हणून सेट करा"</string>
     <string name="change_default_dialer_dialog_negative" msgid="9078144617060173845">"रद्द करा"</string>
-    <string name="change_default_dialer_warning_message" msgid="1417671460801684999">"<xliff:g id="NEW_APP">%s</xliff:g> कॉल करण्यात आणि त्याचे सर्व पैलू नियंत्रित करण्‍यात सक्षम असेल. ज्या अॅप्सवर तुमचा विश्वास आहे फक्त त्यांंनाच तुमचा डीफॉल्ट फोन अॅप म्हणून सेट करावे."</string>
+    <string name="change_default_dialer_warning_message" msgid="1417671460801684999">"<xliff:g id="NEW_APP">%s</xliff:g> कॉल करण्यात आणि त्याचे सर्व पैलू नियंत्रित करण्‍यात सक्षम असेल. ज्या अॅप्सवर आपला विश्वास आहे फक्त त्यांंनाच आपला डीफॉल्ट फोन अॅप म्हणून सेट करावे."</string>
     <string name="blocked_numbers" msgid="2751843139572970579">"ब्लॉक केलेले नंबर"</string>
     <string name="blocked_numbers_msg" msgid="1045015186124965643">"तुम्हाला ब्लॉक केलेल्या नंबरवरून कॉल किंवा मजकूर येणार नाहीत."</string>
     <string name="block_number" msgid="1101252256321306179">"एक नंबर जोडा"</string>
@@ -60,7 +61,7 @@
     <string name="non_primary_user" msgid="5180129233352533459">"फक्त डिव्हाइस मालक अवरोधित केलेले नंबर पाहू आणि व्यवस्थापित करू शकतो."</string>
     <string name="delete_icon_description" msgid="8903995728252556724">"ब्लॉक करा"</string>
     <string name="blocked_numbers_butter_bar_title" msgid="438170866438793182">"अवरोधित करणे तात्पुरते बंद आहे"</string>
-    <string name="blocked_numbers_butter_bar_body" msgid="2223244484319442431">"तुम्ही एखादा आणीबाणी नंबर डायल केला किंवा त्यावर मजकूर पाठविल्यानंतर, आणीबाणी सेवा आपल्याशी संपर्क साधू शकतात हे सुनिश्चित करण्यासाठी अवरोधित करणे बंद करते."</string>
+    <string name="blocked_numbers_butter_bar_body" msgid="2223244484319442431">"आपण एखादा आणीबाणी नंबर डायल केला किंवा त्यावर मजकूर पाठविल्यानंतर, आणीबाणी सेवा आपल्याशी संपर्क साधू शकतात हे सुनिश्चित करण्यासाठी अवरोधित करणे बंद करते."</string>
     <string name="blocked_numbers_butter_bar_button" msgid="2197943354922010696">"आता पुन्हा-सक्षम करा"</string>
     <string name="blocked_numbers_number_blocked_message" msgid="7678509606805029540">"<xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g> अवरोधित केला"</string>
     <string name="blocked_numbers_number_unblocked_message" msgid="977894647366750418">"<xliff:g id="UNBLOCKED_NUMBER">%1$s</xliff:g> अनावरोधित केला"</string>
@@ -69,12 +70,12 @@
     <string name="toast_personal_call_msg" msgid="5115361633476779723">"कॉल करण्यासाठी वैयक्तिक डायलर वापरणे"</string>
     <string name="notification_incoming_call" msgid="7713197997773986670">"<xliff:g id="CALL_FROM">%2$s</xliff:g> कडील <xliff:g id="CALL_VIA">%1$s</xliff:g> मधील कॉल"</string>
     <string name="notification_incoming_video_call" msgid="6638486071698373893">"<xliff:g id="CALL_FROM">%2$s</xliff:g> कडील <xliff:g id="CALL_VIA">%1$s</xliff:g> मधील व्हिडिओ कॉल"</string>
-    <string name="answering_ends_other_call" msgid="8282145910153766401">"उत्तर देण्यामुळे तुमचा <xliff:g id="CALL_VIA">%1$s</xliff:g> कॉल समाप्त होईल"</string>
-    <string name="answering_ends_other_calls" msgid="1198589551399049197">"उत्तर देण्यामुळे तुमचे <xliff:g id="CALL_VIA">%1$s</xliff:g> कॉल समाप्त होतील"</string>
-    <string name="answering_ends_other_video_call" msgid="8510410917384186360">"उत्तर देण्यामुळे तुमचा <xliff:g id="CALL_VIA">%1$s</xliff:g> व्हिडिओ कॉल समाप्त होईल"</string>
-    <string name="answering_ends_other_managed_call" msgid="5186137550267947785">"उत्तर देण्यामुळे तुमचा सुरु असलेला कॉल समाप्त होईल"</string>
-    <string name="answering_ends_other_managed_calls" msgid="6429838309560397988">"उत्तर देण्यामुळे तुमचे सुरु असलेले कॉल समाप्त होतील"</string>
-    <string name="answering_ends_other_managed_video_call" msgid="1585423762458248435">"उत्तर देण्यामुळे तुमचा सुरु असलेला व्हिडिओ कॉल समाप्त होईल"</string>
+    <string name="answering_ends_other_call" msgid="8282145910153766401">"उत्तर देण्यामुळे आपला <xliff:g id="CALL_VIA">%1$s</xliff:g> कॉल समाप्त होईल"</string>
+    <string name="answering_ends_other_calls" msgid="1198589551399049197">"उत्तर देण्यामुळे आपले <xliff:g id="CALL_VIA">%1$s</xliff:g> कॉल समाप्त होतील"</string>
+    <string name="answering_ends_other_video_call" msgid="8510410917384186360">"उत्तर देण्यामुळे आपला <xliff:g id="CALL_VIA">%1$s</xliff:g> व्हिडिओ कॉल समाप्त होईल"</string>
+    <string name="answering_ends_other_managed_call" msgid="5186137550267947785">"उत्तर देण्यामुळे आपला सुरु असलेला कॉल समाप्त होईल"</string>
+    <string name="answering_ends_other_managed_calls" msgid="6429838309560397988">"उत्तर देण्यामुळे आपले सुरु असलेले कॉल समाप्त होतील"</string>
+    <string name="answering_ends_other_managed_video_call" msgid="1585423762458248435">"उत्तर देण्यामुळे आपला सुरु असलेला व्हिडिओ कॉल समाप्त होईल"</string>
     <string name="answer_incoming_call" msgid="4140530013111794587">"उत्तर द्या"</string>
     <string name="decline_incoming_call" msgid="806026168661598368">"नकार द्या"</string>
     <string name="cant_call_due_to_ongoing_call" msgid="4952615196237854748">"आपल्या <xliff:g id="OTHER_CALL">%1$s</xliff:g> कॉलमुळे कॉल केला जाऊ शकत नाही."</string>
@@ -83,7 +84,7 @@
     <string name="notification_channel_incoming_call" msgid="3513761697082968084">"येणारे कॉल"</string>
     <string name="notification_channel_missed_call" msgid="8727062678632713146">"सुटलेले कॉल"</string>
     <string name="notification_channel_call_blocking" msgid="2943358779746676070">"कॉल ब्‍लॉक करणे"</string>
-    <string name="alert_outgoing_call" msgid="982908156825958001">"हा कॉल केल्याने तुमचा <xliff:g id="OTHER_APP">%1$s</xliff:g> कॉल समाप्त होईल."</string>
+    <string name="alert_outgoing_call" msgid="982908156825958001">"हा कॉल केल्याने आपला <xliff:g id="OTHER_APP">%1$s</xliff:g> कॉल समाप्त होईल."</string>
     <string name="phone_settings_call_blocking_txt" msgid="3976004073043846733">"कॉल ब्‍लॉक करणे"</string>
     <string name="phone_settings_number_not_in_contact_txt" msgid="3126829421867168652">"संपर्कांमध्‍ये क्रमांक नाहीत"</string>
     <string name="phone_settings_number_not_in_contact_summary_txt" msgid="9043147855140079119">"तुमच्‍या संपर्कांच्‍या सूचीमध्‍ये नसलेले क्रमांक ब्‍लॉक करा"</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"कॉल ब्‍लॉक करणे बंद केले"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"आणीबाणी कॉल केला"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"आणीबाणीत प्रतिसाद देणार्‍यांना तुमच्‍याशी संपर्क साधण्‍याची अनुमती देण्‍यासाठी कॉल ब्‍लॉक करणे बंद केले आहे."</string>
+    <string name="developer_title" msgid="1816273446906554627">"टेलिकॉम डेव्‍हलपर मेनू"</string>
 </resources>
diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml
index ce0e2d1..1bbbd3c 100644
--- a/res/values-ms/strings.xml
+++ b/res/values-ms/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Respons pantas"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Mesej dihantar ke <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Mesej gagal dihantar kepada <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"Akaun panggilan"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Panggilan kecemasan sahaja dibenarkan."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Aplikasi ini tidak boleh membuat panggilan keluar tanpa kebenaran Telefon."</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Sekatan Panggilan dilumpuhkan"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Panggilan kecemasan dibuat"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Sekatan Panggilan telah dilumpuhkan untuk membolehkan pasukan bantuan kecemasan menghubungi anda."</string>
+    <string name="developer_title" msgid="1816273446906554627">"Menu Pembangun Telekom"</string>
 </resources>
diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml
index 597924b..237fb71 100644
--- a/res/values-my/strings.xml
+++ b/res/values-my/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"အမြန်တုံ့ပြန်ချက်"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"<xliff:g id="PHONE_NUMBER">%s</xliff:g> ထံ စာတိုပို့လိုက်ပါပြီ"</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"<xliff:g id="PHONE_NUMBER">%s</xliff:g> ထံသို့ မက်ဆေ့ဂျ် ပို့၍ မရပါ။"</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"ခေါ်ဆိုသော အကောင့်များ"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"အရေးပေါ်ခေါ်ဆိုမှုများသာ ခွင့်ပြုပါသည်။"</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"ဤအပ္ပလီကေးရှင်းသည် ဖုန်းခွင့်ပြုချက်မရှိဘဲ အထွက်ခေါ်ဆိုမှု ပြုလုပ်၍မရပါ။"</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"\'ခေါ်ဆိုမှု ပိတ်ခြင်း\' ကို ရပ်ထားပါသည်"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"အရေးပေါ် ခေါ်ဆိုမှု ပြုလုပ်ထားပါသည်"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"အရေးပေါ်တုံ့ပြန်သူများက သင့်အား ဆက်သွယ်နိုင်စေရန် \'ခေါ်ဆိုမှု ပိတ်ခြင်း\' ကို ရပ်ထားပါသည်။"</string>
+    <string name="developer_title" msgid="1816273446906554627">"Telecom ဆော့ဖ်ဝဲအင်ဂျင်နီယာ မီနူး"</string>
 </resources>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index 6e63c1e..ce7936f 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Hurtigsvar"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Melding er sendt til <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Kunne ikke sende meldingen til <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"Ringekontoer"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Kun nødanrop er mulig."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Denne appen kan ikke ringe uten tillatelse fra telefonen."</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Anropsblokkering er slått av"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Nødanrop utført"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Anropsblokkering er slått av for å gjøre det mulig for nødtjenester å kontakte deg."</string>
+    <string name="developer_title" msgid="1816273446906554627">"Meny for telekommunikasjonsutviklere"</string>
 </resources>
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
index fe5220f..ad10907 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"द्रुत प्रतिक्रिया"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"<xliff:g id="PHONE_NUMBER">%s</xliff:g> लाई सन्देश पठाइयो।"</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"<xliff:g id="PHONE_NUMBER">%s</xliff:g> मा सन्देश पठाउन सकिएन।"</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"कलिङ खाताहरू"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"आपतकालीन कलहरूलाई मात्र अनुमति दिइएको छ।"</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"यो अनुप्रयोगले फोनको अनुमति बिना बहिर्गमन कलहरू गर्न सक्दैन।"</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"कलमाथि रोक लगाउने सुविधालाई असक्षम पारियो"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"आपतकालीन कल गरियो"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"आपतकालीन अवस्थामा उद्दार गर्ने मान्छेहरूलाई तपाईंलाई सम्पर्क गर्न दिन कलमाथि रोक लगाउने सुविधा असक्षम पारिएको छ।"</string>
+    <string name="developer_title" msgid="1816273446906554627">"टेलिकमको विकासकर्ताको मेनु"</string>
 </resources>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index eb4e873..df920e6 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Snelle reactie"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Bericht verzonden naar <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Kan bericht niet verzenden naar <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"Gespreksaccounts"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Alleen noodoproepen zijn toegestaan."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Deze app kan geen uitgaande gesprekken starten zonder telefoonrechten."</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Gesprekken blokkeren uitgeschakeld"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Noodoproep geplaatst"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Gesprekken blokkeren is uitgeschakeld zodat nooddiensten je kunnen bereiken."</string>
+    <string name="developer_title" msgid="1816273446906554627">"Telecomontwikkelaarsmenu"</string>
 </resources>
diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml
deleted file mode 100644
index c3c6cbe..0000000
--- a/res/values-or/strings.xml
+++ /dev/null
@@ -1,100 +0,0 @@
-<?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="382363169988504520">"କଲ୍ ପରିଚାଳନା"</string>
-    <string name="userCallActivityLabel" product="default" msgid="5415173590855187131">"ଫୋନ୍ କରନ୍ତୁ"</string>
-    <string name="unknown" msgid="6878797917991465859">"ଅଜଣା"</string>
-    <string name="notification_missedCallTitle" msgid="7554385905572364535">"ମିସଡ୍ କଲ୍‌"</string>
-    <string name="notification_missedWorkCallTitle" msgid="6242489980390803090">"କାର୍ଯ୍ୟସ୍ଥଳୀରୁ ଆସିଥିବା ମିସଡ୍ କଲ୍"</string>
-    <string name="notification_missedCallsTitle" msgid="1361677948941502522">"ମିସଡ୍ କଲ୍"</string>
-    <string name="notification_missedCallsMsg" msgid="4575787816055205600">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g>ଟି ମିସଡ୍ କଲ୍"</string>
-    <string name="notification_missedCallTicker" msgid="504686252427747209">"<xliff:g id="MISSED_CALL_FROM">%s</xliff:g>ଙ୍କ ଠାରୁ ମିସ୍-କଲ୍ ମିଳିଛି"</string>
-    <string name="notification_missedCall_call_back" msgid="2684890353590890187">"କଲବ୍ୟାକ୍ କରନ୍ତୁ"</string>
-    <string name="notification_missedCall_message" msgid="3049928912736917988">"ମେସେଜ୍‍ ଦିଅନ୍ତୁ"</string>
-    <string name="accessibility_call_muted" msgid="2776111226185342220">"କଲ୍ ମ୍ୟୁଟ୍ କରାଯାଇଛି।"</string>
-    <string name="accessibility_speakerphone_enabled" msgid="1988512040421036359">"ସ୍ପିକରଫୋନ୍‌କୁ ସକ୍ଷମ କରାଯାଇଛି ।"</string>
-    <string name="respond_via_sms_canned_response_1" msgid="2461606462788380215">"ବର୍ତ୍ତମାନ କଥା ହୋ‌ଇପାରିବ ନାହିଁ। କଥା କ’ଣ?"</string>
-    <string name="respond_via_sms_canned_response_2" msgid="4074450431532859214">"ମୁଁ ଟିକେ ପରେ ଆପଣଙ୍କୁ କଲ୍ କରିବି।"</string>
-    <string name="respond_via_sms_canned_response_3" msgid="3496079065723960450">"ମୁଁ ଆପଣଙ୍କୁ ପରେ କଲ୍ କରିବି।"</string>
-    <string name="respond_via_sms_canned_response_4" msgid="1698989243040062190">"ବର୍ତ୍ତମାନ କଥା ହୋ‌ଇପାରିବ ନାହିଁ। ମୋତେ ପରେ କଲ୍ କରିବେ?"</string>
-    <string name="respond_via_sms_setting_title" msgid="3754000371039709383">"ଶୀଘ୍ର ଉତ୍ତର"</string>
-    <string name="respond_via_sms_setting_title_2" msgid="6104662227299493906">"ଶୀଘ୍ର ଉତ୍ତରକୁ ଏଡିଟ୍ କରନ୍ତୁ"</string>
-    <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
-    <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"ଶୀଘ୍ର ଉତ୍ତର"</string>
-    <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"<xliff:g id="PHONE_NUMBER">%s</xliff:g>କୁ ମେସେଜ୍ ପଠାଗଲା।"</string>
-    <string name="enable_account_preference_title" msgid="2021848090086481720">"କଲ୍ କରିବା ଆକାଉଣ୍ଟ"</string>
-    <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"କେବଳ ଜରୁରିକାଳୀନ କଲ୍‌କୁ ଅନୁମତି ଦିଆଯାଇଛି।"</string>
-    <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"ଫୋନ୍‌ର ବିନାଅନୁମତିରେ ଏହି ଆପ୍ଲିକେଶନ୍ ଆଉଟ୍‌ଗୋ‌ଇଙ୍ଗ କଲ୍ କରିପାରିବ ନାହିଁ।"</string>
-    <string name="outgoing_call_error_no_phone_number_supplied" msgid="1940125199802007505">"ଗୋଟିଏ କଲ୍ କରିବା ପାଇଁ ଏକ ବୈଧ ନମ୍ବର୍ ପ୍ରବେଶ କରନ୍ତୁ।"</string>
-    <string name="duplicate_video_call_not_allowed" msgid="3749211605014548386">"ଏହି ସମୟରେ କଲ୍ ଯୋଡ଼ାଯାଇପାରିବ ନାହିଁ।"</string>
-    <string name="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="9101655962941740507">"<xliff:g id="NEW_APP">%s</xliff:g>କୁ ଆପଣଙ୍କ ଫୋନ୍‌ର ଡିଫଲ୍ଟ ଆପ୍ କରିବେ?"</string>
-    <string name="change_default_dialer_dialog_affirmative" msgid="8606546663509166276">"ଡିଫଲ୍ଟ ସେଟ୍ କରନ୍ତୁ"</string>
-    <string name="change_default_dialer_dialog_negative" msgid="9078144617060173845">"କ୍ୟାନ୍ସଲ୍‍ କରନ୍ତୁ"</string>
-    <string name="change_default_dialer_warning_message" msgid="1417671460801684999">"<xliff:g id="NEW_APP">%s</xliff:g> କଲ୍ କରିବା ଏବଂ କଲ୍‌ର ସମସ୍ତ ଦିଗକୁ ନିୟନ୍ତ୍ରଣ କରିବାରେ ସକ୍ଷମ ହେବ। କେବଳ ନିଜର ଭରସାଯୋଗ୍ୟ ଆପ୍‌କୁ ଡିଫଲ୍ଟ ଫୋନ୍ ଆପ୍ ଭାବେ ସେଟ୍ କରିବା ଉଚିତ୍।"</string>
-    <string name="blocked_numbers" msgid="2751843139572970579">"ଅବରୋଧ କରାଯାଇଥିବା ନମ୍ବର୍"</string>
-    <string name="blocked_numbers_msg" msgid="1045015186124965643">"ଅବରୋଧ କରାଯାଇଥିବା ନମ୍ବର୍‌ରୁ ଆପଣ କଲ୍ କିମ୍ବା ଟେକ୍ସଟ୍ ଗ୍ରହଣ କରିପାରିବେ ନାହିଁ।"</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="6847675097085433553">"ଫୋନ୍ ନମ୍ଵର୍"</string>
-    <string name="block_button" msgid="8822290682524373357">"ଅବରୋଧ କରନ୍ତୁ"</string>
-    <string name="non_primary_user" msgid="5180129233352533459">"କେବଳ ଡିଭାଇସ୍‌ର ମାଲିକ ଅବରୋଧ କରାଯାଇଥିବା ନମ୍ବର୍‌କୁ ଦେଖିପାରିବେ ଓ ପରିଚାଳନା କରିପାରିବେ।"</string>
-    <string name="delete_icon_description" msgid="8903995728252556724">"ଅବରୋଧ ହଟାନ୍ତୁ"</string>
-    <string name="blocked_numbers_butter_bar_title" msgid="438170866438793182">"ଅସ୍ଥାୟୀରୂପେ ଅବରୋଧ ଅଫ୍ ଅଛି"</string>
-    <string name="blocked_numbers_butter_bar_body" msgid="2223244484319442431">"ଆପଣ ଗୋଟିଏ ଜରୁରିକାଳୀନ ନମ୍ବର୍‌କୁ ଡାଏଲ୍ କିମ୍ବା ଟେକ୍ସଟ୍ କରିବା ପରେ, ଜରୁରିକାଳୀନ ସେବା ଆପଣଙ୍କୁ ଯୋଗାଯୋଗ କରିବାକୁ ସୁନିଶ୍ଚିତ କରିବା ପାଇଁ ଅବରୋଧକୁ ବନ୍ଦ କରିଦିଆଯାଇଥାଏ।"</string>
-    <string name="blocked_numbers_butter_bar_button" msgid="2197943354922010696">"ବର୍ତ୍ତମାନ ପୁନଃସକ୍ଷମ କରନ୍ତୁ"</string>
-    <string name="blocked_numbers_number_blocked_message" msgid="7678509606805029540">"<xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g> ଅବରୋଧ କରାଯାଇଛି"</string>
-    <string name="blocked_numbers_number_unblocked_message" msgid="977894647366750418">"<xliff:g id="UNBLOCKED_NUMBER">%1$s</xliff:g> ଅବରୋଧ ହଟାଇଦିଆଯାଇଛି"</string>
-    <string name="blocked_numbers_block_emergency_number_message" msgid="917851876780698387">"ଜରୁରିକାଳୀନ ନମ୍ବର୍‌କୁ ଅବରୋଧ କରିବାରେ ଅକ୍ଷମ।"</string>
-    <string name="blocked_numbers_number_already_blocked_message" msgid="4392247814500811798">"<xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g>କୁ ଅବରୋଧ କରାଯାଇସରିଛି।"</string>
-    <string name="toast_personal_call_msg" msgid="5115361633476779723">"କଲ୍ କରିବା ପାଇଁ ବ୍ୟକ୍ତିଗତ ଡାଏଲର୍‌କୁ ବ୍ୟବହାର କରନ୍ତୁ"</string>
-    <string name="notification_incoming_call" msgid="7713197997773986670">"<xliff:g id="CALL_FROM">%2$s</xliff:g> ଠାରୁ <xliff:g id="CALL_VIA">%1$s</xliff:g>କୁ କଲ୍ କରନ୍ତୁ"</string>
-    <string name="notification_incoming_video_call" msgid="6638486071698373893">"<xliff:g id="CALL_FROM">%2$s</xliff:g> ଠାରୁ <xliff:g id="CALL_VIA">%1$s</xliff:g> ଭିଡିଓ କଲ୍ କରନ୍ତୁ"</string>
-    <string name="answering_ends_other_call" msgid="8282145910153766401">"ଉତ୍ତର ଦେବାଦ୍ଵାରା ଆପଣଙ୍କର <xliff:g id="CALL_VIA">%1$s</xliff:g> କଲ୍ ସମାପ୍ତ ହୋ‌ଇଯିବ"</string>
-    <string name="answering_ends_other_calls" msgid="1198589551399049197">"ଉତ୍ତର ଦେବାଦ୍ଵାରା ଆପଣଙ୍କର <xliff:g id="CALL_VIA">%1$s</xliff:g> କଲ୍ ସମାପ୍ତ ହୋ‌ଇଯିବ"</string>
-    <string name="answering_ends_other_video_call" msgid="8510410917384186360">"ଉତ୍ତର ଦେବାଦ୍ଵାରା ଆପଣଙ୍କର <xliff:g id="CALL_VIA">%1$s</xliff:g> ଭିଡିଓ କଲ୍ ସମାପ୍ତ ହୋ‌ଇଯିବ"</string>
-    <string name="answering_ends_other_managed_call" msgid="5186137550267947785">"ଉତ୍ତର ଦେବାଦ୍ଵାରା ଆପଣଙ୍କର ଜାରି ରହିଥିବା କଲ୍ ସମାପ୍ତ ହୋ‌ଇଯିବ"</string>
-    <string name="answering_ends_other_managed_calls" msgid="6429838309560397988">"ଉତ୍ତର ଦେବାଦ୍ଵାରା ଆପଣଙ୍କର ଜାରି ରହିଥିବା କଲ୍ ସମାପ୍ତ ହୋ‌ଇଯିବ"</string>
-    <string name="answering_ends_other_managed_video_call" msgid="1585423762458248435">"ଉତ୍ତର ଦେବାଦ୍ଵାରା ଆପଣଙ୍କର ଜାରି ରହିଥିବା ଭିଡିଓ କଲ୍ ସମାପ୍ତ ହୋ‌ଇଯିବ"</string>
-    <string name="answer_incoming_call" msgid="4140530013111794587">"ଉତ୍ତର ଦିଅନ୍ତୁ"</string>
-    <string name="decline_incoming_call" msgid="806026168661598368">"ଅସ୍ୱୀକାର"</string>
-    <string name="cant_call_due_to_ongoing_call" msgid="4952615196237854748">"ଆପଣଙ୍କର <xliff:g id="OTHER_CALL">%1$s</xliff:g> କଲ୍ ହେତୁ କଲ୍ କରାଯାଇପାରିବ ନାହିଁ।"</string>
-    <string name="cant_call_due_to_ongoing_calls" msgid="1380804892363503856">"ଆପଣଙ୍କର <xliff:g id="OTHER_CALL">%1$s</xliff:g> କଲ୍ ହେତୁ କଲ୍ କରାଯାଇପାରିବ ନାହିଁ।"</string>
-    <string name="cant_call_due_to_ongoing_unknown_call" msgid="149091978697302211">"ଅନ୍ୟ ଆପ୍‌ରେ କରାଯାଇଥିବା କଲ୍ ହେତୁ କଲ୍ କରାଯାଇପାରିବ ନାହିଁ।"</string>
-    <string name="notification_channel_incoming_call" msgid="3513761697082968084">"ଇନ୍‌କମିଙ୍ଗ କଲ୍"</string>
-    <string name="notification_channel_missed_call" msgid="8727062678632713146">"ମିସଡ୍ କଲ୍"</string>
-    <string name="notification_channel_call_blocking" msgid="2943358779746676070">"କଲ୍‌କୁ ଅବରୋଧ କରନ୍ତୁ"</string>
-    <string name="alert_outgoing_call" msgid="982908156825958001">"ଏହି କଲ୍‌କୁ ସ୍ଥାପନ କରିବା ଦ୍ଵାରା ଆପଣଙ୍କର <xliff:g id="OTHER_APP">%1$s</xliff:g> କଲ୍ ସମାପ୍ତ ହୋ‌ଇଯିବ।"</string>
-    <string name="phone_settings_call_blocking_txt" msgid="3976004073043846733">"କଲ୍‌କୁ ଅବରୋଧ କରନ୍ତୁ"</string>
-    <string name="phone_settings_number_not_in_contact_txt" msgid="3126829421867168652">"ଯୋଗାଯୋଗରେ ନଥିବା ନମ୍ବର୍"</string>
-    <string name="phone_settings_number_not_in_contact_summary_txt" msgid="9043147855140079119">"ଆପଣଙ୍କ ଯୋଗାଯୋଗରେ ତାଲିକାଭୁକ୍ତ ହୋ‌ଇନଥିବା ନମ୍ବର୍‌କୁ ଅବରୋଧ କରନ୍ତୁ"</string>
-    <string name="phone_settings_private_num_txt" msgid="8623574188879134262">"ଗୋପନୀୟ"</string>
-    <string name="phone_settings_private_num_summary_txt" msgid="7516314821207782191">"ନିଜର ନମ୍ବର୍‌କୁ ପ୍ରକାଶ କରୁନଥିବା କଲ୍‌କର୍ତ୍ତାଙ୍କୁ ଅବରୋଧ କରନ୍ତୁ"</string>
-    <string name="phone_settings_payphone_txt" msgid="2493356957416981318">"ପେ-ଫୋନ୍"</string>
-    <string name="phone_settings_payphone_summary_txt" msgid="6126709946103814653">"ପେ-ଫୋନ୍‌ରୁ କଲ୍‌କୁ ଅବରୋଧ କରନ୍ତୁ"</string>
-    <string name="phone_settings_unknown_txt" msgid="5836407031508172721">"ଅଜଣା"</string>
-    <string name="phone_settings_unknown_summary_txt" msgid="3457690230497753233">"ଅଚିହ୍ନା କଲକର୍ତ୍ତାଙ୍କର କଲ୍‌କୁ ଅବରୋଧ କରନ୍ତୁ"</string>
-    <string name="phone_strings_call_blocking_turned_off_notification_title_txt" msgid="628536625775266096">"କଲ୍‌କୁ ଅବରୋଧ କରନ୍ତୁ"</string>
-    <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"କଲ୍ ଅବରୋଧ ସୁବିଧାକୁ ଅକ୍ଷମ କରାଯାଇଛି"</string>
-    <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"ଜରୁରିକାଳୀନ କଲ୍ କରାଗଲା"</string>
-    <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"ଜରୁରିକାଳୀନ ସହାୟତା କର୍ମଚାରୀମାନେ ଆପଣଙ୍କୁ ଯୋଗଯୋଗ କରିବା ପାଇଁ କଲ୍ ଅବରୋଧକୁ ଅକ୍ଷମ କରାଯାଇଛି।"</string>
-</resources>
diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml
index 47e279b..12e0591 100644
--- a/res/values-pa/strings.xml
+++ b/res/values-pa/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"ਤਤਕਾਲ ਜਵਾਬ"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"ਸੁਨੇਹਾ <xliff:g id="PHONE_NUMBER">%s</xliff:g> ਨੂੰ ਭੇਜਿਆ ਗਿਆ।"</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"<xliff:g id="PHONE_NUMBER">%s</xliff:g> \'ਤੇ ਸੁਨੇਹਾ ਭੇਜਣਾ ਅਸਫਲ ਰਿਹਾ।"</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"ਕਾਲਿੰਗ ਖਾਤੇ"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"ਸਿਰਫ਼ ਸੰਕਟ ਕਾਲਾਂ ਨੂੰ ਮਨਜ਼ੂਰੀ ਹੈ।"</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"ਇਹ ਐਪਲੀਕੇਸ਼ਨ ਫੋਨ ਅਨੁਮਤੀ ਦੇ ਬਿਨਾਂ ਆਉਟਗੋਇੰਗ ਕਾਲਾਂ ਨਹੀਂ ਕਰ ਸਕਦੀ।"</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"ਕਾਲ ਬਲਾਕਿੰਗ ਵਿਕਲਪ ਬੰਦ ਕੀਤਾ ਗਿਆ"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"ਸੰਕਟਕਾਲੀਨ ਕਾਲ ਕੀਤੀ ਗਈ"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"ਸੰਕਟਕਾਲੀਨ ਸਥਿਤੀ ਵਿੱਚ ਮਦਦ ਕਰਨ ਵਾਲੇ ਵਿਅਕਤੀ ਨੂੰ ਤੁਹਾਨੂੰ ਸੰਪਰਕ ਕਰਨ ਦੇਣ ਲਈ ਕਾਲ ਬਲਾਕਿੰਗ ਵਿਕਲਪ ਬੰਦ ਕਰ ਦਿੱਤਾ ਗਿਆ ਹੈ।"</string>
+    <string name="developer_title" msgid="1816273446906554627">"ਟੈਲੀਕੋਮ ਵਿਕਾਸਕਾਰ ਮੀਨੂ"</string>
 </resources>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index b48bdb3..6119726 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Szybka odpowiedź"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Wiadomość wysłano na numer <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Nie udało się wysłać wiadomości do: <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"Konta telefoniczne"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Dozwolone są tylko połączenia alarmowe."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Ta aplikacja nie może wykonywać połączeń bez uprawnienia Telefon."</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Blokowanie połączeń wyłączone"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Wykonano połączenie alarmowe"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Blokowanie połączeń zostało wyłączone, aby służby ratownicze mogły się z Tobą skontaktować."</string>
+    <string name="developer_title" msgid="1816273446906554627">"Menu programisty Telecom"</string>
 </resources>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index 8dc9405..87cbf5c 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Resposta rápida"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Mensagem enviada para <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Falha ao enviar a mensagem para <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"Contas de chamadas"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Apenas são permitidas chamadas de emergência."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Esta aplicação não pode fazer chamadas sem a autorização do telefone."</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Bloqueio de chamadas desativado"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Chamada de emergência efetuada"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"O bloqueio de chamadas foi desativado para permitir a receção de contactos de resposta a emergências."</string>
+    <string name="developer_title" msgid="1816273446906554627">"Menu do programador de telecomunicações"</string>
 </resources>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index 25d2345..4c0372f 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Resposta rápida"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Mensagem enviada para <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Falha ao enviar a mensagem para <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"Contas de chamadas"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Somente chamadas de emergência são permitidas."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Este aplicativo não pode fazer chamadas sem a permissão do smartphone."</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Bloqueio de chamadas desativado"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"A chamada de emergência foi feita"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"O bloqueio de chamadas foi desativado para permitir que a equipe de emergência entre em contato com você."</string>
+    <string name="developer_title" msgid="1816273446906554627">"Menu do desenvolvedor de telecomunicação"</string>
 </resources>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index ae17707..ae96c51 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Răspuns rapid"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Mesajul a fost trimis la <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Mesajul nu a fost trimis la <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"Conturi pentru apelare"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Sunt permise doar apelurile de urgență."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Această aplicație nu poate efectua apeluri fără permisiunea Telefon."</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Blocarea apelurilor este dezactivată."</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"S-a efectuat un apel de urgență."</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Blocarea apelurilor a fost dezactivată pentru a permite serviciilor de urgență să vă contacteze."</string>
+    <string name="developer_title" msgid="1816273446906554627">"Meniu pentru dezvoltatori de telecomunicații"</string>
 </resources>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index 8b56d7b..d1ac989 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Быстрый ответ"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Сообщение отправлено на номер <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Не удалось отправить сообщение на номер <xliff:g id="PHONE_NUMBER">%s</xliff:g>"</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"Аккаунты для звонков"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Только экстренные вызовы"</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Приложение не может совершать звонки без соответствующего разрешения."</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Блокировка вызовов отключена"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Выполнен экстренный вызов"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Блокировка вызовов отключена, чтобы у экстренных служб была возможность позвонить вам."</string>
+    <string name="developer_title" msgid="1816273446906554627">"Меню разработчика Telecom"</string>
 </resources>
diff --git a/res/values-si/strings.xml b/res/values-si/strings.xml
index 538f96d..784cc66 100644
--- a/res/values-si/strings.xml
+++ b/res/values-si/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"ක්ෂණික ප්‍රතිචාරය"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"<xliff:g id="PHONE_NUMBER">%s</xliff:g> හට පණිවිඩය යවන්න."</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"පණිවිඩය <xliff:g id="PHONE_NUMBER">%s</xliff:g> වෙත යැවීමට අසමත් විය."</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"ඇමතීමේ ගිණුම්"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"හදිසි ඇමතුම්වලට පමණක් ඉඩ දේ."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"දුරකථන අවසරයෙන් තොරව මෙම යෙදුමට පිටතට යන ඇමතුම් සිදු කළ නොහැකිය."</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"ඇමතුම් අවහිර කිරීම අබල කර ඇත"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"හදිසි ඇමතුම් ගැනීම"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"හදිසි අමතන්නන්ට ඔබව සම්බන්ධ කර ගැනීමට ඉඩ දීමට ඇමතුම් අවහිර කිරීම අබල කර ඇත."</string>
+    <string name="developer_title" msgid="1816273446906554627">"ටෙලිකොම් සංවර්ධක මෙනුව"</string>
 </resources>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index eb7fc51..217a46e 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Rýchla odpoveď"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Správa bola odoslaná na číslo <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Správu sa nepodarilo odoslať na číslo <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"Telefónne účty"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Povolené sú len tiesňové volania."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"V tejto aplikácii nie je možné uskutočňovať odchádzajúce hovory bez povolenia Telefón"</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Blokovanie hovorov je vypnuté"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Bolo uskutočnené tiesňové volanie"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Blokovanie hovorov bolo vypnuté, aby vás mohli kontaktovať pracovníci tiesňových služieb."</string>
+    <string name="developer_title" msgid="1816273446906554627">"Ponuka pre vývojárov Telecomu"</string>
 </resources>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index 083a7c5..46fb2d6 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Hiter odgovor"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"SMS poslan na številko <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Sporočilo ni bilo poslano na številko <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"Računi za klicanje"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Dovoljeno je samo opravljanje klicev v sili."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Ta aplikacija lahko opravlja odhodne klice brez dovoljenja za telefon."</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Blokiranje klicev je onemogočeno"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Opravljen je klic v sili"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Blokiranje klicev je onemogočeno, da lahko stik z vami vzpostavijo uslužbenci služb za klic v sili."</string>
+    <string name="developer_title" msgid="1816273446906554627">"Meni za razvijalce Telecom"</string>
 </resources>
diff --git a/res/values-sq/strings.xml b/res/values-sq/strings.xml
index 325f62b..8a2c88c 100644
--- a/res/values-sq/strings.xml
+++ b/res/values-sq/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Përgjigje e shpejtë"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Mesazhi u dërgua te <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Dërgimi i mesazhit te <xliff:g id="PHONE_NUMBER">%s</xliff:g> dështoi."</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"Llogaritë e telefonatave"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Lejohen vetëm telefonatat e urgjencës."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Ky aplikacion nuk mund të kryejë telefonata dalëse pa lejen e Telefonit."</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Bllokimi i telefonatave u çaktivizua"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Telefonata e urgjencës u krye"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Bllokimi i telefonatave është çaktivizuar për të lejuar që personat që përgjigjen në rast urgjence të kontaktojnë me ty."</string>
+    <string name="developer_title" msgid="1816273446906554627">"Menyja e zhvilluesit të telekomunikimit"</string>
 </resources>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index 59f5969..dddb959 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Брзи одговор"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Порука је послата на број <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Слање поруке на <xliff:g id="PHONE_NUMBER">%s</xliff:g> није успело."</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"Налози за позивање"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Дозвољени су само хитни позиви."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Ова апликација не може да позива без дозволе за телефонирање."</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Блокирање позива је онемогућено"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Упућен је хитни позив"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Блокирање позива је онемогућено да би хитне службе могле да вас контактирају."</string>
+    <string name="developer_title" msgid="1816273446906554627">"Мени за програмере Telecom-а"</string>
 </resources>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index 78a3963..525069c 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Snabbsvar"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Meddelandet har skickats till <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Meddelande till <xliff:g id="PHONE_NUMBER">%s</xliff:g> inte skickat."</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"Konton för samtal"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Det går bara att ringa nödsamtal."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Den här appen kan inte göra utgående samtal utan behörigheten Telefon."</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Samtalsblockering inaktiverad"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Nödsamtal ringt"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Samtalsblockering har inaktiverats för att tillåta att räddningstjänsten kontaktar dig."</string>
+    <string name="developer_title" msgid="1816273446906554627">"Meny för telekomutvecklare"</string>
 </resources>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index 86b83a5..810a977 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Majibu ya haraka"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Ujumbe uliotumwa kwa <xliff:g id="PHONE_NUMBER">%s</xliff:g> ."</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Imeshindwa kutuma SMS kwa <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"Akaunti za simu"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Piga simu za dharura pekee."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Programu hii haiwezi kupiga simu bila ruhusa ya Simu."</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Kipengele cha Kuzuia Simu kimezimwa"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Simu ya dharura imepigwa"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Kipengele cha Kuzuia Simu kimezimwa ili kuruhusu wapigaji simu za dharura kuwasiliana nawe."</string>
+    <string name="developer_title" msgid="1816273446906554627">"Menyu ya Msanidi programu wa Telecom"</string>
 </resources>
diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml
index 8c2d3a1..afc6cde 100644
--- a/res/values-ta/strings.xml
+++ b/res/values-ta/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"விரைவு பதில்"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"<xliff:g id="PHONE_NUMBER">%s</xliff:g> க்குச் செய்தி அனுப்பப்பட்டது."</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"செய்தியை <xliff:g id="PHONE_NUMBER">%s</xliff:g>க்கு அனுப்ப முடியவில்லை."</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"அழைப்புக் கணக்குகள்"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"அவசர அழைப்புகள் மட்டுமே அனுமதிக்கப்படும்."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"ஃபோன் அனுமதியில்லாமல், பயன்பாட்டினால் வெளிச்செல்லும் அழைப்புகளைச் செய்ய முடியாது."</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"அழைப்புத் தடுப்பு முடக்கப்பட்டுள்ளது"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"அவசர அழைப்பு செய்யப்பட்டது"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"அவசரநிலையில் பதிலளிப்பவர்களை உங்களைத் தொடர்புகொள்வதற்கு அனுமதிக்க, அழைப்புத் தடுப்பு முடக்கப்பட்டுள்ளது."</string>
+    <string name="developer_title" msgid="1816273446906554627">"டெலிகாம் டெவெலப்பர் மெனு"</string>
 </resources>
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
index d6cd091..600c64e 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"శీఘ్ర ప్రతిస్పందన"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"<xliff:g id="PHONE_NUMBER">%s</xliff:g>కు సందేశం పంపబడింది."</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"<xliff:g id="PHONE_NUMBER">%s</xliff:g>కు సందేశాన్ని పంపడం విఫలమైంది."</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"కాలింగ్ ఖాతాలు"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"కేవలం అత్యవసర కాల్‌లు మాత్రమే అనుమతించబడతాయి."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"ఈ అనువర్తనం ఫోన్ అనుమతి లేకుండా అవుట్‌గోయింగ్ కాల్‌లను చేయలేదు."</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"కాల్ బ్లాక్ చేయడం నిలిపివేయబడింది"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"అత్యవసర కాల్ చేయబడింది"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"మిమ్మల్ని సంప్రదించడానికి అత్యవసర ప్రతిస్పందనదారులను అనుమతించడానికి కాల్ బ్లాక్ చేయడం నిలిపివేయబడింది."</string>
+    <string name="developer_title" msgid="1816273446906554627">"టెలికామ్ డెవలపర్ మెను"</string>
 </resources>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index 913d1da..39d0a8e 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"คำตอบด่วน"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"ส่งข้อความไปยัง <xliff:g id="PHONE_NUMBER">%s</xliff:g> แล้ว"</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"ส่งข้อความไปยัง <xliff:g id="PHONE_NUMBER">%s</xliff:g> ไม่สำเร็จ"</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"บัญชีการโทร"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"อนุญาตเฉพาะหมายเลขฉุกเฉิน"</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"แอปพลิเคชันนี้ไม่สามารถโทรออกโดยไม่มีสิทธิ์ใช้โทรศัพท์"</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"ปิดใช้การบล็อกสาย"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"โทรหมายเลขฉุกเฉินแล้ว"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"ปิดใช้การบล็อกสายแล้วเพื่อให้ทีมฉุกเฉินติดต่อคุณ"</string>
+    <string name="developer_title" msgid="1816273446906554627">"เมนูนักพัฒนาโทรคมนาคม"</string>
 </resources>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index d4182da..5992be2 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Mabilisang tugon"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Naipadala ang mensahe sa <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Hindi naipadala ang mensahe sa <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"Account sa pagtawag"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Ang mga pang-emergency na tawag lang ang pinapayagan."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Hindi makakapagsagawa ng mga papalabas na tawag ang application na ito nang wala ang pahintulot ng Telepono."</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Naka-disable ang Pag-block ng Tawag"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Ginawang emergency na tawag"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Na-disable ang Pag-block ng Tawag para payagan ang mga tumutugon sa emergency na kontakin ka."</string>
+    <string name="developer_title" msgid="1816273446906554627">"Menu ng Telecom Developer"</string>
 </resources>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index b5a05f3..95ec0a6 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Hızlı yanıt"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Mesaj, <xliff:g id="PHONE_NUMBER">%s</xliff:g> numaralı telefona gönderildi."</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"<xliff:g id="PHONE_NUMBER">%s</xliff:g> numaralı telefona mesaj gönderilemedi."</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"Çağrı hesapları"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Yalnızca acil durum çağrılarına izin veriliyor."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Bu uygulama, Telefonun izni olmadan giden çağrılar yapamaz."</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Çağrı Engelleme devre dışı bırakıldı"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Acil durum çağrısı yapıldı"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Acil durum müdahale ekibinin sizinle iletişime geçmesine olanak tanımak için Çağrı Engelleme devre dışı bırakıldı."</string>
+    <string name="developer_title" msgid="1816273446906554627">"Telekomünikasyon Geliştirici Menüsü"</string>
 </resources>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index f17be5c..03ce788 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Швидка відповідь"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Повідомлення надіслано на номер <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Повідомлення не надіслано на номер <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"Облікові записи для дзвінків"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Дозволено лише екстрені виклики."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Цей додаток не може телефонувати без відповідного дозволу."</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Блокування викликів вимкнено"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Здійснено екстрений виклик"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Блокування викликів вимкнено, щоб ви могли отримувати екстрені сповіщення."</string>
+    <string name="developer_title" msgid="1816273446906554627">"Меню розробника Telecom"</string>
 </resources>
diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml
index 186bdf1..b25e6d8 100644
--- a/res/values-ur/strings.xml
+++ b/res/values-ur/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"فوری جواب"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"پیغام <xliff:g id="PHONE_NUMBER">%s</xliff:g> کو بھیج دیا گیا۔"</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"<xliff:g id="PHONE_NUMBER">%s</xliff:g> پر پیغام نہیں بھیجا جا سکا۔"</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"کالنگ اکاؤنٹس"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"صرف ہنگامی کالز کی اجازت ہے۔"</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"یہ ایپلی کیشن فون کی اجازت کے بغیر باہر جانے والی کالیں نہیں کر سکتی۔"</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"کال مسدود کرنا غیر فعال ہو گیا ہے"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"ہنگامی کال کی گئی"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"ہنگامی حالت میں جواب دہندگان کو آپ سے رابطہ کرنے کی اجازت دینے کیلئے کال مسدود کرنا غیر فعال ہو گیا ہے۔"</string>
+    <string name="developer_title" msgid="1816273446906554627">"ٹیلی کام ڈیولپر مینو"</string>
 </resources>
diff --git a/res/values-uz/strings.xml b/res/values-uz/strings.xml
index 11cc6fa..db3e791 100644
--- a/res/values-uz/strings.xml
+++ b/res/values-uz/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Tezkor javob"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Xabar <xliff:g id="PHONE_NUMBER">%s</xliff:g>ga jo‘natildi."</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Xabar <xliff:g id="PHONE_NUMBER">%s</xliff:g> raqamiga yuborilmadi."</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"Chaqiruv uchun hisoblar"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Faqat favqulodda qo‘ng‘iroqlarga ruxsat berilgan."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Bu ilova Telefon ruxsatnomasisiz chiquvchi qo‘ng‘iroqlarni amalga oshira olmaydi."</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Chaqiruvlarni bloklash funksiyasi yoqilmagan"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Favqulodda chaqiruv qilindi"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Sizga favqulodda chiqiruv qilish imkoni bo‘lishi uchun chaqiruvlarni bloklash funksiyasi o‘chirib qo‘yilgan."</string>
+    <string name="developer_title" msgid="1816273446906554627">"Telecom dasturchisi menyusi"</string>
 </resources>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index b0f6560..8be0ca6 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Trả lời nhanh"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Đã gửi tin nhắn tới <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Không gửi được tin nhắn đến <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"Tài khoản gọi"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Chỉ được phép thực hiện cuộc gọi khẩn cấp."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Ứng dụng này không thể thực hiện cuộc gọi đi mà không có quyền của Điện thoại."</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Đã tắt tính năng Chặn cuộc gọi"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Đã thực hiện cuộc gọi khẩn cấp"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Đã tắt tính năng Chặn cuộc gọi để cho phép người trả lời khẩn cấp liên hệ với bạn."</string>
+    <string name="developer_title" msgid="1816273446906554627">"Menu nhà phát triển dịch vụ viễn thông"</string>
 </resources>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index 370b718..5514b3d 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"快速回复"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"讯息已发送至 <xliff:g id="PHONE_NUMBER">%s</xliff:g>。"</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"未能将信息发送到 <xliff:g id="PHONE_NUMBER">%s</xliff:g>。"</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"通话帐号"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"只能拨打紧急呼救电话。"</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"此应用没有电话权限,无法拨出电话。"</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"来电屏蔽功能已停用"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"已拨打紧急呼救电话"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"系统已停用来电屏蔽功能,以便急救人员与您联系。"</string>
+    <string name="developer_title" msgid="1816273446906554627">"电信开发者菜单"</string>
 </resources>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index 1d2dd46..4e48b81 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"快速回應"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"訊息已傳送至 <xliff:g id="PHONE_NUMBER">%s</xliff:g>。"</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"無法將訊息傳送至 <xliff:g id="PHONE_NUMBER">%s</xliff:g>。"</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"通話帳戶"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"只限緊急電話。"</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"此應用程式沒有電話權限,無法撥出電話。"</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"已停用來電封鎖功能"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"已撥緊急電話"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"已停用來電封鎖功能,以便救援人員與您聯絡。"</string>
+    <string name="developer_title" msgid="1816273446906554627">"電信開發商選單"</string>
 </resources>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index da34cd2..3fd47de 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"快速回應"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"訊息已傳送至 <xliff:g id="PHONE_NUMBER">%s</xliff:g>。"</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"無法將訊息傳送到 <xliff:g id="PHONE_NUMBER">%s</xliff:g>。"</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"通話帳戶"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"只能撥打緊急電話。"</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"這個應用程式不具備電話應用程式存取權限,無法撥出電話。"</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"已停用來電封鎖"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"已撥打緊急電話"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"系統已停用來電封鎖功能,以便緊急應變人員與你聯絡。"</string>
+    <string name="developer_title" msgid="1816273446906554627">"電信開發人員選單"</string>
 </resources>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index 79e5846..0b34857 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -37,6 +37,7 @@
     <string name="respond_via_sms_setting_summary" msgid="9150281183930613065"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="20379890418289778">"Izimpendulo ezisheshayo"</string>
     <string name="respond_via_sms_confirmation_format" msgid="7229149977515784269">"Umlayezo othunyelwe ku <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
+    <string name="respond_via_sms_failure_format" msgid="90791421289769504">"Umlayezo uhlulekile ukuthunyelwa ku-<xliff:g id="PHONE_NUMBER">%s</xliff:g>"</string>
     <string name="enable_account_preference_title" msgid="2021848090086481720">"Ama-akhawunti wokushaya"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="6872406278300131364">"Amakholi aphuthumayo kuphela avunyelwe."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="1996571596464271228">"Lolu hlelo lokusebenza alikwazi ukwenza amakhli aphumayo ngaphandle kwemvume yefoni."</string>
@@ -97,4 +98,5 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="6264230048947693941">"Ukuvimbela ikholi kukhutshaziwe"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="7421611725400166580">"Ikholi ephuthumayo yenziwe"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="4083285098613193052">"Ukuvimbela ikholi kukhutshaziwe ukuze kuvunyelwe abaphenduli besimo esiphuthumayo ukuthi baxhumane nawe."</string>
+    <string name="developer_title" msgid="1816273446906554627">"Imenyu yonjiniyela we-Telecom"</string>
 </resources>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 8ff0b8e..0c7c175 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -15,17 +15,16 @@
   -->
 
 <resources>
+    <!--follows the dialer theme color-->
     <color name="theme_color">#2a56c6</color>
+    <color name="background_color">#fafafa</color>
 
-    <color name="dialer_settings_actionbar_text_color">#ffffff</color>
-    <color name="dialer_settings_actionbar_background_color">@color/theme_color</color>
-    <color name="dialer_settings_color_dark">#1c3aa9</color>
+    <!--Use the one consistence with dialer-->
+    <color name="blocked_numbers_divider_color">#d8d8d8</color>
+    <color name="blocked_numbers_primary_text_color">#202124</color>
+    <color name="blocked_numbers_secondary_text_color">#5f6368</color>
 
-    <color name="blocked_numbers_divider_color">#e5e5e5</color>
-    <color name="blocked_numbers_butter_bar_color">#f5f5f5</color>
-    <color name="blocked_numbers_title_text_color">#ba000000</color>
-    <color name="blocked_numbers_secondary_text_color">#89000000</color>
-
+    <!--follows dialer color-->
     <color name="notification_action_answer">#097138</color>
     <color name="notification_action_decline">#A52714</color>
 </resources>
diff --git a/res/values/config.xml b/res/values/config.xml
index 8c84688..c0e9669 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -60,4 +60,13 @@
          between repeats of the ringtone.
          When false, the ringtone will be looping with no pause. -->
     <bool name="should_pause_between_ringtone_repeats">true</bool>
+
+    <!-- Threshold for the X+Y component of gravity needed for the device orientation to be
+         classified as being on a user's ear. -->
+    <item name="device_on_ear_xy_gravity_threshold" format="float" type="dimen">5.5</item>
+
+    <!-- Lower threshold for the Y-component of gravity needed for the device orientation to be
+         classified as being on a user's ear. If the Y-component is less than this negative value,
+         the device is probably upside-down and therefore not on a ear -->
+    <item name="device_on_ear_y_gravity_negative_threshold" format="float" type="dimen">-1</item>
 </resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 7b085ad..9986cb9 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -27,7 +27,8 @@
     <dimen name="blocked_numbers_dialog_padding">24dp</dimen>
     <dimen name="blocked_numbers_delete_icon_padding">12dp</dimen>
     <dimen name="blocked_numbers_progress_bar_padding">100dp</dimen>
-    <dimen name="blocked_numbers_font_size">16sp</dimen>
+    <dimen name="blocked_numbers_head1_font_size">18sp</dimen>
+    <dimen name="blocked_numbers_primary2_font_size">16sp</dimen>
     <dimen name="blocked_numbers_secondary_font_size">14sp</dimen>
     <dimen name="blocked_numbers_line_spacing">8sp</dimen>
     <dimen name="blocked_numbers_secondary_line_spacing">6sp</dimen>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index bb63ad2..da61da8 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -79,6 +79,10 @@
         a text response. [CHAR LIMIT=40] -->
     <string name="respond_via_sms_confirmation_format">Message sent to <xliff:g id="phone_number">%s</xliff:g>.</string>
 
+    <!-- "Respond via SMS": Error toast shown after failing to send
+        a text response. [CHAR LIMIT=40] -->
+    <string name="respond_via_sms_failure_format">Message failed to send to <xliff:g id="phone_number">%s</xliff:g>.</string>
+
     <!-- Title of settings screen that allows user to enable and disable phone-accounts.
          Each method for placing a call (SIM1, SIM2, SIP account, etc) has a phone-account.
          Phone-accounts that are created by third party apps can be disabled and enabled by user.
@@ -107,7 +111,7 @@
     <!-- Button label on the "Missing voicemail number" dialog -->
     <string name="add_vm_number_str">Add number</string>
 
-    <!-- Title of dialog used to comfirm whether the user intends to change the default dialer
+    <!-- Title of dialog used to confirm whether the user intends to change the default dialer
             application [CHAR LIMIT=55]-->
     <string name="change_default_dialer_dialog_title">Make <xliff:g id="new_app">%s</xliff:g> your default Phone app?</string>
     <!-- Confirmation text that a user taps on to change the Default Phone App-->
@@ -117,6 +121,18 @@
     <!-- Warning message indicating what may happen if a user allows a 3rd party app to become the default dialer.-->
     <string name="change_default_dialer_warning_message"><xliff:g id="new_app">%s</xliff:g> will be able to place and control all aspects of calls. Only apps you trust should be set as the default Phone app.</string>
 
+    <!-- Title of dialog used to confirm whether the user intends to change the default call screening
+            application [CHAR LIMIT=55]-->
+    <string name="change_default_call_screening_dialog_title">Make <xliff:g id="new_app">%s</xliff:g> your default call screening app?</string>
+    <!-- Warning message indicating the old call screening app will no longer be able to screen calls.-->
+    <string name="change_default_call_screening_warning_message_for_disable_old_app"><xliff:g id="old_app">%s</xliff:g> will no longer be able to screen calls.</string>
+    <!-- Warning message indicating what may happen if a user allows a 3rd party app to become the default call screening.-->
+    <string name="change_default_call_screening_warning_message"><xliff:g id="new_app">%s</xliff:g> will be able to see information about callers not in your contacts and will be able to block these calls. Only apps you trust should be set as the default call screening app.</string>
+    <!-- Confirmation text that a user taps on to change the Default call screening App-->
+    <string name="change_default_call_screening_dialog_affirmative">Set Default</string>
+    <!-- Cancel text that a user taps on to not change the Default call screening App-->
+    <string name="change_default_call_screening_dialog_negative">Cancel</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. -->
@@ -233,6 +249,12 @@
     <!-- The "decline" button for an incoming call. [CHAR LIMIT=60] -->
     <string name="decline_incoming_call">Decline</string>
 
+    <!-- Error message shown to the user when an outgoing call cannot be placed because there no
+         calling service is present on the device which supports this call type.
+         This is typically encountered when the user tries to dial a SIP/VOIP call, but there are
+         no calling services present which support SIP calling. [CHAR LIMIT=none] -->
+    <string name="cant_call_due_to_no_supported_service">Call cannot be placed because there are no calling accounts which support calls of this type.</string>
+
     <!-- Error message shown to the user when an outgoing call cannot be placed due to an ongoing
          phone call in a third-party app.  For example:
          Call cannot be placed due to your Duo call. [CHAR LIMIT=none] -->
@@ -285,4 +307,10 @@
     <string name="phone_strings_emergency_call_made_dialog_title_txt">Emergency call made</string>
     <!-- Notification details that appear when the user taps the notification "phone_strings_call_blocking_turned_off_notification_text_txt". -->
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt">Call Blocking has been disabled to allow emergency responders to contact you.</string>
+    <!-- Window title used for the Telecom Developer Menu -->
+    <string name="developer_title">Telecom Developer Menu</string>
+    <!-- Label for a switch in the Telecom Developer Menu which is used to enable the enhanced call
+         blocking functionality (for test purposes).
+         DO NOT TRANSLATE -->
+    <string name="developer_enhanced_call_blocking" translatable="false">Enhanced Call Blocking</string>
 </resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 3216719..2e8e624 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -26,58 +26,66 @@
     </style>
 
     <style name="Theme.Telecom.DialerSettings" parent="@android:style/Theme.Material.Light">
+        <item name="android:tint">@color/blocked_numbers_secondary_text_color</item>
         <item name="android:actionBarStyle">@style/TelecomDialerSettingsActionBarStyle</item>
         <item name="android:actionOverflowButtonStyle">@style/TelecomDialerSettingsActionOverflowButtonStyle</item>
-        <item name="android:colorPrimaryDark">@color/dialer_settings_color_dark</item>
-        <item name="android:homeAsUpIndicator">@drawable/ic_back_arrow</item>
+        <item name="android:colorPrimaryDark">@color/background_color</item>
+        <item name="android:windowLightStatusBar">true</item>
+        <item name="android:navigationBarColor">@color/background_color</item>
+        <item name="android:navigationBarDividerColor">@color/blocked_numbers_divider_color</item>
+        <item name="android:windowLightNavigationBar">true</item>
         <item name="android:windowContentOverlay">@null</item>
     </style>
 
     <style name="Theme.Telecom.BlockedNumbers" parent="@android:style/Theme.Material.Light">
+        <item name="android:tint">@color/blocked_numbers_secondary_text_color</item>
         <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:colorPrimaryDark">@color/background_color</item>
+        <item name="android:windowLightStatusBar">true</item>
+        <item name="android:navigationBarColor">@color/background_color</item>
+        <item name="android:navigationBarDividerColor">@color/blocked_numbers_divider_color</item>
+        <item name="android:windowLightNavigationBar">true</item>
         <item name="android:windowContentOverlay">@null</item>
         <item name="android:colorAccent">@color/theme_color</item>
         <item name="android:listDivider">@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>
+        <item name="android:background">@color/background_color</item>
+        <item name="android:titleTextStyle">@style/BlockedNumbersTextHead1</item>
         <item name="android:elevation">@dimen/dialer_settings_actionbar_elevation</item>
         <!-- Empty icon -->
         <item name="android:icon">@android:color/transparent</item>
     </style>
 
-    <style name="TelecomDialerSettingsActionBarTitleText"
-            parent="@android:style/TextAppearance.Material.Widget.ActionBar.Title">
-        <item name="android:textColor">@color/dialer_settings_actionbar_text_color</item>
-    </style>
-
     <style name="TelecomDialerSettingsActionOverflowButtonStyle"
             parent="@android:style/Widget.Material.Light.ActionButton.Overflow">
         <item name="android:src">@drawable/ic_more_vert_24dp</item>
     </style>
 
-    <style name="BlockedNumbersButton">
+    <style name="BlockedNumbersButton" parent="BlockedNumbersTextPrimary2">
         <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>
+    <style name="BlockedNumbersTextHead1"
+           parent="@android:style/TextAppearance.Material.Widget.ActionBar.Title">
+        <item name="android:textColor">@color/blocked_numbers_primary_text_color</item>
+        <item name="android:textSize">@dimen/blocked_numbers_head1_font_size</item>
+        <item name="android:fontFamily">sans-serif-regular</item>
+    </style>
+
+    <style name="BlockedNumbersTextPrimary2">
+        <item name="android:textColor">@color/blocked_numbers_primary_text_color</item>
+        <item name="android:textSize">@dimen/blocked_numbers_primary2_font_size</item>
+        <item name="android:fontFamily">sans-serif-regular</item>
         <item name="android:lineSpacingExtra">@dimen/blocked_numbers_line_spacing</item>
     </style>
 
-    <style name="BlockedNumbersTitleText"  parent="BlockedNumbersText">
-        <item name="android:textColor">@color/blocked_numbers_title_text_color</item>
-    </style>
-
-    <style name="BlockedNumbersSecondaryText">
+    <style name="BlockedNumbersTextSecondary">
         <item name="android:textColor">@color/blocked_numbers_secondary_text_color</item>
         <item name="android:textSize">@dimen/blocked_numbers_secondary_font_size</item>
+        <item name="android:fontFamily">sans-serif-regular</item>
         <item name="android:lineSpacingExtra">@dimen/blocked_numbers_secondary_line_spacing</item>
     </style>
 </resources>
diff --git a/res/xml/activity_blocked_numbers.xml b/res/xml/activity_blocked_numbers.xml
index 15e1859..e137313 100644
--- a/res/xml/activity_blocked_numbers.xml
+++ b/res/xml/activity_blocked_numbers.xml
@@ -43,7 +43,7 @@
                     android:paddingTop="@dimen/blocked_numbers_large_padding"
                     android:paddingLeft="@dimen/blocked_numbers_large_padding"
                     android:paddingRight="@dimen/blocked_numbers_large_padding"
-                    style="@style/BlockedNumbersTitleText"
+                    style="@style/BlockedNumbersTextPrimary2"
                     android:visibility="gone" />
 
             <LinearLayout
@@ -70,7 +70,7 @@
                             android:layout_height="wrap_content"
                             android:text="@string/blocked_numbers_msg"
                             android:paddingBottom="@dimen/blocked_numbers_extra_large_padding"
-                            style="@style/BlockedNumbersTitleText" />
+                            style="@style/BlockedNumbersTextPrimary2" />
 
                     <TextView
                             android:id="@+id/add_blocked"
diff --git a/res/xml/add_blocked_number_dialog.xml b/res/xml/add_blocked_number_dialog.xml
index ed7d507..d0942ec 100644
--- a/res/xml/add_blocked_number_dialog.xml
+++ b/res/xml/add_blocked_number_dialog.xml
@@ -20,13 +20,14 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:orientation="vertical"
+        android:gravity="center"
         android:padding="@dimen/blocked_numbers_dialog_padding">
     <TextView
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:text="@string/add_blocked_dialog_body"
             android:paddingBottom="@dimen/blocked_numbers_large_padding"
-            style="@style/BlockedNumbersTitleText" />
+            style="@style/BlockedNumbersTextPrimary2" />
     <EditText
             android:id="@+id/add_blocked_number"
             android:layout_width="match_parent"
diff --git a/res/xml/blocking_suppressed_butterbar.xml b/res/xml/blocking_suppressed_butterbar.xml
index d6a8472..8b941b9 100644
--- a/res/xml/blocking_suppressed_butterbar.xml
+++ b/res/xml/blocking_suppressed_butterbar.xml
@@ -19,7 +19,7 @@
         xmlns:android="http://schemas.android.com/apk/res/android"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:background="@color/blocked_numbers_butter_bar_color">
+        android:background="@color/background_color">
 
     <ImageView
             android:id="@+id/icon"
@@ -39,7 +39,7 @@
             android:paddingTop="@dimen/blocked_numbers_large_padding"
             android:paddingRight="@dimen/blocked_numbers_large_padding"
             android:text="@string/blocked_numbers_butter_bar_title"
-            style="@style/BlockedNumbersTitleText" />
+            style="@style/BlockedNumbersTextPrimary2" />
 
     <TextView
             android:id="@+id/description"
@@ -51,7 +51,7 @@
             android:paddingBottom="@dimen/blocked_numbers_large_padding"
             android:paddingRight="@dimen/blocked_numbers_large_padding"
             android:text="@string/blocked_numbers_butter_bar_body"
-            style="@style/BlockedNumbersSecondaryText" />
+            style="@style/BlockedNumbersTextSecondary" />
 
     <TextView
             android:id="@+id/reenable_button"
diff --git a/res/xml/layout_blocked_number.xml b/res/xml/layout_blocked_number.xml
index fbd7de3..720d71a 100644
--- a/res/xml/layout_blocked_number.xml
+++ b/res/xml/layout_blocked_number.xml
@@ -20,7 +20,7 @@
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:focusable="true"
-    style="@style/BlockedNumbersText"
+    style="@style/BlockedNumbersTextPrimary2"
     android:gravity="start">
 
     <TextView
diff --git a/scripts/telecom_testing.sh b/scripts/telecom_testing.sh
index 779f449..17309d6 100644
--- a/scripts/telecom_testing.sh
+++ b/scripts/telecom_testing.sh
@@ -121,7 +121,7 @@
   if [ $coverage = true ] && [ $project =~ "telecom" ] ; then
     e_options="${e_options} -e coverage 'true'"
   fi
-  adb shell am instrument ${e_options} -w "$package_prefix/$instrumentation"
+  adb shell am instrument --no-hidden-api-checks ${e_options} -w "$package_prefix/$instrumentation"
 
   # Code coverage only enabled for Telecom.
   if [ $coverage = true ] && [ $project =~ "telecom" ] ; then
diff --git a/src/com/android/server/telecom/Analytics.java b/src/com/android/server/telecom/Analytics.java
index d2cdeca..1d3a90e 100644
--- a/src/com/android/server/telecom/Analytics.java
+++ b/src/com/android/server/telecom/Analytics.java
@@ -31,6 +31,9 @@
 import com.android.server.telecom.nano.TelecomLogClass;
 
 import java.io.PrintWriter;
+import java.time.Instant;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -39,6 +42,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.PriorityQueue;
+import java.util.concurrent.LinkedBlockingDeque;
 import java.util.stream.Collectors;
 
 import static android.telecom.ParcelableCallAnalytics.AnalyticsEvent;
@@ -165,6 +169,9 @@
         public void setCallIsAdditional(boolean isAdditional) {
         }
 
+        public void setCallIsEmergency(boolean isEmergency) {
+        }
+
         public void setCallIsInterrupted(boolean isInterrupted) {
         }
 
@@ -305,6 +312,12 @@
         }
 
         @Override
+        public void setCallIsEmergency(boolean isEmergency) {
+            Log.d(TAG, "setting call as emergency: " + isEmergency);
+            this.isEmergency = isEmergency;
+        }
+
+        @Override
         public void setCallDisconnectCause(DisconnectCause disconnectCause) {
             Log.d(TAG, "setting disconnectCause for call " + callId + " to " + disconnectCause);
             this.callTerminationReason = disconnectCause;
@@ -376,6 +389,7 @@
                     + "    direction: " + getCallDirectionString() + '\n'
                     + "    isAdditionalCall: " + isAdditionalCall + '\n'
                     + "    isInterrupted: " + isInterrupted + '\n'
+                    + "    isEmergency: " + isEmergency + '\n'
                     + "    callTechnologies: " + getCallTechnologiesAsString() + '\n'
                     + "    callTerminationReason: " + getCallDisconnectReasonString() + '\n'
                     + "    connectionService: " + connectionService + '\n'
@@ -568,8 +582,11 @@
     public static final long MILLIS_IN_1_SECOND = ParcelableCallAnalytics.MILLIS_IN_1_SECOND;
 
     public static final int MAX_NUM_CALLS_TO_STORE = 100;
+    public static final int MAX_NUM_DUMP_TIMES_TO_STORE = 100;
 
     private static final Object sLock = new Object(); // Coarse lock for all of analytics
+    private static final LinkedBlockingDeque<Long> sDumpTimes =
+            new LinkedBlockingDeque<>(MAX_NUM_DUMP_TIMES_TO_STORE);
     private static final Map<String, CallInfoImpl> sCallIdToInfo = new HashMap<>();
     private static final LinkedList<String> sActiveCallIds = new LinkedList<>();
     private static final List<SessionTiming> sSessionTimings = new LinkedList<>();
@@ -615,6 +632,7 @@
         TelecomLogClass.TelecomLog result = new TelecomLogClass.TelecomLog();
 
         synchronized (sLock) {
+            noteDumpTime();
             result.callLogs = sCallIdToInfo.values().stream()
                     .map(CallInfoImpl::toProto)
                     .toArray(TelecomLogClass.CallLog[]::new);
@@ -670,6 +688,12 @@
                     .forEach(e -> writer.printf("%s: %.2f\n",
                             sSessionIdToLogSession.get(e.getKey()), e.getValue()));
             writer.println("Hardware Version: " + SystemProperties.get("ro.boot.revision", ""));
+            writer.println("Past analytics dumps: ");
+            writer.increaseIndent();
+            for (long time : sDumpTimes) {
+                writer.println(Instant.ofEpochMilli(time).atZone(ZoneOffset.UTC));
+            }
+            writer.decreaseIndent();
         }
     }
 
@@ -679,6 +703,17 @@
         }
     }
 
+    public static void noteDumpTime() {
+        if (sDumpTimes.remainingCapacity() == 0) {
+            sDumpTimes.removeLast();
+        }
+        try {
+            sDumpTimes.addFirst(System.currentTimeMillis());
+        } catch (IllegalStateException e) {
+            Log.w(TAG, "Failed to note dump time -- full");
+        }
+    }
+
     /**
      * Returns a copy of callIdToInfo. Use only for testing.
      */
diff --git a/src/com/android/server/telecom/AsyncRingtonePlayer.java b/src/com/android/server/telecom/AsyncRingtonePlayer.java
index 0940429..439eb63 100644
--- a/src/com/android/server/telecom/AsyncRingtonePlayer.java
+++ b/src/com/android/server/telecom/AsyncRingtonePlayer.java
@@ -170,6 +170,10 @@
             handleRepeat();
         } else {
             mRingtone.setLooping(true);
+            if (mRingtone.isPlaying()) {
+                Log.d(this, "Ringtone already playing.");
+                return;
+            }
             mRingtone.play();
             Log.i(this, "Play ringtone, looping.");
         }
diff --git a/src/com/android/server/telecom/BluetoothHeadsetProxy.java b/src/com/android/server/telecom/BluetoothHeadsetProxy.java
index 0f492df..a43b3cd 100644
--- a/src/com/android/server/telecom/BluetoothHeadsetProxy.java
+++ b/src/com/android/server/telecom/BluetoothHeadsetProxy.java
@@ -43,9 +43,10 @@
     }
 
     public void phoneStateChanged(int numActive, int numHeld, int callState, String number,
-            int type) {
+            int type, String name) {
 
-        mBluetoothHeadset.phoneStateChanged(numActive, numHeld, callState, number, type);
+        mBluetoothHeadset.phoneStateChanged(numActive, numHeld, callState, number, type,
+            name);
     }
 
     public List<BluetoothDevice> getConnectedDevices() {
@@ -83,4 +84,4 @@
     public boolean isInbandRingingEnabled() {
         return mBluetoothHeadset.isInbandRingingEnabled();
     }
-}
\ No newline at end of file
+}
diff --git a/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java b/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java
index 804909c..ff358d5 100644
--- a/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java
+++ b/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java
@@ -514,8 +514,6 @@
                 mCallsManager.disconnectCall(activeCall);
                 if (ringingCall != null) {
                     mCallsManager.answerCall(ringingCall, VideoProfile.STATE_AUDIO_ONLY);
-                } else if (heldCall != null) {
-                    mCallsManager.unholdCall(heldCall);
                 }
                 return true;
             }
@@ -708,11 +706,16 @@
 
         String ringingAddress = null;
         int ringingAddressType = 128;
+        String ringingName = null;
         if (ringingCall != null && ringingCall.getHandle() != null) {
             ringingAddress = ringingCall.getHandle().getSchemeSpecificPart();
             if (ringingAddress != null) {
                 ringingAddressType = PhoneNumberUtils.toaFromString(ringingAddress);
             }
+            ringingName = ringingCall.getCallerDisplayName();
+            if (TextUtils.isEmpty(ringingName)) {
+                ringingName = ringingCall.getName();
+            }
         }
         if (ringingAddress == null) {
             ringingAddress = "";
@@ -784,18 +787,21 @@
                         "numHeld %s, " +
                         "callState %s, " +
                         "ringing number %s, " +
-                        "ringing type %s",
+                        "ringing type %s, " +
+                        "ringing name %s",
                         mNumActiveCalls,
                         mNumHeldCalls,
                         CALL_STATE_DIALING,
                         Log.pii(mRingingAddress),
-                        mRingingAddressType);
+                        mRingingAddressType,
+                        Log.pii(ringingName));
                 mBluetoothHeadset.phoneStateChanged(
                         mNumActiveCalls,
                         mNumHeldCalls,
                         CALL_STATE_DIALING,
                         mRingingAddress,
-                        mRingingAddressType);
+                        mRingingAddressType,
+                        ringingName);
             }
 
             Log.i(TAG, "updateHeadsetWithCallState " +
@@ -803,19 +809,22 @@
                     "numHeld %s, " +
                     "callState %s, " +
                     "ringing number %s, " +
-                    "ringing type %s",
+                    "ringing type %s, " +
+                    "ringing name %s",
                     mNumActiveCalls,
                     mNumHeldCalls,
                     mBluetoothCallState,
                     Log.pii(mRingingAddress),
-                    mRingingAddressType);
+                    mRingingAddressType,
+                    Log.pii(ringingName));
 
             mBluetoothHeadset.phoneStateChanged(
                     mNumActiveCalls,
                     mNumHeldCalls,
                     mBluetoothCallState,
                     mRingingAddress,
-                    mRingingAddressType);
+                    mRingingAddressType,
+                    ringingName);
 
             mHeadsetUpdatedRecently = true;
         }
@@ -875,6 +884,7 @@
                 return CALL_STATE_HELD;
 
             case CallState.RINGING:
+            case CallState.ANSWERED:
                 if (isForegroundCall) {
                     return CALL_STATE_INCOMING;
                 } else {
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index bc20d1c..84ceee6 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -32,6 +32,7 @@
 import android.os.Trace;
 import android.provider.ContactsContract.Contacts;
 import android.telecom.CallAudioState;
+import android.telecom.CallIdentification;
 import android.telecom.Conference;
 import android.telecom.ConnectionService;
 import android.telecom.DisconnectCause;
@@ -50,6 +51,7 @@
 import android.text.TextUtils;
 import android.util.StatsLog;
 import android.os.UserHandle;
+import android.widget.Toast;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telecom.IVideoProvider;
@@ -130,6 +132,7 @@
         void onConnectionManagerPhoneAccountChanged(Call call);
         void onPhoneAccountChanged(Call call);
         void onConferenceableCallsChanged(Call call);
+        void onConferenceStateChanged(Call call, boolean isConference);
         boolean onCanceledViaNewOutgoingCallBroadcast(Call call, long disconnectionTimeout);
         void onHoldToneRequested(Call call);
         void onConnectionEvent(Call call, String event, Bundle extras);
@@ -140,6 +143,7 @@
                                  Bundle extras, boolean isLegacy);
         void onHandoverFailed(Call call, int error);
         void onHandoverComplete(Call call);
+        void onCallIdentificationChanged(Call call);
     }
 
     public abstract static class ListenerBase implements Listener {
@@ -198,6 +202,8 @@
         @Override
         public void onConferenceableCallsChanged(Call call) {}
         @Override
+        public void onConferenceStateChanged(Call call, boolean isConference) {}
+        @Override
         public boolean onCanceledViaNewOutgoingCallBroadcast(Call call, long disconnectionTimeout) {
             return false;
         }
@@ -218,6 +224,8 @@
         public void onHandoverFailed(Call call, int error) {}
         @Override
         public void onHandoverComplete(Call call) {}
+        @Override
+        public void onCallIdentificationChanged(Call call) {}
     }
 
     private final CallerInfoLookupHelper.OnQueryCompleteListener mCallerInfoQueryListener =
@@ -246,7 +254,7 @@
     /**
      * The post-dial digits that were dialed after the network portion of the number
      */
-    private final String mPostDialDigits;
+    private String mPostDialDigits;
 
     /**
      * The secondary line number that an incoming call has been received on if the SIM subscription
@@ -414,7 +422,7 @@
     private final TelecomSystem.SyncRoot mLock;
     private final String mId;
     private String mConnectionId;
-    private Analytics.CallInfo mAnalytics;
+    private Analytics.CallInfo mAnalytics = new Analytics.CallInfo();
     private char mPlayingDtmfTone;
 
     private boolean mWasConferencePreviouslyMerged = false;
@@ -461,7 +469,7 @@
      * Indicates whether the {@link PhoneAccount} associated with this call supports video calling.
      * {@code True} if the phone account supports video calling, {@code false} otherwise.
      */
-    private boolean mIsVideoCallingSupported = false;
+    private boolean mIsVideoCallingSupportedByPhoneAccount = false;
 
     private PhoneNumberUtilsAdapter mPhoneNumberUtilsAdapter;
 
@@ -523,18 +531,29 @@
     private int mHandoverState = HandoverState.HANDOVER_NONE;
 
     /**
+     * Indicates whether this call is using one of the
+     * {@link com.android.server.telecom.callfiltering.IncomingCallFilter.CallFilter} modules.
+     */
+    private boolean mIsUsingCallFiltering = false;
+
+    /**
+     * {@link CallIdentification} provided by a {@link android.telecom.CallScreeningService}.
+     */
+    private CallIdentification mCallIdentification = null;
+
+    /**
      * Persists the specified parameters and initializes the new instance.
-     *  @param context The context.
+     * @param context The context.
      * @param repository The connection service repository.
      * @param handle The handle to dial.
      * @param gatewayInfo Gateway information to use for the call.
      * @param connectionManagerPhoneAccountHandle Account to use for the service managing the call.
-*         This account must be one that was registered with the
-*         {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER} flag.
+     *         This account must be one that was registered with the
+     *           {@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.
+     *         one that was registered with the {@link PhoneAccount#CAPABILITY_CALL_PROVIDER} flag.
      * @param callDirection one of CALL_DIRECTION_INCOMING, CALL_DIRECTION_OUTGOING,
-*         or CALL_DIRECTION_UNKNOWN.
+     *         or CALL_DIRECTION_UNKNOWN.
      * @param shouldAttachToExistingConnection Set to true to attach the call to an existing
      * @param clockProxy
      */
@@ -544,8 +563,6 @@
             CallsManager callsManager,
             TelecomSystem.SyncRoot lock,
             ConnectionServiceRepository repository,
-            ContactsAsyncHelper contactsAsyncHelper,
-            CallerInfoAsyncQueryFactory callerInfoAsyncQueryFactory,
             PhoneNumberUtilsAdapter phoneNumberUtilsAdapter,
             Uri handle,
             GatewayInfo gatewayInfo,
@@ -574,14 +591,13 @@
         mShouldAttachToExistingConnection = shouldAttachToExistingConnection
                 || callDirection == CALL_DIRECTION_INCOMING;
         maybeLoadCannedSmsResponses();
-        mAnalytics = new Analytics.CallInfo();
         mClockProxy = clockProxy;
         mCreationTimeMillis = mClockProxy.currentTimeMillis();
     }
 
     /**
      * Persists the specified parameters and initializes the new instance.
-     *  @param context The context.
+     * @param context The context.
      * @param repository The connection service repository.
      * @param handle The handle to dial.
      * @param gatewayInfo Gateway information to use for the call.
@@ -603,8 +619,6 @@
             CallsManager callsManager,
             TelecomSystem.SyncRoot lock,
             ConnectionServiceRepository repository,
-            ContactsAsyncHelper contactsAsyncHelper,
-            CallerInfoAsyncQueryFactory callerInfoAsyncQueryFactory,
             PhoneNumberUtilsAdapter phoneNumberUtilsAdapter,
             Uri handle,
             GatewayInfo gatewayInfo,
@@ -616,8 +630,8 @@
             long connectTimeMillis,
             long connectElapsedTimeMillis,
             ClockProxy clockProxy) {
-        this(callId, context, callsManager, lock, repository, contactsAsyncHelper,
-                callerInfoAsyncQueryFactory, phoneNumberUtilsAdapter, handle, gatewayInfo,
+        this(callId, context, callsManager, lock, repository,
+                phoneNumberUtilsAdapter, handle, gatewayInfo,
                 connectionManagerPhoneAccountHandle, targetPhoneAccountHandle, callDirection,
                 shouldAttachToExistingConnection, isConference, clockProxy);
 
@@ -637,6 +651,10 @@
     }
 
     public void initAnalytics() {
+        initAnalytics(null);
+    }
+
+    public void initAnalytics(String callingPackage) {
         int analyticsDirection;
         switch (mCallDirection) {
             case CALL_DIRECTION_OUTGOING:
@@ -651,7 +669,8 @@
                 analyticsDirection = Analytics.UNKNOWN_DIRECTION;
         }
         mAnalytics = Analytics.initiateCallAnalytics(mId, analyticsDirection);
-        Log.addEvent(this, LogUtils.Events.CREATED);
+        mAnalytics.setCallIsEmergency(mIsEmergencyCall);
+        Log.addEvent(this, LogUtils.Events.CREATED, callingPackage);
     }
 
     public Analytics.CallInfo getAnalytics() {
@@ -666,6 +685,7 @@
             mCallerInfo.cachedPhotoIcon = null;
             mCallerInfo.cachedPhoto = null;
         }
+        closeRttStreams();
 
         Log.addEvent(this, LogUtils.Events.DESTROYED);
     }
@@ -869,15 +889,17 @@
      * (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
      * and instead keep the code resilient to unexpected state changes.
+     * @return true indicates if setState succeeded in setting the state to newState,
+     * else it is failed, and the call is still in its original state.
      */
-    public void setState(int newState, String tag) {
+    public boolean setState(int newState, String tag) {
         if (mState != newState) {
             Log.v(this, "setState %s -> %s", mState, newState);
 
             if (newState == CallState.DISCONNECTED && shouldContinueProcessingAfterDisconnect()) {
                 Log.w(this, "continuing processing disconnected call with another service");
                 mCreateConnectionProcessor.continueProcessingIfPossible(this, mDisconnectCause);
-                return;
+                return false;
             }
 
             updateVideoHistoryViaState(mState, newState);
@@ -938,6 +960,9 @@
                 case CallState.RINGING:
                     event = LogUtils.Events.SET_RINGING;
                     break;
+                case CallState.ANSWERED:
+                    event = LogUtils.Events.SET_ANSWERED;
+                    break;
             }
             if (event != null) {
                 // The string data should be just the tag.
@@ -953,6 +978,7 @@
             StatsLog.write(StatsLog.CALL_STATE_CHANGED, newState, statsdDisconnectCause,
                     isSelfManaged(), isExternalCall());
         }
+        return true;
     }
 
     void setRingbackRequested(boolean ringbackRequested) {
@@ -979,6 +1005,10 @@
         return mPostDialDigits;
     }
 
+    public void clearPostDialDigits() {
+        mPostDialDigits = null;
+    }
+
     public String getViaNumber() {
         return mViaNumber;
     }
@@ -1021,6 +1051,7 @@
                 mIsEmergencyCall = mHandle != null &&
                         mPhoneNumberUtilsAdapter.isLocalEmergencyNumber(mContext,
                                 mHandle.getSchemeSpecificPart());
+                mAnalytics.setCallIsEmergency(mIsEmergencyCall);
             }
             startCallerInfoLookup();
             for (Listener l : mListeners) {
@@ -1079,12 +1110,23 @@
         return mDisconnectCause;
     }
 
+    /**
+     * @return {@code true} if this is an outgoing call to emergency services. An outgoing call is
+     * identified as an emergency call by the dialer phone number.
+     */
     @VisibleForTesting
     public boolean isEmergencyCall() {
         return mIsEmergencyCall;
     }
 
     /**
+     * @return {@code true} if the network has identified this call as an emergency call.
+     */
+    public boolean isNetworkIdentifiedEmergencyCall() {
+        return hasProperty(Connection.PROPERTY_NETWORK_IDENTIFIED_EMERGENCY_CALL);
+    }
+
+    /**
      * @return The original handle this call is associated with. In-call services should use this
      * handle when indicating in their UI the handle that is being called.
      */
@@ -1205,8 +1247,36 @@
         return mUseCallRecordingTone;
     }
 
-    public boolean isVideoCallingSupported() {
-        return mIsVideoCallingSupported;
+    /**
+     * @return {@code true} if the {@link Call}'s {@link #getTargetPhoneAccount()} supports video.
+     */
+    public boolean isVideoCallingSupportedByPhoneAccount() {
+        return mIsVideoCallingSupportedByPhoneAccount;
+    }
+
+    /**
+     * Sets whether video calling is supported by the current phone account. Since video support
+     * can change during a call, this method facilitates updating call video state.
+     * @param isVideoCallingSupported Sets whether video calling is supported.
+     */
+    public void setVideoCallingSupportedByPhoneAccount(boolean isVideoCallingSupported) {
+        if (mIsVideoCallingSupportedByPhoneAccount == isVideoCallingSupported) {
+            return;
+        }
+        Log.i(this, "setVideoCallingSupportedByPhoneAccount: isSupp=%b", isVideoCallingSupported);
+        mIsVideoCallingSupportedByPhoneAccount = isVideoCallingSupported;
+
+        // Force an update of the connection capabilities so that the dialer is informed of the new
+        // video capabilities based on the phone account's support for video.
+        setConnectionCapabilities(getConnectionCapabilities(), true /* force */);
+    }
+
+    /**
+     * @return {@code true} if the {@link Call} locally supports video.
+     */
+    public boolean isLocallyVideoCapable() {
+        return (getConnectionCapabilities() & Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL)
+                == Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL;
     }
 
     public boolean isSelfManaged() {
@@ -1308,16 +1378,16 @@
         if (mTargetPhoneAccountHandle == null) {
             // If no target phone account handle is specified, assume we can potentially perform a
             // video call; once the phone account is set, we can confirm that it is video capable.
-            mIsVideoCallingSupported = true;
+            mIsVideoCallingSupportedByPhoneAccount = true;
             Log.d(this, "checkIfVideoCapable: no phone account selected; assume video capable.");
             return;
         }
         PhoneAccount phoneAccount =
                 phoneAccountRegistrar.getPhoneAccountUnchecked(mTargetPhoneAccountHandle);
-        mIsVideoCallingSupported = phoneAccount != null && phoneAccount.hasCapabilities(
-                    PhoneAccount.CAPABILITY_VIDEO_CALLING);
+        mIsVideoCallingSupportedByPhoneAccount = phoneAccount != null &&
+                phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_VIDEO_CALLING);
 
-        if (!mIsVideoCallingSupported && VideoProfile.isVideo(getVideoState())) {
+        if (!mIsVideoCallingSupportedByPhoneAccount && VideoProfile.isVideo(getVideoState())) {
             // The PhoneAccount for the Call was set to one which does not support video calling,
             // and the current call is configured to be a video call; downgrade to audio-only.
             setVideoState(VideoProfile.STATE_AUDIO_ONLY);
@@ -1398,7 +1468,15 @@
         return mConnectTimeMillis;
     }
 
-    int getConnectionCapabilities() {
+    public void setConnectTimeMillis(long connectTimeMillis) {
+        mConnectTimeMillis = connectTimeMillis;
+    }
+
+    public void setConnectElapsedTimeMillis(long connectElapsedTimeMillis) {
+        mConnectElapsedTimeMillis = connectElapsedTimeMillis;
+    }
+
+    public int getConnectionCapabilities() {
         return mConnectionCapabilities;
     }
 
@@ -1406,7 +1484,7 @@
         return mConnectionProperties;
     }
 
-    void setConnectionCapabilities(int connectionCapabilities) {
+    public void setConnectionCapabilities(int connectionCapabilities) {
         setConnectionCapabilities(connectionCapabilities, false /* forceUpdate */);
     }
 
@@ -1416,12 +1494,12 @@
         if (forceUpdate || mConnectionCapabilities != connectionCapabilities) {
             // If the phone account does not support video calling, and the connection capabilities
             // passed in indicate that the call supports video, remove those video capabilities.
-            if (!isVideoCallingSupported() && doesCallSupportVideo(connectionCapabilities)) {
+            if (!isVideoCallingSupportedByPhoneAccount()
+                    && doesCallSupportVideo(connectionCapabilities)) {
                 Log.w(this, "setConnectionCapabilities: attempt to set connection as video " +
                         "capable when not supported by the phone account.");
                 connectionCapabilities = removeVideoCapabilities(connectionCapabilities);
             }
-
             int previousCapabilities = mConnectionCapabilities;
             mConnectionCapabilities = connectionCapabilities;
             for (Listener l : mListeners) {
@@ -1437,7 +1515,7 @@
         }
     }
 
-    void setConnectionProperties(int connectionProperties) {
+    public void setConnectionProperties(int connectionProperties) {
         Log.v(this, "setConnectionProperties: %s", Connection.propertiesToString(
                 connectionProperties));
 
@@ -1852,7 +1930,7 @@
         // Check to verify that the call is still in the ringing state. A call can change states
         // between the time the user hits 'answer' and Telecom receives the command.
         if (isRinging("answer")) {
-            if (!isVideoCallingSupported() && VideoProfile.isVideo(videoState)) {
+            if (!isVideoCallingSupportedByPhoneAccount() && VideoProfile.isVideo(videoState)) {
                 // Video calling is not supported, yet the InCallService is attempting to answer as
                 // video.  We will simply answer as audio-only.
                 videoState = VideoProfile.STATE_AUDIO_ONLY;
@@ -1979,6 +2057,7 @@
         switch (mState) {
             case CallState.NEW:
             case CallState.RINGING:
+            case CallState.ANSWERED:
             case CallState.DISCONNECTED:
             case CallState.ABORTED:
                 return false;
@@ -2007,7 +2086,7 @@
      * @param source The source of the extras addition.
      * @param extras The extras.
      */
-    void putExtras(int source, Bundle extras) {
+    public void putExtras(int source, Bundle extras) {
         if (extras == null) {
             return;
         }
@@ -2231,10 +2310,20 @@
     public void sendCallEvent(String event, int targetSdkVer, Bundle extras) {
         if (mConnectionService != null) {
             if (android.telecom.Call.EVENT_REQUEST_HANDOVER.equals(event)) {
-                if (targetSdkVer > Build.VERSION_CODES.O_MR1) {
+                if (targetSdkVer > Build.VERSION_CODES.P) {
                     Log.e(this, new Exception(), "sendCallEvent failed. Use public api handoverTo" +
-                            " for API > 27(O-MR1)");
-                    // TODO: Add "return" after DUO team adds new API support for handover
+                            " for API > 28(P)");
+                    // Event-based Handover APIs are deprecated, so inform the user.
+                    mHandler.post(new Runnable() {
+                        @Override
+                        public void run() {
+                            Toast.makeText(mContext, "WARNING: Event-based handover APIs are deprecated "
+                                            + "and will no longer function in Android Q.",
+                                    Toast.LENGTH_LONG).show();
+                        }
+                    });
+
+                    // Uncomment and remove toast at feature complete: return;
                 }
 
                 // Handover requests are targeted at Telecom, not the ConnectionService.
@@ -2755,7 +2844,7 @@
     public void setVideoState(int videoState) {
         // If the phone account associated with this call does not support video calling, then we
         // will automatically set the video state to audio-only.
-        if (!isVideoCallingSupported()) {
+        if (!isVideoCallingSupportedByPhoneAccount()) {
             Log.d(this, "setVideoState: videoState=%s defaulted to audio (video not supported)",
                     VideoProfile.videoStateToString(videoState));
             videoState = VideoProfile.STATE_AUDIO_ONLY;
@@ -3046,15 +3135,29 @@
     }
 
     /**
+     * Sets whether this {@link Call} is a conference or not.
+     * @param isConference
+     */
+    public void setConferenceState(boolean isConference) {
+        mIsConference = isConference;
+        Log.addEvent(this, LogUtils.Events.CONF_STATE_CHANGED, "isConference=" + isConference);
+        // Ultimately CallsManager needs to know so it can update the "add call" state and inform
+        // the UI to update itself.
+        for (Listener l : mListeners) {
+            l.onConferenceStateChanged(this, isConference);
+        }
+    }
+
+    /**
      * Sets the video history based on the state and state transitions of the call. Always add the
      * current video state to the video state history during a call transition except for the
-     * transitions DIALING->ACTIVE and RINGING->ACTIVE. In these cases, clear the history. If a
+     * transitions DIALING->ACTIVE and RINGING->ANSWERED. In these cases, clear the history. If a
      * call starts dialing/ringing as a VT call and gets downgraded to audio, we need to record
      * the history as an audio call.
      */
     private void updateVideoHistoryViaState(int oldState, int newState) {
-        if ((oldState == CallState.DIALING || oldState == CallState.RINGING)
-                && newState == CallState.ACTIVE) {
+        if ((oldState == CallState.DIALING && newState == CallState.ACTIVE)
+                || (oldState == CallState.RINGING && newState == CallState.ANSWERED)) {
             mVideoStateHistory = mVideoState;
         }
 
@@ -3069,4 +3172,45 @@
     boolean wasHighDefAudio() {
         return mWasHighDefAudio;
     }
+
+    public void setIsUsingCallFiltering(boolean isUsingCallFiltering) {
+        mIsUsingCallFiltering = isUsingCallFiltering;
+    }
+
+    /**
+     * Update the {@link CallIdentification} for a call.
+     * @param callIdentification the {@link CallIdentification}.
+     */
+    public void setCallIdentification(CallIdentification callIdentification) {
+        if (callIdentification != null) {
+            Log.addEvent(this, LogUtils.Events.CALL_IDENTIFICATION_SET,
+                    callIdentification.getCallScreeningPackageName());
+        }
+        mCallIdentification = callIdentification;
+
+        for (Listener l : mListeners) {
+            l.onCallIdentificationChanged(this);
+        }
+    }
+
+    /**
+     * @return Call identification returned by a {@link android.telecom.CallScreeningService}.
+     */
+    public CallIdentification getCallIdentification() {
+        return mCallIdentification;
+    }
+
+    /**
+     * When upgrading a call to video via
+     * {@link VideoProviderProxy#onSendSessionModifyRequest(VideoProfile, VideoProfile)}, if the
+     * upgrade is from audio to video, potentially auto-engage the speakerphone.
+     * @param newVideoState The proposed new video state for the call.
+     */
+    public void maybeEnableSpeakerForVideoUpgrade(@VideoProfile.VideoState int newVideoState) {
+        if (mCallsManager.isSpeakerphoneAutoEnabledForVideoCalls(newVideoState)) {
+            Log.i(this, "maybeEnableSpeakerForVideoCall; callId=%s, auto-enable speaker for call"
+                            + " upgraded to video.");
+            mCallsManager.setAudioRoute(CallAudioState.ROUTE_SPEAKER, null);
+        }
+    }
 }
diff --git a/src/com/android/server/telecom/CallAudioManager.java b/src/com/android/server/telecom/CallAudioManager.java
index 56f8db9..b68a851 100644
--- a/src/com/android/server/telecom/CallAudioManager.java
+++ b/src/com/android/server/telecom/CallAudioManager.java
@@ -105,11 +105,10 @@
         Log.d(LOG_TAG, "Call state changed for TC@%s: %s -> %s", call.getId(),
                 CallState.toString(oldState), CallState.toString(newState));
 
-        for (int i = 0; i < mCallStateToCalls.size(); i++) {
-            mCallStateToCalls.valueAt(i).remove(call);
-        }
-        if (mCallStateToCalls.get(newState) != null) {
-            mCallStateToCalls.get(newState).add(call);
+        removeCallFromAllBins(call);
+        HashSet<Call> newBinForCall = getBinForCall(call);
+        if (newBinForCall != null) {
+            newBinForCall.add(call);
         }
 
         updateForegroundCall();
@@ -148,8 +147,9 @@
         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);
+        HashSet<Call> newBinForCall = getBinForCall(call);
+        if (newBinForCall != null) {
+            newBinForCall.add(call);
         }
         updateForegroundCall();
         mCalls.add(call);
@@ -168,9 +168,7 @@
         Log.d(LOG_TAG, "Call removed with id TC@%s in state %s", call.getId(),
                 CallState.toString(call.getState()));
 
-        for (int i = 0; i < mCallStateToCalls.size(); i++) {
-            mCallStateToCalls.valueAt(i).remove(call);
-        }
+        removeCallFromAllBins(call);
 
         updateForegroundCall();
         mCalls.remove(call);
@@ -226,24 +224,6 @@
             return;
         }
 
-        // 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)) {
-            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);
-                }
-                mActiveDialingOrConnectingCalls.add(call);
-                mCallAudioModeStateMachine.sendMessageWithArgs(
-                        CallAudioModeStateMachine.MT_AUDIO_SPEEDUP_FOR_RINGING_CALL,
-                        makeArgsForModeStateMachine());
-            }
-        }
-
         // Turn off mute when a new incoming call is answered iff it's not a handover.
         if (!call.isHandoverInProgress()) {
             mute(false /* shouldMute */);
@@ -325,10 +305,7 @@
             onCallAdded(call);
         } else {
             // The call joined a conference, so stop tracking it.
-            if (mCallStateToCalls.get(call.getState()) != null) {
-                mCallStateToCalls.get(call.getState()).remove(call);
-            }
-
+            removeCallFromAllBins(call);
             updateForegroundCall();
             mCalls.remove(call);
         }
@@ -469,9 +446,9 @@
     }
 
     @VisibleForTesting
-    public void startCallWaiting() {
+    public void startCallWaiting(String reason) {
         if (mRingingCalls.size() == 1) {
-            mRinger.startCallWaiting(mRingingCalls.iterator().next());
+            mRinger.startCallWaiting(mRingingCalls.iterator().next(), reason);
         }
     }
 
@@ -591,6 +568,11 @@
                 onCallEnteringActiveDialingOrConnecting();
                 playRingbackForCall(call);
                 break;
+            case CallState.ANSWERED:
+                if (call.can(android.telecom.Call.Details.CAPABILITY_SPEED_UP_MT_AUDIO)) {
+                    onCallEnteringActiveDialingOrConnecting();
+                }
+                break;
         }
     }
 
@@ -681,6 +663,24 @@
                 Log.createSubsession());
     }
 
+    private HashSet<Call> getBinForCall(Call call) {
+        if (call.getState() == CallState.ANSWERED) {
+            // If the call has the speed-up-mt-audio capability, treat answered state as active
+            // for audio purposes.
+            if (call.can(android.telecom.Call.Details.CAPABILITY_SPEED_UP_MT_AUDIO)) {
+                return mActiveDialingOrConnectingCalls;
+            }
+            return mRingingCalls;
+        }
+        return mCallStateToCalls.get(call.getState());
+    }
+
+    private void removeCallFromAllBins(Call call) {
+        for (int i = 0; i < mCallStateToCalls.size(); i++) {
+            mCallStateToCalls.valueAt(i).remove(call);
+        }
+    }
+
     private void playToneForDisconnectedCall(Call call) {
         // If this call is being disconnected as a result of being handed over to another call,
         // we will not play a disconnect tone.
@@ -727,9 +727,11 @@
             Log.d(this, "Found a disconnected call with tone to play %d.", toneToPlay);
 
             if (toneToPlay != InCallTonePlayer.TONE_INVALID) {
-                mPlayerFactory.createPlayer(toneToPlay).startTone();
-                mCallsManager.onDisconnectedTonePlaying(true);
-                mIsDisconnectedTonePlaying = true;
+                boolean didToneStart = mPlayerFactory.createPlayer(toneToPlay).startTone();
+                if (didToneStart) {
+                    mCallsManager.onDisconnectedTonePlaying(true);
+                    mIsDisconnectedTonePlaying = true;
+                }
             }
         }
     }
diff --git a/src/com/android/server/telecom/CallAudioModeStateMachine.java b/src/com/android/server/telecom/CallAudioModeStateMachine.java
index 716f23a..42a1d36 100644
--- a/src/com/android/server/telecom/CallAudioModeStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioModeStateMachine.java
@@ -29,6 +29,13 @@
 import com.android.internal.util.StateMachine;
 
 public class CallAudioModeStateMachine extends StateMachine {
+    public static class Factory {
+        public CallAudioModeStateMachine create(SystemStateHelper systemStateHelper,
+                AudioManager am) {
+            return new CallAudioModeStateMachine(systemStateHelper, am);
+        }
+    }
+
     public static class MessageArgs {
         public boolean hasActiveOrDialingCalls;
         public boolean hasRingingCalls;
@@ -80,7 +87,6 @@
     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;
@@ -103,7 +109,6 @@
         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");
@@ -273,15 +278,6 @@
                             " 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.
-
-                    // The IMS audio routing may be via modem or via RTP stream. In case via RTP
-                    // stream, the state machine should transit to mVoipCallFocusState.
-                    transitionTo(args.foregroundCallIsVoip
-                            ? mVoipCallFocusState : mSimCallFocusState);
-                    return HANDLED;
                 case RINGER_MODE_CHANGE: {
                     Log.i(LOG_TAG, "RINGING state, received RINGER_MODE_CHANGE");
                     tryStartRinging();
@@ -338,7 +334,7 @@
                     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();
+                    mCallAudioManager.startCallWaiting("call already active");
                     return HANDLED;
                 case NEW_HOLDING_CALL:
                     // Don't do anything now. Putting an active call on hold will be handled when
@@ -393,7 +389,7 @@
                     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();
+                    mCallAudioManager.startCallWaiting("call already active");
                     return HANDLED;
                 case NEW_HOLDING_CALL:
                     // Don't do anything now. Putting an active call on hold will be handled when
@@ -447,8 +443,14 @@
                             ? mVoipCallFocusState : mSimCallFocusState);
                     return HANDLED;
                 case NEW_RINGING_CALL:
-                    // Apparently this is current behavior. Should this be the case?
-                    transitionTo(mRingingFocusState);
+                    // TODO: consider whether to move this into MessageArgs if more things start
+                    // to use it.
+                    if (args.hasHoldingCalls && mSystemStateHelper.isDeviceAtEar()) {
+                        mCallAudioManager.startCallWaiting(
+                                "Device is at ear with held call");
+                    } else {
+                        transitionTo(mRingingFocusState);
+                    }
                     return HANDLED;
                 case NEW_HOLDING_CALL:
                     // Do nothing.
@@ -475,14 +477,17 @@
     private final BaseState mOtherFocusState = new OtherFocusState();
 
     private final AudioManager mAudioManager;
+    private final SystemStateHelper mSystemStateHelper;
     private CallAudioManager mCallAudioManager;
 
     private int mMostRecentMode;
     private boolean mIsInitialized = false;
 
-    public CallAudioModeStateMachine(AudioManager audioManager) {
+    public CallAudioModeStateMachine(SystemStateHelper systemStateHelper,
+            AudioManager audioManager) {
         super(CallAudioModeStateMachine.class.getSimpleName());
         mAudioManager = audioManager;
+        mSystemStateHelper = systemStateHelper;
         mMostRecentMode = AudioManager.MODE_NORMAL;
 
         addState(mUnfocusedState);
diff --git a/src/com/android/server/telecom/CallAudioRoutePeripheralAdapter.java b/src/com/android/server/telecom/CallAudioRoutePeripheralAdapter.java
index a871dfc..2b6ba64 100644
--- a/src/com/android/server/telecom/CallAudioRoutePeripheralAdapter.java
+++ b/src/com/android/server/telecom/CallAudioRoutePeripheralAdapter.java
@@ -75,6 +75,12 @@
                 CallAudioRouteStateMachine.BT_AUDIO_DISCONNECTED);
     }
 
+    @Override
+    public void onUnexpectedBluetoothStateChange() {
+        mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+                CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE);
+    }
+
     /**
       * 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.
diff --git a/src/com/android/server/telecom/CallAudioRouteStateMachine.java b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
index 4274017..dcf1b27 100644
--- a/src/com/android/server/telecom/CallAudioRouteStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
@@ -71,6 +71,24 @@
  */
 public class CallAudioRouteStateMachine extends StateMachine {
 
+    public static class Factory {
+        public CallAudioRouteStateMachine create(
+                Context context,
+                CallsManager callsManager,
+                BluetoothRouteManager bluetoothManager,
+                WiredHeadsetManager wiredHeadsetManager,
+                StatusBarNotifier statusBarNotifier,
+                CallAudioManager.AudioServiceFactory audioServiceFactory,
+                int earpieceControl) {
+            return new CallAudioRouteStateMachine(context,
+                    callsManager,
+                    bluetoothManager,
+                    wiredHeadsetManager,
+                    statusBarNotifier,
+                    audioServiceFactory,
+                    earpieceControl);
+        }
+    }
     /** Values for CallAudioRouteStateMachine constructor's earPieceRouting arg. */
     public static final int EARPIECE_FORCE_DISABLED = 0;
     public static final int EARPIECE_FORCE_ENABLED  = 1;
@@ -136,7 +154,7 @@
     public static final int ACTIVE_FOCUS = 2;
     public static final int RINGING_FOCUS = 3;
 
-    /** Valid values for the argument for SWITCH_BASELINE_ROUTE */
+    /** Valid values for the first argument for SWITCH_BASELINE_ROUTE */
     public static final int NO_INCLUDE_BLUETOOTH_IN_BASELINE = 0;
     public static final int INCLUDE_BLUETOOTH_IN_BASELINE = 1;
 
@@ -290,6 +308,12 @@
                     mHasUserExplicitlyLeftBluetooth = false;
                     return NOT_HANDLED;
                 case SWITCH_FOCUS:
+                    // Perform BT hearing aid active device caching/restoration
+                    if (mAudioFocusType != NO_FOCUS && msg.arg1 == NO_FOCUS) {
+                        mBluetoothRouteManager.restoreHearingAidDevice();
+                    } else if (mAudioFocusType == NO_FOCUS && msg.arg1 != NO_FOCUS) {
+                        mBluetoothRouteManager.cacheHearingAidDevice();
+                    }
                     mAudioFocusType = msg.arg1;
                     return NOT_HANDLED;
                 default:
@@ -328,7 +352,6 @@
         public void enter() {
             super.enter();
             setSpeakerphoneOn(false);
-            setBluetoothOff();
             CallAudioState newState = new CallAudioState(mIsMuted, ROUTE_EARPIECE,
                     mAvailableRoutes, null,
                     mBluetoothRouteManager.getConnectedDevices());
@@ -523,7 +546,6 @@
         public void enter() {
             super.enter();
             setSpeakerphoneOn(false);
-            setBluetoothOff();
             CallAudioState newState = new CallAudioState(mIsMuted, ROUTE_WIRED_HEADSET,
                     mAvailableRoutes, null, mBluetoothRouteManager.getConnectedDevices());
             setSystemAudioState(newState, true);
@@ -742,6 +764,32 @@
         }
 
         @Override
+        public void handleBtInitiatedDisconnect() {
+            // There's special-case state transitioning here -- if BT tells us that
+            // something got disconnected, we don't want to disconnect BT before
+            // transitioning, since BT might be trying to connect another device in the
+            // meantime.
+            int command = calculateBaselineRouteMessage(false, false);
+            switch (command) {
+                case SWITCH_EARPIECE:
+                    transitionTo(mActiveEarpieceRoute);
+                    break;
+                case SWITCH_HEADSET:
+                    transitionTo(mActiveHeadsetRoute);
+                    break;
+                case SWITCH_SPEAKER:
+                    transitionTo(mActiveSpeakerRoute);
+                    break;
+                default:
+                    Log.w(this, "Got unexpected code " + command + " when processing a"
+                            + " BT-initiated audio disconnect");
+                    // Some fallback logic to make sure we make it off the bluetooth route.
+                    super.handleBtInitiatedDisconnect();
+                    break;
+            }
+        }
+
+        @Override
         public boolean processMessage(Message msg) {
             if (super.processMessage(msg) == HANDLED) {
                 return HANDLED;
@@ -752,6 +800,7 @@
                     // fall through
                 case SWITCH_EARPIECE:
                     if ((mAvailableRoutes & ROUTE_EARPIECE) != 0) {
+                        setBluetoothOff();
                         transitionTo(mActiveEarpieceRoute);
                     } else {
                         Log.w(this, "Ignoring switch to earpiece command. Not available.");
@@ -775,6 +824,7 @@
                     // fall through
                 case SWITCH_HEADSET:
                     if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) {
+                        setBluetoothOff();
                         transitionTo(mActiveHeadsetRoute);
                     } else {
                         Log.w(this, "Ignoring switch to headset command. Not available.");
@@ -784,11 +834,13 @@
                     mHasUserExplicitlyLeftBluetooth = true;
                     // fall through
                 case SWITCH_SPEAKER:
+                    setBluetoothOff();
                     transitionTo(mActiveSpeakerRoute);
                     return HANDLED;
                 case SWITCH_FOCUS:
                     if (msg.arg1 == NO_FOCUS) {
-                        setBluetoothOff();
+                        // Only disconnect SCO audio here instead of routing away from BT entirely.
+                        mBluetoothRouteManager.disconnectSco();
                         reinitialize();
                     } else if (msg.arg1 == RINGING_FOCUS
                             && !mBluetoothRouteManager.isInbandRingingEnabled()) {
@@ -797,7 +849,7 @@
                     }
                     return HANDLED;
                 case BT_AUDIO_DISCONNECTED:
-                    sendInternalMessage(SWITCH_BASELINE_ROUTE, NO_INCLUDE_BLUETOOTH_IN_BASELINE);
+                    handleBtInitiatedDisconnect();
                     return HANDLED;
                 default:
                     return NOT_HANDLED;
@@ -975,6 +1027,10 @@
             return CallAudioState.ROUTE_BLUETOOTH;
         }
 
+        public void handleBtInitiatedDisconnect() {
+            sendInternalMessage(SWITCH_BASELINE_ROUTE, NO_INCLUDE_BLUETOOTH_IN_BASELINE);
+        }
+
         @Override
         public boolean processMessage(Message msg) {
             if (super.processMessage(msg) == HANDLED) {
@@ -989,7 +1045,7 @@
                             + " have been null while we were in BT route.");
                     return HANDLED;
                 case BT_ACTIVE_DEVICE_GONE:
-                    sendInternalMessage(SWITCH_BASELINE_ROUTE, NO_INCLUDE_BLUETOOTH_IN_BASELINE);
+                    handleBtInitiatedDisconnect();
                     mWasOnSpeaker = false;
                     return HANDLED;
                 case DISCONNECT_WIRED_HEADSET:
@@ -1023,7 +1079,6 @@
             super.enter();
             mWasOnSpeaker = true;
             setSpeakerphoneOn(true);
-            setBluetoothOff();
             CallAudioState newState = new CallAudioState(mIsMuted, ROUTE_SPEAKER,
                     mAvailableRoutes, null, mBluetoothRouteManager.getConnectedDevices());
             setSystemAudioState(newState, true);
@@ -1250,7 +1305,7 @@
      */
     private int mDeviceSupportedRoutes;
     private int mAvailableRoutes;
-    private int mAudioFocusType;
+    private int mAudioFocusType = NO_FOCUS;
     private boolean mWasOnSpeaker;
     private boolean mIsMuted;
 
@@ -1417,6 +1472,7 @@
                 }
                 return;
             case UPDATE_SYSTEM_AUDIO_ROUTE:
+                updateInternalCallAudioState();
                 updateRouteForForegroundCall();
                 resendSystemAudioState();
                 return;
@@ -1606,7 +1662,8 @@
         int supportedRouteMask = calculateSupportedRoutes() & getCurrentCallSupportedRoutes();
         final int route;
 
-        if ((supportedRouteMask & ROUTE_BLUETOOTH) != 0) {
+        if ((supportedRouteMask & ROUTE_BLUETOOTH) != 0
+                && mBluetoothRouteManager.hasBtActiveDevice()) {
             route = ROUTE_BLUETOOTH;
         } else if ((supportedRouteMask & ROUTE_WIRED_HEADSET) != 0) {
             route = ROUTE_WIRED_HEADSET;
diff --git a/src/com/android/server/telecom/CallIntentProcessor.java b/src/com/android/server/telecom/CallIntentProcessor.java
index ff3b7b2..7bdc23d 100644
--- a/src/com/android/server/telecom/CallIntentProcessor.java
+++ b/src/com/android/server/telecom/CallIntentProcessor.java
@@ -11,6 +11,7 @@
 import android.os.UserManager;
 import android.telecom.DefaultDialerManager;
 import android.telecom.Log;
+import android.telecom.Logging.Session;
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
@@ -19,6 +20,8 @@
 import android.telephony.PhoneNumberUtils;
 import android.widget.Toast;
 
+import java.util.concurrent.CompletableFuture;
+
 /**
  * Single point of entry for all outgoing and incoming calls.
  * {@link com.android.server.telecom.components.UserCallIntentProcessor} serves as a trampoline that
@@ -28,7 +31,7 @@
 public class CallIntentProcessor {
     public interface Adapter {
         void processOutgoingCallIntent(Context context, CallsManager callsManager,
-                Intent intent);
+                Intent intent, String callingPackage);
         void processIncomingCallIntent(CallsManager callsManager, Intent intent);
         void processUnknownCallIntent(CallsManager callsManager, Intent intent);
     }
@@ -36,8 +39,9 @@
     public static class AdapterImpl implements Adapter {
         @Override
         public void processOutgoingCallIntent(Context context, CallsManager callsManager,
-                Intent intent) {
-            CallIntentProcessor.processOutgoingCallIntent(context, callsManager, intent);
+                Intent intent, String callingPackage) {
+            CallIntentProcessor.processOutgoingCallIntent(context, callsManager, intent,
+                    callingPackage);
         }
 
         @Override
@@ -73,7 +77,7 @@
         this.mCallsManager = callsManager;
     }
 
-    public void processIntent(Intent intent) {
+    public void processIntent(Intent intent, String callingPackage) {
         final boolean isUnknownCall = intent.getBooleanExtra(KEY_IS_UNKNOWN_CALL, false);
         Log.i(this, "onReceive - isUnknownCall: %s", isUnknownCall);
 
@@ -81,7 +85,7 @@
         if (isUnknownCall) {
             processUnknownCallIntent(mCallsManager, intent);
         } else {
-            processOutgoingCallIntent(mContext, mCallsManager, intent);
+            processOutgoingCallIntent(mContext, mCallsManager, intent, callingPackage);
         }
         Trace.endSection();
     }
@@ -91,11 +95,13 @@
      * Processes CALL, CALL_PRIVILEGED, and CALL_EMERGENCY intents.
      *
      * @param intent Call intent containing data about the handle to call.
+     * @param callingPackage The package which initiated the outgoing call (if known).
      */
     static void processOutgoingCallIntent(
             Context context,
             CallsManager callsManager,
-            Intent intent) {
+            Intent intent,
+            String callingPackage) {
 
         Uri handle = intent.getData();
         String scheme = handle.getScheme();
@@ -117,6 +123,12 @@
             clientExtras = new Bundle();
         }
 
+        if (intent.hasExtra(TelecomManager.EXTRA_IS_USER_INTENT_EMERGENCY_CALL)) {
+            clientExtras.putBoolean(TelecomManager.EXTRA_IS_USER_INTENT_EMERGENCY_CALL,
+                    intent.getBooleanExtra(TelecomManager.EXTRA_IS_USER_INTENT_EMERGENCY_CALL,
+                            false));
+        }
+
         // Ensure call subject is passed on to the connection service.
         if (intent.hasExtra(TelecomManager.EXTRA_CALL_SUBJECT)) {
             String callsubject = intent.getStringExtra(TelecomManager.EXTRA_CALL_SUBJECT);
@@ -143,13 +155,21 @@
         UserHandle initiatingUser = intent.getParcelableExtra(KEY_INITIATING_USER);
 
         // Send to CallsManager to ensure the InCallUI gets kicked off before the broadcast returns
-        Call call = callsManager
+        CompletableFuture<Call> callFuture = callsManager
                 .startOutgoingCall(handle, phoneAccountHandle, clientExtras, initiatingUser,
-                        intent);
+                        intent, callingPackage);
 
-        if (call != null) {
-            sendNewOutgoingCallIntent(context, call, callsManager, intent);
-        }
+        final Session logSubsession = Log.createSubsession();
+        callFuture.thenAccept((call) -> {
+            if (call != null) {
+                Log.continueSession(logSubsession, "CIP.sNOCI");
+                try {
+                    sendNewOutgoingCallIntent(context, call, callsManager, intent);
+                } finally {
+                    Log.endSession();
+                }
+            }
+        });
     }
 
     static void sendNewOutgoingCallIntent(Context context, Call call, CallsManager callsManager,
@@ -164,12 +184,15 @@
         NewOutgoingCallIntentBroadcaster broadcaster = new NewOutgoingCallIntentBroadcaster(
                 context, callsManager, call, intent, callsManager.getPhoneNumberUtilsAdapter(),
                 isPrivilegedDialer);
-        final int result = broadcaster.processIntent();
-        final boolean success = result == DisconnectCause.NOT_DISCONNECTED;
 
-        if (!success && call != null) {
-            disconnectCallAndShowErrorDialog(context, call, result);
+        // If the broadcaster comes back with an immediate error, disconnect and show a dialog.
+        NewOutgoingCallIntentBroadcaster.CallDisposition disposition = broadcaster.evaluateCall();
+        if (disposition.disconnectCause != DisconnectCause.NOT_DISCONNECTED) {
+            disconnectCallAndShowErrorDialog(context, call, disposition.disconnectCause);
+            return;
         }
+
+        broadcaster.processCall(disposition);
     }
 
     /**
diff --git a/src/com/android/server/telecom/CallLogManager.java b/src/com/android/server/telecom/CallLogManager.java
index 6e71b73..cfb5e80 100755
--- a/src/com/android/server/telecom/CallLogManager.java
+++ b/src/com/android/server/telecom/CallLogManager.java
@@ -27,6 +27,7 @@
 import android.os.UserHandle;
 import android.os.PersistableBundle;
 import android.provider.CallLog.Calls;
+import android.telecom.CallIdentification;
 import android.telecom.Connection;
 import android.telecom.DisconnectCause;
 import android.telecom.Log;
@@ -39,14 +40,11 @@
 // TODO: Needed for move to system service: import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.CallerInfo;
+import com.android.server.telecom.callfiltering.CallFilteringResult;
 
 import java.util.Arrays;
-import java.util.HashSet;
-import java.util.LinkedHashSet;
 import java.util.Locale;
 import java.util.Objects;
-import java.util.Set;
-import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 /**
@@ -83,7 +81,9 @@
                 String postDialDigits, String viaNumber, int presentation, int callType,
                 int features, PhoneAccountHandle accountHandle, long creationDate,
                 long durationInMillis, Long dataUsage, UserHandle initiatingUser, boolean isRead,
-                @Nullable LogCallCompletedListener logCallCompletedListener) {
+                @Nullable LogCallCompletedListener logCallCompletedListener, int callBlockReason,
+                CharSequence callScreeningAppName, String callScreeningComponentName,
+                CallIdentification callIdentification) {
             this.context = context;
             this.callerInfo = callerInfo;
             this.number = number;
@@ -99,6 +99,10 @@
             this.initiatingUser = initiatingUser;
             this.isRead = isRead;
             this.logCallCompletedListener = logCallCompletedListener;
+            this.callBockReason = callBlockReason;
+            this.callScreeningAppName = callScreeningAppName;
+            this.callScreeningComponentName = callScreeningComponentName;
+            this.callIdentification = callIdentification;
         }
         // Since the members are accessed directly, we don't use the
         // mXxxx notation.
@@ -119,6 +123,12 @@
 
         @Nullable
         public final LogCallCompletedListener logCallCompletedListener;
+
+        public final int callBockReason;
+        public final CharSequence callScreeningAppName;
+        public final String callScreeningComponentName;
+
+        public final CallIdentification callIdentification;
     }
 
     private static final String TAG = CallLogManager.class.getSimpleName();
@@ -151,22 +161,11 @@
                 newState == CallState.DISCONNECTED || newState == CallState.ABORTED;
         boolean isCallCanceled = isNewlyDisconnected && disconnectCause == DisconnectCause.CANCELED;
 
-        // Log newly disconnected calls only if:
-        // 1) It was not in the "choose account" phase when disconnected
-        // 2) It is a conference call
-        // 3) Call was not explicitly canceled
-        // 4) Call is not an external call
-        // 5) Call is not a self-managed call OR call is a self-managed call which has indicated it
-        //    should be logged in its PhoneAccount
-        if (isNewlyDisconnected &&
-                (oldState != CallState.SELECT_PHONE_ACCOUNT &&
-                        !call.isConference() &&
-                        !isCallCanceled) &&
-                !call.isExternalCall() &&
-                (!call.isSelfManaged() ||
-                        (call.isLoggedSelfManaged() &&
-                                (call.getHandoverState() == HandoverState.HANDOVER_NONE ||
-                                call.getHandoverState() == HandoverState.HANDOVER_COMPLETE)))) {
+        if (!isNewlyDisconnected) {
+            return;
+        }
+
+        if (shouldLogDisconnectedCall(call, oldState, isCallCanceled)) {
             int type;
             if (!call.isIncoming()) {
                 type = Calls.OUTGOING_TYPE;
@@ -182,22 +181,77 @@
             // Always show the notification for managed calls. For self-managed calls, it is up to
             // the app to show the notification, so suppress the notification when logging the call.
             boolean showNotification = !call.isSelfManaged();
-            logCall(call, type, showNotification);
+            logCall(call, type, showNotification, null /*result*/);
         }
     }
 
-    void logCall(Call call, int type, boolean showNotificationForMissedCall) {
-        if (type == Calls.MISSED_TYPE && showNotificationForMissedCall) {
-            logCall(call, Calls.MISSED_TYPE,
-                    new LogCallCompletedListener() {
-                        @Override
-                        public void onLogCompleted(@Nullable Uri uri) {
-                            mMissedCallNotifier.showMissedCallNotification(
-                                    new MissedCallNotifier.CallInfo(call));
-                        }
-                    });
+    /**
+     * Log newly disconnected calls only if all of below conditions are met:
+     * 1) Call was NOT in the "choose account" phase when disconnected
+     * 2) Call is NOT a conference call
+     * 3) Call is NOT simulating a single party conference.
+     * 4) Call was NOT explicitly canceled, except for disconnecting from a conference.
+     * 5) Call is NOT an external call
+     * 6) Call is NOT disconnected because of merging into a conference.
+     * 7) Call is NOT a self-managed call OR call is a self-managed call which has indicated it
+     *    should be logged in its PhoneAccount
+     */
+    private boolean shouldLogDisconnectedCall(Call call, int oldState, boolean isCallCanceled) {
+        // 1) "Choose account" phase when disconnected
+        if (oldState == CallState.SELECT_PHONE_ACCOUNT) {
+            return false;
+        }
+        // 2) A conference call
+        if (call.isConference()) {
+            return false;
+        }
+
+        DisconnectCause cause = call.getDisconnectCause();
+        if (isCallCanceled) {
+            // 3) No log when disconnecting to simulate a single party conference.
+            if (cause != null
+                    && DisconnectCause.REASON_EMULATING_SINGLE_CALL.equals(cause.getReason())) {
+                return false;
+            }
+            // 4) Explicitly canceled
+            // Conference children connections only have CAPABILITY_DISCONNECT_FROM_CONFERENCE.
+            // Log them when they are disconnected from conference.
+            return Connection.can(call.getConnectionCapabilities(),
+                    Connection.CAPABILITY_DISCONNECT_FROM_CONFERENCE);
+        }
+        // 5) An external call
+        if (call.isExternalCall()) {
+            return false;
+        }
+
+        // 6) Call merged into conferences.
+        if (cause != null && android.telephony.DisconnectCause.toString(
+                android.telephony.DisconnectCause.IMS_MERGED_SUCCESSFULLY)
+                .equals(cause.getReason())) {
+            return false;
+        }
+
+        boolean shouldCallSelfManagedLogged = call.isLoggedSelfManaged()
+                && (call.getHandoverState() == HandoverState.HANDOVER_NONE
+                || call.getHandoverState() == HandoverState.HANDOVER_COMPLETE);
+        // 7) Call is NOT a self-managed call OR call is a self-managed call which has indicated it
+        //    should be logged in its PhoneAccount
+        return !call.isSelfManaged() || shouldCallSelfManagedLogged;
+    }
+
+    void logCall(Call call, int type, boolean showNotificationForMissedCall, CallFilteringResult
+            result) {
+        if ((type == Calls.MISSED_TYPE || type == Calls.BLOCKED_TYPE) &&
+                showNotificationForMissedCall) {
+            logCall(call, type, new LogCallCompletedListener() {
+                @Override
+                public void onLogCompleted(@Nullable Uri uri) {
+                    mMissedCallNotifier.showMissedCallNotification(
+                            new MissedCallNotifier.CallInfo(call));
+                }
+            }, result);
         } else {
-            logCall(call, type, null);
+            logCall(call, type, null, result);
         }
     }
 
@@ -209,10 +263,13 @@
      *     {@link android.provider.CallLog.Calls#INCOMING_TYPE}
      *     {@link android.provider.CallLog.Calls#OUTGOING_TYPE}
      *     {@link android.provider.CallLog.Calls#MISSED_TYPE}
+     *     {@link android.provider.CallLog.Calls#BLOCKED_TYPE}
      * @param logCallCompletedListener optional callback called after the call is logged.
+     * @param result is generated when call type is
+     *     {@link android.provider.CallLog.Calls#BLOCKED_TYPE}.
      */
     void logCall(Call call, int callLogType,
-        @Nullable LogCallCompletedListener logCallCompletedListener) {
+        @Nullable LogCallCompletedListener logCallCompletedListener, CallFilteringResult result) {
         final long creationTime = call.getCreationTimeMillis();
         final long age = call.getAgeMillis();
 
@@ -242,10 +299,24 @@
                 (call.getConnectionProperties() & Connection.PROPERTY_ASSISTED_DIALING_USED) ==
                         Connection.PROPERTY_ASSISTED_DIALING_USED,
                 call.wasEverRttCall());
-        logCall(call.getCallerInfo(), logNumber, call.getPostDialDigits(), formattedViaNumber,
-                call.getHandlePresentation(), callLogType, callFeatures, accountHandle,
-                creationTime, age, callDataUsage, call.isEmergencyCall(), call.getInitiatingUser(),
-                call.isSelfManaged(), logCallCompletedListener);
+
+        CallIdentification callIdentification = call.getCallIdentification();
+
+        if (callLogType == Calls.BLOCKED_TYPE) {
+            logCall(call.getCallerInfo(), logNumber, call.getPostDialDigits(), formattedViaNumber,
+                    call.getHandlePresentation(), callLogType, callFeatures, accountHandle,
+                    creationTime, age, callDataUsage, call.isEmergencyCall(),
+                    call.getInitiatingUser(), call.isSelfManaged(), logCallCompletedListener,
+                    result.mCallBlockReason, result.mCallScreeningAppName,
+                    result.mCallScreeningComponentName, callIdentification);
+        } else {
+            logCall(call.getCallerInfo(), logNumber, call.getPostDialDigits(), formattedViaNumber,
+                    call.getHandlePresentation(), callLogType, callFeatures, accountHandle,
+                    creationTime, age, callDataUsage, call.isEmergencyCall(),
+                    call.getInitiatingUser(), call.isSelfManaged(), logCallCompletedListener,
+                    Calls.BLOCK_REASON_NOT_BLOCKED, null /*callScreeningAppName*/,
+                    null /*callScreeningComponentName*/, callIdentification);
+        }
     }
 
     /**
@@ -265,6 +336,11 @@
      * @param logCallCompletedListener optional callback called after the call is logged.
      * @param initiatingUser The user the call was initiated under.
      * @param isSelfManaged {@code true} if this is a self-managed call, {@code false} otherwise.
+     * @param callBlockReason The reason why the call is blocked.
+     * @param callScreeningAppName The call screening application name which block the call.
+     * @param callScreeningComponentName The call screening component name which block the call.
+     * @param callIdentification Call identification information, if provided by a call screening
+     *                           service.
      */
     private void logCall(
             CallerInfo callerInfo,
@@ -281,7 +357,11 @@
             boolean isEmergency,
             UserHandle initiatingUser,
             boolean isSelfManaged,
-            @Nullable LogCallCompletedListener logCallCompletedListener) {
+            @Nullable LogCallCompletedListener logCallCompletedListener,
+            int callBlockReason,
+            CharSequence callScreeningAppName,
+            String callScreeningComponentName,
+            @Nullable CallIdentification callIdentification) {
 
         // 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
@@ -314,7 +394,8 @@
             }
             AddCallArgs args = new AddCallArgs(mContext, callerInfo, number, postDialDigits,
                     viaNumber, presentation, callType, features, accountHandle, start, duration,
-                    dataUsage, initiatingUser, isRead, logCallCompletedListener);
+                    dataUsage, initiatingUser, isRead, logCallCompletedListener, callBlockReason,
+                    callScreeningAppName, callScreeningComponentName, callIdentification);
             logCallAsync(args);
         } else {
           Log.d(TAG, "Not adding emergency call to call log.");
@@ -475,7 +556,8 @@
             return Calls.addCall(c.callerInfo, c.context, c.number, c.postDialDigits, c.viaNumber,
                     c.presentation, c.callType, c.features, c.accountHandle, c.timestamp,
                     c.durationInSec, c.dataUsage, userToBeInserted == null,
-                    userToBeInserted, c.isRead);
+                    userToBeInserted, c.isRead, c.callBockReason, c.callScreeningAppName,
+                    c.callScreeningComponentName, c.callIdentification);
         }
 
 
diff --git a/src/com/android/server/telecom/CallScreeningServiceHelper.java b/src/com/android/server/telecom/CallScreeningServiceHelper.java
new file mode 100644
index 0000000..cb50b86
--- /dev/null
+++ b/src/com/android/server/telecom/CallScreeningServiceHelper.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2018 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.CallIdentification;
+import android.telecom.CallScreeningService;
+import android.telecom.Log;
+import android.telecom.Logging.Session;
+import android.text.TextUtils;
+
+import com.android.internal.telecom.ICallScreeningAdapter;
+import com.android.internal.telecom.ICallScreeningService;
+
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * Helper class for performing operations with {@link CallScreeningService}s.
+ */
+public class CallScreeningServiceHelper {
+    private static final String TAG = CallScreeningServiceHelper.class.getSimpleName();
+
+    /**
+     * Abstracts away dependency on the {@link PackageManager} required to fetch the label for an
+     * app.
+     */
+    public interface AppLabelProxy {
+        CharSequence getAppLabel(String packageName);
+    }
+
+    /**
+     * Implementation of {@link CallScreeningService} adapter AIDL; provides a means for responses
+     * from the call screening service to be handled.
+     */
+    private class CallScreeningAdapter extends ICallScreeningAdapter.Stub {
+        @Override
+        public void allowCall(String s) throws RemoteException {
+            // no-op; we don't allow this on outgoing calls.
+        }
+
+        @Override
+        public void disallowCall(String s, boolean b, boolean b1, boolean b2,
+                ComponentName componentName) throws RemoteException {
+            // no-op; we don't allow this on outgoing calls.
+        }
+
+        @Override
+        public void provideCallIdentification(String callId, CallIdentification callIdentification)
+                throws RemoteException {
+            Log.startSession("CSA.pCI");
+            long token = Binder.clearCallingIdentity();
+            try {
+                synchronized (mTelecomLock) {
+                    if (mCall != null && mCall.getId().equals(callId)) {
+                        Log.i(TAG, "provideCallIdentification - got call ID");
+                        callIdentification.setCallScreeningAppName(mAppLabelProxy.getAppLabel(
+                                mPackageName));
+                        callIdentification.setCallScreeningPackageName(mPackageName);
+                        mFuture.complete(callIdentification);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(token);
+                Log.endSession();
+            }
+            mFuture.complete(null);
+        }
+    }
+
+    private final ParcelableCallUtils.Converter mParcelableCallUtilsConverter;
+    private final TelecomSystem.SyncRoot mTelecomLock;
+    private final Call mCall;
+    private final UserHandle mUserHandle;
+    private final Context mContext;
+    private final AppLabelProxy mAppLabelProxy;
+    private final Session mLoggingSession;
+    private CompletableFuture<CallIdentification> mFuture;
+    private String mPackageName;
+
+    public CallScreeningServiceHelper(Context context, TelecomSystem.SyncRoot telecomLock,
+            String packageName, ParcelableCallUtils.Converter converter,
+            UserHandle userHandle, Call call, AppLabelProxy appLabelProxy) {
+        mContext = context;
+        mTelecomLock = telecomLock;
+        mParcelableCallUtilsConverter = converter;
+        mCall = call;
+        mUserHandle = userHandle;
+        mPackageName = packageName;
+        mAppLabelProxy = appLabelProxy;
+        mLoggingSession = Log.createSubsession();
+    }
+
+    /**
+     * Builds a {@link CompletableFuture} which performs a bind to a {@link CallScreeningService}
+     * @return
+     */
+    public CompletableFuture<CallIdentification> process() {
+        Log.d(this, "process");
+        return bindAndGetCallIdentification();
+    }
+
+    public CompletableFuture<CallIdentification> bindAndGetCallIdentification() {
+        Log.d(this, "bindAndGetCallIdentification");
+        if (mPackageName == null) {
+            return CompletableFuture.completedFuture(null);
+        }
+
+        mFuture = new CompletableFuture<>();
+
+        ServiceConnection serviceConnection = new ServiceConnection() {
+            @Override
+            public void onServiceConnected(ComponentName name, IBinder service) {
+                ICallScreeningService screeningService =
+                        ICallScreeningService.Stub.asInterface(service);
+                Log.continueSession(mLoggingSession, "CSSH.oSC");
+                try {
+                    try {
+                        screeningService.screenCall(new CallScreeningAdapter(),
+                                mParcelableCallUtilsConverter.toParcelableCallForScreening(mCall));
+                    } catch (RemoteException e) {
+                        Log.w(CallScreeningServiceHelper.this,
+                                "Cancelling call id due to remote exception");
+                        mFuture.complete(null);
+                    }
+                } finally {
+                    Log.endSession();
+                }
+            }
+
+            @Override
+            public void onServiceDisconnected(ComponentName name) {
+                // No locking needed -- CompletableFuture only lets one thread call complete.
+                Log.continueSession(mLoggingSession, "CSSH.oSD");
+                try {
+                    if (!mFuture.isDone()) {
+                        Log.w(CallScreeningServiceHelper.this,
+                                "Cancelling outgoing call screen due to service disconnect.");
+                    }
+                    mFuture.complete(null);
+                } finally {
+                    Log.endSession();
+                }
+            }
+        };
+
+        if (!bindCallScreeningService(mContext, mUserHandle, mPackageName, serviceConnection)) {
+            Log.i(this, "bindAndGetCallIdentification - bind failed");
+            Log.addEvent(mCall, LogUtils.Events.BIND_SCREENING, mPackageName);
+            mFuture.complete(null);
+        }
+
+        // Set up a timeout so that we're not waiting forever for the caller ID information.
+        Handler handler = new Handler();
+        handler.postDelayed(() -> {
+                    // No locking needed -- CompletableFuture only lets one thread call complete.
+                    Log.continueSession(mLoggingSession, "CSSH.timeout");
+                    try {
+                        if (!mFuture.isDone()) {
+                            Log.w(TAG, "Cancelling call id process due to timeout");
+                        }
+                        mFuture.complete(null);
+                    } finally {
+                        Log.endSession();
+                    }
+                },
+                Timeouts.getCallScreeningTimeoutMillis(mContext.getContentResolver()));
+        return mFuture;
+    }
+
+    /**
+     * Binds to a {@link CallScreeningService}.
+     * @param context The current context.
+     * @param userHandle User to bind as.
+     * @param packageName Package name of the {@link CallScreeningService}.
+     * @param serviceConnection The {@link ServiceConnection} to be notified of binding.
+     * @return {@code true} if binding succeeds, {@code false} otherwise.
+     */
+    public static boolean bindCallScreeningService(Context context, UserHandle userHandle,
+            String packageName, ServiceConnection serviceConnection) {
+        if (TextUtils.isEmpty(packageName)) {
+            Log.i(TAG, "PackageName is empty. Not performing call screening.");
+            return false;
+        }
+
+        Intent intent = new Intent(CallScreeningService.SERVICE_INTERFACE)
+                .setPackage(packageName);
+        List<ResolveInfo> entries = context.getPackageManager().queryIntentServicesAsUser(
+                intent, 0, userHandle.getIdentifier());
+        if (entries.isEmpty()) {
+            Log.i(TAG, packageName + " has no call screening service defined.");
+            return false;
+        }
+
+        ResolveInfo entry = entries.get(0);
+        if (entry.serviceInfo == null) {
+            Log.w(TAG, packageName + " call screening service has invalid service info");
+            return false;
+        }
+
+        if (entry.serviceInfo.permission == null || !entry.serviceInfo.permission.equals(
+                Manifest.permission.BIND_SCREENING_SERVICE)) {
+            Log.w(TAG, "CallScreeningService must require BIND_SCREENING_SERVICE permission: " +
+                    entry.serviceInfo.packageName);
+            return false;
+        }
+
+        ComponentName componentName =
+                new ComponentName(entry.serviceInfo.packageName, entry.serviceInfo.name);
+        intent.setComponent(componentName);
+        if (context.bindServiceAsUser(
+                intent,
+                serviceConnection,
+                Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
+                UserHandle.CURRENT)) {
+            Log.d(TAG, "bindService, found service, waiting for it to connect");
+            return true;
+        }
+
+        return false;
+    }
+}
diff --git a/src/com/android/server/telecom/CallState.java b/src/com/android/server/telecom/CallState.java
index 05ac38e..1e31732 100644
--- a/src/com/android/server/telecom/CallState.java
+++ b/src/com/android/server/telecom/CallState.java
@@ -114,6 +114,12 @@
      */
     public static final int PULLING = TelecomProtoEnums.PULLING; // = 10
 
+    /**
+     * Indicates that an incoming call has been answered by the in-call UI, but Telephony hasn't yet
+     * set the call to active.
+     */
+    public static final int ANSWERED = 11;
+
     public static String toString(int callState) {
         switch (callState) {
             case NEW:
@@ -138,6 +144,8 @@
                 return "DISCONNECTING";
             case PULLING:
                 return "PULLING";
+            case ANSWERED:
+                return "ANSWERED";
             default:
                 return "UNKNOWN";
         }
diff --git a/src/com/android/server/telecom/CallerInfoLookupHelper.java b/src/com/android/server/telecom/CallerInfoLookupHelper.java
index f67a7f7..a919921 100644
--- a/src/com/android/server/telecom/CallerInfoLookupHelper.java
+++ b/src/com/android/server/telecom/CallerInfoLookupHelper.java
@@ -27,6 +27,7 @@
 import android.telecom.Logging.Runnable;
 import android.telecom.Logging.Session;
 import android.text.TextUtils;
+import android.util.Pair;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.CallerInfo;
@@ -37,6 +38,7 @@
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.CompletableFuture;
 
 public class CallerInfoLookupHelper {
     public interface OnQueryCompleteListener {
@@ -77,6 +79,47 @@
         mLock = lock;
     }
 
+    /**
+     * Generates a CompletableFuture which performs a contacts lookup asynchronously.  The future
+     * returns a {@link Pair} containing the original handle which is being looked up and any
+     * {@link CallerInfo} which was found.
+     * @param handle
+     * @return {@link CompletableFuture} to perform the contacts lookup.
+     */
+    public CompletableFuture<Pair<Uri, CallerInfo>> startLookup(final Uri handle) {
+        // Create the returned future and
+        final CompletableFuture<Pair<Uri, CallerInfo>> callerInfoFuture = new CompletableFuture<>();
+
+        final String number = handle.getSchemeSpecificPart();
+        if (TextUtils.isEmpty(number)) {
+            // Nothing to do here, just finish.
+            Log.d(CallerInfoLookupHelper.this, "onCallerInfoQueryComplete - no number; end early");
+            callerInfoFuture.complete(new Pair<>(handle, null));
+            return callerInfoFuture;
+        }
+
+        // Setup a query complete listener which will get the results of the contacts lookup.
+        OnQueryCompleteListener listener = new OnQueryCompleteListener() {
+            @Override
+            public void onCallerInfoQueryComplete(Uri handle, CallerInfo info) {
+                Log.d(CallerInfoLookupHelper.this, "onCallerInfoQueryComplete - found info for %s",
+                        Log.piiHandle(handle));
+                // Got something, so complete the future.
+                callerInfoFuture.complete(new Pair<>(handle, info));
+            }
+
+            @Override
+            public void onContactPhotoQueryComplete(Uri handle, CallerInfo info) {
+                // No-op for now; not something this future cares about.
+            }
+        };
+
+        // Start async lookup.
+        startLookup(handle, listener);
+
+        return callerInfoFuture;
+    }
+
     public void startLookup(final Uri handle, OnQueryCompleteListener listener) {
         if (handle == null) {
             listener.onCallerInfoQueryComplete(handle, null);
@@ -108,8 +151,9 @@
                     Log.i(this, "There is a previously incomplete query for handle %s. Adding to " +
                             "listeners for this query.", Log.piiHandle(handle));
                     info.listeners.add(listener);
-                    return;
                 }
+                // Since we have a pending query for this handle already, don't re-query it.
+                return;
             } else {
                 CallerInfoQueryInfo info = new CallerInfoQueryInfo();
                 info.listeners.add(listener);
@@ -117,7 +161,7 @@
             }
         }
 
-        mHandler.post(new Runnable("CILH.sL", mLock) {
+        mHandler.post(new Runnable("CILH.sL", null) {
             @Override
             public void loggedRun() {
                 Session continuedSession = Log.createSubsession();
@@ -171,7 +215,7 @@
     }
 
     private void startPhotoLookup(final Uri handle, final Uri contactPhotoUri) {
-        mHandler.post(new Runnable("CILH.sPL", mLock) {
+        mHandler.post(new Runnable("CILH.sPL", null) {
             @Override
             public void loggedRun() {
                 Session continuedSession = Log.createSubsession();
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index ad7c03d..4b9e63c 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -16,16 +16,22 @@
 
 package com.android.server.telecom;
 
+import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.app.KeyguardManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
-import android.content.pm.UserInfo;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
 import android.media.AudioManager;
 import android.media.AudioSystem;
+import android.media.ToneGenerator;
+import android.media.MediaPlayer;
 import android.net.Uri;
+import android.os.AsyncTask;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
@@ -40,25 +46,30 @@
 import android.provider.CallLog.Calls;
 import android.provider.Settings;
 import android.telecom.CallAudioState;
+import android.telecom.CallIdentification;
 import android.telecom.Conference;
 import android.telecom.Connection;
 import android.telecom.DisconnectCause;
 import android.telecom.GatewayInfo;
 import android.telecom.Log;
+import android.telecom.Logging.Runnable;
+import android.telecom.Logging.Session;
 import android.telecom.ParcelableConference;
 import android.telecom.ParcelableConnection;
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
-import android.telecom.Logging.Runnable;
+import android.telecom.PhoneAccountSuggestion;
 import android.telecom.TelecomManager;
 import android.telecom.VideoProfile;
 import android.telephony.CarrierConfigManager;
 import android.telephony.PhoneNumberUtils;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
+import android.util.Pair;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.AsyncEmergencyContactNotifier;
+import com.android.internal.telephony.CallerInfo;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.TelephonyProperties;
 import com.android.internal.util.IndentingPrintWriter;
@@ -68,7 +79,7 @@
 import com.android.server.telecom.callfiltering.BlockCheckerAdapter;
 import com.android.server.telecom.callfiltering.CallFilterResultCallback;
 import com.android.server.telecom.callfiltering.CallFilteringResult;
-import com.android.server.telecom.callfiltering.CallScreeningServiceFilter;
+import com.android.server.telecom.callfiltering.CallScreeningServiceController;
 import com.android.server.telecom.callfiltering.DirectToVoicemailCallFilter;
 import com.android.server.telecom.callfiltering.IncomingCallFilter;
 import com.android.server.telecom.components.ErrorDialogActivity;
@@ -88,6 +99,7 @@
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -128,6 +140,8 @@
         void onHoldToneRequested(Call call);
         void onExternalCallChanged(Call call, boolean isExternalCall);
         void onDisconnectedTonePlaying(boolean isTonePlaying);
+        void onConnectionTimeChanged(Call call);
+        void onConferenceStateChanged(Call call, boolean isConference);
     }
 
     /** Interface used to define the action which is executed delay under some condition. */
@@ -191,12 +205,13 @@
      */
     public static final int[] ONGOING_CALL_STATES =
             {CallState.SELECT_PHONE_ACCOUNT, CallState.DIALING, CallState.PULLING, CallState.ACTIVE,
-                    CallState.ON_HOLD, CallState.RINGING};
+                    CallState.ON_HOLD, CallState.RINGING, CallState.ANSWERED};
 
     private static final int[] ANY_CALL_STATE =
             {CallState.NEW, CallState.CONNECTING, CallState.SELECT_PHONE_ACCOUNT, CallState.DIALING,
                     CallState.RINGING, CallState.ACTIVE, CallState.ON_HOLD, CallState.DISCONNECTED,
-                    CallState.ABORTED, CallState.DISCONNECTING, CallState.PULLING};
+                    CallState.ABORTED, CallState.DISCONNECTING, CallState.PULLING,
+                    CallState.ANSWERED};
 
     public static final String TELECOM_CALL_ID_PREFIX = "TC@";
 
@@ -225,9 +240,17 @@
 
     /**
      * A pending call is one which requires user-intervention in order to be placed.
-     * Used by {@link #startCallConfirmation(Call)}.
+     * Used by {@link #startCallConfirmation}.
      */
     private Call mPendingCall;
+    private CompletableFuture<Call> mPendingCallConfirm;
+    private CompletableFuture<Pair<Call, PhoneAccountHandle>> mPendingAccountSelection;
+
+    // Instance variables for testing -- we keep the latest copy of the outgoing call futures
+    // here so that we can wait on them in tests
+    private CompletableFuture<Call> mLatestPostSelectionProcessingFuture;
+    private CompletableFuture<Pair<Call, List<PhoneAccountSuggestion>>>
+            mLatestPreAccountSelectionFuture;
 
     /**
      * The current telecom call ID.  Used when creating new instances of {@link Call}.  Should
@@ -256,6 +279,7 @@
             new ConcurrentHashMap<CallsManagerListener, Boolean>(16, 0.9f, 1));
     private final HeadsetMediaButton mHeadsetMediaButton;
     private final WiredHeadsetManager mWiredHeadsetManager;
+    private final SystemStateHelper mSystemStateHelper;
     private final BluetoothRouteManager mBluetoothRouteManager;
     private final DockManager mDockManager;
     private final TtyManager mTtyManager;
@@ -264,8 +288,6 @@
     private final CallLogManager mCallLogManager;
     private final Context mContext;
     private final TelecomSystem.SyncRoot mLock;
-    private final ContactsAsyncHelper mContactsAsyncHelper;
-    private final CallerInfoAsyncQueryFactory mCallerInfoAsyncQueryFactory;
     private final PhoneAccountRegistrar mPhoneAccountRegistrar;
     private final MissedCallNotifier mMissedCallNotifier;
     private IncomingCallNotifier mIncomingCallNotifier;
@@ -280,6 +302,7 @@
     /* Handler tied to thread in which CallManager was initialized. */
     private final Handler mHandler = new Handler(Looper.getMainLooper());
     private final EmergencyCallHelper mEmergencyCallHelper;
+    private final RoleManagerAdapter mRoleManagerAdapter;
 
     private final ConnectionServiceFocusManager.CallsManagerRequester mRequester =
             new ConnectionServiceFocusManager.CallsManagerRequester() {
@@ -317,6 +340,12 @@
                                                PhoneAccountHandle handle) {
             broadcastUnregisterIntent(handle);
         }
+
+        @Override
+        public void onPhoneAccountChanged(PhoneAccountRegistrar registrar,
+                PhoneAccount phoneAccount) {
+            handlePhoneAccountChanged(registrar, phoneAccount);
+        }
     };
 
     /**
@@ -328,15 +357,33 @@
     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
+            Log.startSession("CM.CCCR");
             String action = intent.getAction();
             if (CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED.equals(action)
                     || SystemContract.ACTION_BLOCK_SUPPRESSION_STATE_CHANGED.equals(action)) {
-                BlockedNumbersUtil.updateEmergencyCallNotification(context,
-                        SystemContract.shouldShowEmergencyCallNotification(context));
-             }
+                new UpdateEmergencyCallNotificationTask().doInBackground(
+                        Pair.create(context, Log.createSubsession()));
+            }
         }
     };
 
+    private static class UpdateEmergencyCallNotificationTask
+            extends AsyncTask<Pair<Context, Session>, Void, Void> {
+        @SafeVarargs
+        @Override
+        protected final Void doInBackground(Pair<Context, Session>... args) {
+            if (args == null || args.length != 1 || args[0] == null) {
+                Log.e(this, new IllegalArgumentException(), "Incorrect invocation");
+                return null;
+            }
+            Log.continueSession(args[0].second, "CM.UECNT");
+            Context context = args[0].first;
+            BlockedNumbersUtil.updateEmergencyCallNotification(context,
+                    SystemContract.shouldShowEmergencyCallNotification(context));
+            return null;
+        }
+    }
+
     /**
      * Initializes the required Telecom components.
      */
@@ -344,8 +391,7 @@
     public CallsManager(
             Context context,
             TelecomSystem.SyncRoot lock,
-            ContactsAsyncHelper contactsAsyncHelper,
-            CallerInfoAsyncQueryFactory callerInfoAsyncQueryFactory,
+            CallerInfoLookupHelper callerInfoLookupHelper,
             MissedCallNotifier missedCallNotifier,
             PhoneAccountRegistrar phoneAccountRegistrar,
             HeadsetMediaButtonFactory headsetMediaButtonFactory,
@@ -356,7 +402,7 @@
             CallAudioManager.AudioServiceFactory audioServiceFactory,
             BluetoothRouteManager bluetoothManager,
             WiredHeadsetManager wiredHeadsetManager,
-            SystemStateProvider systemStateProvider,
+            SystemStateHelper systemStateHelper,
             DefaultDialerCache defaultDialerCache,
             Timeouts.Adapter timeoutsAdapter,
             AsyncRingtonePlayer asyncRingtonePlayer,
@@ -365,36 +411,38 @@
             InCallTonePlayer.ToneGeneratorFactory toneGeneratorFactory,
             ClockProxy clockProxy,
             BluetoothStateReceiver bluetoothStateReceiver,
-            InCallControllerFactory inCallControllerFactory) {
+            CallAudioRouteStateMachine.Factory callAudioRouteStateMachineFactory,
+            CallAudioModeStateMachine.Factory callAudioModeStateMachineFactory,
+            InCallControllerFactory inCallControllerFactory,
+            RoleManagerAdapter roleManagerAdapter) {
         mContext = context;
         mLock = lock;
         mPhoneNumberUtilsAdapter = phoneNumberUtilsAdapter;
-        mContactsAsyncHelper = contactsAsyncHelper;
-        mCallerInfoAsyncQueryFactory = callerInfoAsyncQueryFactory;
         mPhoneAccountRegistrar = phoneAccountRegistrar;
         mPhoneAccountRegistrar.addListener(mPhoneAccountListener);
         mMissedCallNotifier = missedCallNotifier;
         StatusBarNotifier statusBarNotifier = new StatusBarNotifier(context, this);
         mWiredHeadsetManager = wiredHeadsetManager;
+        mSystemStateHelper = systemStateHelper;
         mDefaultDialerCache = defaultDialerCache;
         mBluetoothRouteManager = bluetoothManager;
         mDockManager = new DockManager(context);
         mTimeoutsAdapter = timeoutsAdapter;
         mEmergencyCallHelper = emergencyCallHelper;
-        mCallerInfoLookupHelper = new CallerInfoLookupHelper(context, mCallerInfoAsyncQueryFactory,
-                mContactsAsyncHelper, mLock);
+        mCallerInfoLookupHelper = callerInfoLookupHelper;
 
         mDtmfLocalTonePlayer =
                 new DtmfLocalTonePlayer(new DtmfLocalTonePlayer.ToneGeneratorProxy());
-        CallAudioRouteStateMachine callAudioRouteStateMachine = new CallAudioRouteStateMachine(
-                context,
-                this,
-                bluetoothManager,
-                wiredHeadsetManager,
-                statusBarNotifier,
-                audioServiceFactory,
-                CallAudioRouteStateMachine.EARPIECE_AUTO_DETECT
-        );
+        CallAudioRouteStateMachine callAudioRouteStateMachine =
+                callAudioRouteStateMachineFactory.create(
+                        context,
+                        this,
+                        bluetoothManager,
+                        wiredHeadsetManager,
+                        statusBarNotifier,
+                        audioServiceFactory,
+                        CallAudioRouteStateMachine.EARPIECE_AUTO_DETECT
+                );
         callAudioRouteStateMachine.initialize();
 
         CallAudioRoutePeripheralAdapter callAudioRoutePeripheralAdapter =
@@ -404,27 +452,34 @@
                         wiredHeadsetManager,
                         mDockManager);
 
+        AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+        InCallTonePlayer.MediaPlayerFactory mediaPlayerFactory =
+                (resourceId, attributes) ->
+                        new InCallTonePlayer.MediaPlayerAdapterImpl(
+                                MediaPlayer.create(mContext, resourceId, attributes,
+                                        audioManager.generateAudioSessionId()));
         InCallTonePlayer.Factory playerFactory = new InCallTonePlayer.Factory(
-                callAudioRoutePeripheralAdapter, lock, toneGeneratorFactory);
+                callAudioRoutePeripheralAdapter, lock, toneGeneratorFactory, mediaPlayerFactory,
+                () -> audioManager.getStreamVolume(AudioManager.STREAM_RING) > 0);
 
         SystemSettingsUtil systemSettingsUtil = new SystemSettingsUtil();
         RingtoneFactory ringtoneFactory = new RingtoneFactory(this, context);
         SystemVibrator systemVibrator = new SystemVibrator(context);
         mInCallController = inCallControllerFactory.create(context, mLock, this,
-                systemStateProvider, defaultDialerCache, mTimeoutsAdapter,
+                systemStateHelper, defaultDialerCache, mTimeoutsAdapter,
                 emergencyCallHelper);
         mRinger = new Ringer(playerFactory, context, systemSettingsUtil, asyncRingtonePlayer,
-                ringtoneFactory, systemVibrator, mInCallController);
+                ringtoneFactory, systemVibrator,
+                new Ringer.VibrationEffectProxy(), mInCallController);
         mCallRecordingTonePlayer = new CallRecordingTonePlayer(mContext,
                 (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE), mLock);
         mCallAudioManager = new CallAudioManager(callAudioRouteStateMachine,
-                this,new CallAudioModeStateMachine((AudioManager)
-                        mContext.getSystemService(Context.AUDIO_SERVICE)),
+                this, callAudioModeStateMachineFactory.create(systemStateHelper,
+                (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE)),
                 playerFactory, mRinger, new RingbackPlayer(playerFactory),
                 bluetoothStateReceiver, mDtmfLocalTonePlayer);
 
-        mConnectionSvrFocusMgr = connectionServiceFocusManagerFactory.create(
-                mRequester, Looper.getMainLooper());
+        mConnectionSvrFocusMgr = connectionServiceFocusManagerFactory.create(mRequester);
         mHeadsetMediaButton = headsetMediaButtonFactory.create(context, this, mLock);
         mTtyManager = new TtyManager(context, mWiredHeadsetManager);
         mProximitySensorManager = proximitySensorManagerFactory.create(context, this);
@@ -434,6 +489,7 @@
                 new ConnectionServiceRepository(mPhoneAccountRegistrar, mContext, mLock, this);
         mInCallWakeLockController = inCallWakeLockControllerFactory.create(context, this);
         mClockProxy = clockProxy;
+        mRoleManagerAdapter = roleManagerAdapter;
 
         mListeners.add(mInCallWakeLockController);
         mListeners.add(statusBarNotifier);
@@ -483,6 +539,10 @@
         return mCallerInfoLookupHelper;
     }
 
+    public RoleManagerAdapter getRoleManagerAdapter() {
+        return mRoleManagerAdapter;
+    }
+
     @Override
     public void onSuccessfulOutgoingCall(Call call, int callState) {
         Log.v(this, "onSuccessfulOutgoingCall, %s", call);
@@ -512,18 +572,47 @@
     @Override
     public void onSuccessfulIncomingCall(Call incomingCall) {
         Log.d(this, "onSuccessfulIncomingCall");
-        if (incomingCall.hasProperty(Connection.PROPERTY_EMERGENCY_CALLBACK_MODE)) {
-            Log.i(this, "Skipping call filtering due to ECBM");
+        PhoneAccount phoneAccount = mPhoneAccountRegistrar.getPhoneAccountUnchecked(
+                incomingCall.getTargetPhoneAccount());
+        Bundle extras =
+            phoneAccount == null || phoneAccount.getExtras() == null
+                ? new Bundle()
+                : phoneAccount.getExtras();
+        if (incomingCall.hasProperty(Connection.PROPERTY_EMERGENCY_CALLBACK_MODE) ||
+                incomingCall.isSelfManaged() ||
+                extras.getBoolean(PhoneAccount.EXTRA_SKIP_CALL_FILTERING)) {
+            Log.i(this, "Skipping call filtering for %s (ecm=%b, selfMgd=%b, skipExtra=%b)",
+                    incomingCall.getId(),
+                    incomingCall.hasProperty(Connection.PROPERTY_EMERGENCY_CALLBACK_MODE),
+                    incomingCall.isSelfManaged(),
+                    extras.getBoolean(PhoneAccount.EXTRA_SKIP_CALL_FILTERING));
             onCallFilteringComplete(incomingCall, new CallFilteringResult(true, false, true, true));
+            incomingCall.setIsUsingCallFiltering(false);
             return;
         }
 
+        incomingCall.setIsUsingCallFiltering(true);
         List<IncomingCallFilter.CallFilter> filters = new ArrayList<>();
         filters.add(new DirectToVoicemailCallFilter(mCallerInfoLookupHelper));
         filters.add(new AsyncBlockCheckFilter(mContext, new BlockCheckerAdapter(),
-                mCallerInfoLookupHelper));
-        filters.add(new CallScreeningServiceFilter(mContext, this, mPhoneAccountRegistrar,
-                mDefaultDialerCache, new ParcelableCallUtils.Converter(), mLock));
+                mCallerInfoLookupHelper, null));
+        filters.add(new CallScreeningServiceController(mContext, this, mPhoneAccountRegistrar,
+                new ParcelableCallUtils.Converter(), mLock,
+                new TelecomServiceImpl.SettingsSecureAdapterImpl(), mCallerInfoLookupHelper,
+                new CallScreeningServiceHelper.AppLabelProxy() {
+                    @Override
+                    public CharSequence getAppLabel(String packageName) {
+                        PackageManager pm = mContext.getPackageManager();
+                        try {
+                            ApplicationInfo info = pm.getApplicationInfo(packageName, 0);
+                            return pm.getApplicationLabel(info);
+                        } catch (PackageManager.NameNotFoundException nnfe) {
+                            Log.w(this, "Could not determine package name.");
+                        }
+
+                        return null;
+                    }
+                }));
         new IncomingCallFilter(mContext, this, incomingCall, mLock,
                 mTimeoutsAdapter, filters).performFiltering();
     }
@@ -549,12 +638,17 @@
                 } else {
                     Log.i(this, "onCallFilteringCompleted: Call rejected! " +
                             "Exceeds maximum number of ringing calls.");
-                    rejectCallAndLog(incomingCall);
+                    rejectCallAndLog(incomingCall, result);
                 }
             } else if (hasMaximumManagedDialingCalls(incomingCall)) {
-                Log.i(this, "onCallFilteringCompleted: Call rejected! Exceeds maximum number of " +
-                        "dialing calls.");
-                rejectCallAndLog(incomingCall);
+                if (shouldSilenceInsteadOfReject(incomingCall)) {
+                    incomingCall.silence();
+                } else {
+
+                    Log.i(this, "onCallFilteringCompleted: Call rejected! Exceeds maximum number of " +
+                            "dialing calls.");
+                    rejectCallAndLog(incomingCall, result);
+                }
             } else {
                 addCall(incomingCall);
             }
@@ -568,8 +662,8 @@
                 if (result.shouldShowNotification) {
                     Log.w(this, "onCallScreeningCompleted: blocked call, showing notification.");
                 }
-                mCallLogManager.logCall(incomingCall, Calls.MISSED_TYPE,
-                        result.shouldShowNotification);
+                mCallLogManager.logCall(incomingCall, Calls.BLOCKED_TYPE,
+                        result.shouldShowNotification, result);
             } else if (result.shouldShowNotification) {
                 Log.i(this, "onCallScreeningCompleted: blocked call, showing notification.");
                 mMissedCallNotifier.showMissedCallNotification(
@@ -579,8 +673,10 @@
     }
 
     /**
-     * Whether allow (silence rather than reject) the incoming call if it has a different source
-     * (connection service) from the existing ringing call when reaching maximum ringing calls.
+     * In the event that the maximum supported calls of a given type is reached, the
+     * default behavior is to reject any additional calls of that type.  This checks
+     * if the device is configured to silence instead of reject the call, provided
+     * that the incoming call is from a different source (connection service).
      */
     private boolean shouldSilenceInsteadOfReject(Call incomingCall) {
         if (!mContext.getResources().getBoolean(
@@ -588,8 +684,6 @@
             return false;
         }
 
-        Call ringingCall = null;
-
         for (Call call : mCalls) {
             // Only operate on top-level calls
             if (call.getParentCall() != null) {
@@ -600,8 +694,7 @@
                 continue;
             }
 
-            if (CallState.RINGING == call.getState() &&
-                    call.getConnectionService() == incomingCall.getConnectionService()) {
+            if (call.getConnectionService() == incomingCall.getConnectionService()) {
                 return false;
             }
         }
@@ -696,6 +789,15 @@
     }
 
     @Override
+    public void onConferenceStateChanged(Call call, boolean isConference) {
+        // Conference changed whether it is treated as a conference or not.
+        updateCanAddCall();
+        for (CallsManagerListener listener : mListeners) {
+            listener.onConferenceStateChanged(call, isConference);
+        }
+    }
+
+    @Override
     public void onIsVoipAudioModeChanged(Call call) {
         for (CallsManagerListener listener : mListeners) {
             listener.onIsVoipAudioModeChanged(call);
@@ -829,6 +931,11 @@
     }
 
     @VisibleForTesting
+    public PhoneAccountRegistrar.Listener getPhoneAccountListener() {
+        return mPhoneAccountListener;
+    }
+
+    @VisibleForTesting
     public boolean hasEmergencyCall() {
         for (Call call : mCalls) {
             if (call.isEmergencyCall()) {
@@ -912,8 +1019,6 @@
                 this,
                 mLock,
                 mConnectionServiceRepository,
-                mContactsAsyncHelper,
-                mCallerInfoAsyncQueryFactory,
                 mPhoneNumberUtilsAdapter,
                 handle,
                 null /* gatewayInfo */,
@@ -1047,8 +1152,6 @@
                 this,
                 mLock,
                 mConnectionServiceRepository,
-                mContactsAsyncHelper,
-                mCallerInfoAsyncQueryFactory,
                 mPhoneNumberUtilsAdapter,
                 handle,
                 null /* gatewayInfo */,
@@ -1108,20 +1211,24 @@
      * For self-managed connections, we don't expect the Incall UI to launch, but this is still a
      * first step in getting the self-managed ConnectionService to create the connection.
      * @param handle Handle to connect the call with.
-     * @param phoneAccountHandle The phone account which contains the component name of the
+     * @param requestedAccountHandle 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.
      * @param originalIntent
+     * @param callingPackage the package name of the app which initiated the outgoing call.
      */
     @VisibleForTesting
-    public Call startOutgoingCall(Uri handle, PhoneAccountHandle phoneAccountHandle, Bundle extras,
-            UserHandle initiatingUser, Intent originalIntent) {
-        boolean isReusedCall = true;
+    public @NonNull
+    CompletableFuture<Call> startOutgoingCall(Uri handle,
+            PhoneAccountHandle requestedAccountHandle,
+            Bundle extras, UserHandle initiatingUser, Intent originalIntent,
+            String callingPackage) {
+        boolean isReusedCall;
         Call call = reuseOutgoingCall(handle);
 
         PhoneAccount account =
-                mPhoneAccountRegistrar.getPhoneAccount(phoneAccountHandle, initiatingUser);
+                mPhoneAccountRegistrar.getPhoneAccount(requestedAccountHandle, initiatingUser);
         boolean isSelfManaged = account != null && account.isSelfManaged();
 
         // Create a call with original handle. The handle may be changed when the call is attached
@@ -1131,18 +1238,16 @@
                     this,
                     mLock,
                     mConnectionServiceRepository,
-                    mContactsAsyncHelper,
-                    mCallerInfoAsyncQueryFactory,
                     mPhoneNumberUtilsAdapter,
                     handle,
                     null /* gatewayInfo */,
                     null /* connectionManagerPhoneAccount */,
-                    null /* phoneAccountHandle */,
+                    null /* requestedAccountHandle */,
                     Call.CALL_DIRECTION_OUTGOING /* callDirection */,
                     false /* forceAttachToExistingConnection */,
                     false, /* isConference */
                     mClockProxy);
-            call.initAnalytics();
+            call.initAnalytics(callingPackage);
 
             // Ensure new calls related to self-managed calls/connections are set as such.  This
             // will be overridden when the actual connection is returned in startCreateConnection,
@@ -1155,6 +1260,8 @@
             }
             call.setInitiatingUser(initiatingUser);
             isReusedCall = false;
+        } else {
+            isReusedCall = true;
         }
 
         int videoState = VideoProfile.STATE_AUDIO_ONLY;
@@ -1189,96 +1296,279 @@
             call.setVideoState(videoState);
         }
 
-        List<PhoneAccountHandle> potentialPhoneAccounts = findOutgoingCallPhoneAccount(
-                phoneAccountHandle, handle, VideoProfile.isVideo(videoState), initiatingUser);
-        if (potentialPhoneAccounts.size() == 1) {
-            phoneAccountHandle = potentialPhoneAccounts.get(0);
-        } else {
-            phoneAccountHandle = null;
+        final int finalVideoState = videoState;
+        final Call finalCall = call;
+        Handler outgoingCallHandler = new Handler(Looper.getMainLooper());
+        // Create a empty CompletableFuture and compose it with findOutgoingPhoneAccount to get
+        // a first guess at the list of suitable outgoing PhoneAccounts.
+        // findOutgoingPhoneAccount returns a CompletableFuture which is either already complete
+        // (in the case where we don't need to do the per-contact lookup) or a CompletableFuture
+        // that completes once the contact lookup via CallerInfoLookupHelper is complete.
+        CompletableFuture<List<PhoneAccountHandle>> accountsForCall =
+                CompletableFuture.completedFuture((Void) null).thenComposeAsync((x) ->
+                                findOutgoingCallPhoneAccount(requestedAccountHandle, handle,
+                                        VideoProfile.isVideo(finalVideoState), initiatingUser),
+                        new LoggedHandlerExecutor(outgoingCallHandler, "CM.fOCP", mLock));
+
+        // This is a block of code that executes after the list of potential phone accts has been
+        // retrieved.
+        CompletableFuture<List<PhoneAccountHandle>> setAccountHandle =
+                accountsForCall.whenCompleteAsync((potentialPhoneAccounts, exception) -> {
+                    Log.i(CallsManager.this, "set outgoing call phone acct stage");
+                    PhoneAccountHandle phoneAccountHandle;
+                    if (potentialPhoneAccounts.size() == 1) {
+                        phoneAccountHandle = potentialPhoneAccounts.get(0);
+                    } else {
+                        phoneAccountHandle = null;
+                    }
+                    finalCall.setTargetPhoneAccount(phoneAccountHandle);
+                }, new LoggedHandlerExecutor(outgoingCallHandler, "CM.sOCPA", mLock));
+
+
+        // This composes the future containing the potential phone accounts with code that queries
+        // the suggestion service if necessary (i.e. if the list is longer than 1).
+        // If the suggestion service is queried, the inner lambda will return a future that
+        // completes when the suggestion service calls the callback.
+        CompletableFuture<List<PhoneAccountSuggestion>> suggestionFuture = accountsForCall.
+                thenComposeAsync(potentialPhoneAccounts -> {
+                    Log.i(CallsManager.this, "call outgoing call suggestion service stage");
+                    if (potentialPhoneAccounts.size() == 1) {
+                        PhoneAccountSuggestion suggestion =
+                                new PhoneAccountSuggestion(potentialPhoneAccounts.get(0),
+                                        PhoneAccountSuggestion.REASON_NONE, true);
+                        return CompletableFuture.completedFuture(
+                                Collections.singletonList(suggestion));
+                    }
+                    return PhoneAccountSuggestionHelper.bindAndGetSuggestions(mContext,
+                            finalCall.getHandle(), potentialPhoneAccounts);
+                }, new LoggedHandlerExecutor(outgoingCallHandler, "CM.cOCSS", mLock));
+
+
+        // This future checks the status of existing calls and attempts to make room for the
+        // outgoing call. The future returned by the inner method will usually be pre-completed --
+        // we only pause here if user interaction is required to disconnect a self-managed call.
+        // It runs after the account handle is set, independently of the phone account suggestion
+        // future.
+        CompletableFuture<Call> makeRoomForCall = setAccountHandle.thenComposeAsync(
+                potentialPhoneAccounts -> {
+                    Log.i(CallsManager.this, "make room for outgoing call stage");
+                    boolean isPotentialInCallMMICode =
+                            isPotentialInCallMMICode(handle) && !isSelfManaged;
+                    // 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 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(finalCall, finalCall.isEmergencyCall()))) {
+                        Call foregroundCall = getForegroundCall();
+                        Log.d(CallsManager.this, "No more room for outgoing call %s ", finalCall);
+                        if (foregroundCall.isSelfManaged()) {
+                            // If the ongoing call is a self-managed call, then prompt the user to
+                            // ask if they'd like to disconnect their ongoing call and place the
+                            // outgoing call.
+                            Log.i(CallsManager.this, "Prompting user to disconnect "
+                                    + "self-managed call");
+                            finalCall.setOriginalCallIntent(originalIntent);
+                            CompletableFuture<Call> completionFuture = new CompletableFuture<>();
+                            startCallConfirmation(finalCall, completionFuture);
+                            return completionFuture;
+                        } else {
+                            // If the ongoing call is a managed call, we will prevent the outgoing
+                            // call from dialing.
+                            notifyCreateConnectionFailed(
+                                    finalCall.getTargetPhoneAccount(), finalCall);
+                        }
+                        Log.i(CallsManager.this, "Aborting call since there's no room");
+                        return CompletableFuture.completedFuture(null);
+                    }
+                    return CompletableFuture.completedFuture(finalCall);
+        }, new LoggedHandlerExecutor(outgoingCallHandler, "CM.dSMCP", mLock));
+
+        // The outgoing call can be placed, go forward. This future glues together the results of
+        // the account suggestion stage and the make room for call stage.
+        CompletableFuture<Pair<Call, List<PhoneAccountSuggestion>>> preSelectStage =
+                makeRoomForCall.thenCombine(suggestionFuture, Pair::create);
+        mLatestPreAccountSelectionFuture = preSelectStage;
+
+        // This future takes the list of suggested accounts and the call and determines if more
+        // user interaction in the form of a phone account selection screen is needed. If so, it
+        // will set the call to SELECT_PHONE_ACCOUNT, add it to our internal list/send it to dialer,
+        // and then execution will pause pending the dialer calling phoneAccountSelected.
+        CompletableFuture<Pair<Call, PhoneAccountHandle>> dialerSelectPhoneAccountFuture =
+                preSelectStage.thenComposeAsync(
+                        (args) -> {
+                            Log.i(CallsManager.this, "dialer phone acct select stage");
+                            Call callToPlace = args.first;
+                            List<PhoneAccountSuggestion> accountSuggestions = args.second;
+                            if (callToPlace == null) {
+                                return CompletableFuture.completedFuture(null);
+                            }
+                            if (accountSuggestions == null || accountSuggestions.isEmpty()) {
+                                Log.i(CallsManager.this, "Aborting call since there are no"
+                                        + " available accounts.");
+                                showErrorMessage(R.string.cant_call_due_to_no_supported_service);
+                                return CompletableFuture.completedFuture(null);
+                            }
+                            boolean needsAccountSelection = accountSuggestions.size() > 1
+                                    && !callToPlace.isEmergencyCall() && !isSelfManaged;
+                            if (!needsAccountSelection) {
+                                return CompletableFuture.completedFuture(Pair.create(callToPlace,
+                                        accountSuggestions.get(0).getPhoneAccountHandle()));
+                            }
+                            // This is the state where the user is expected to select an account
+                            callToPlace.setState(CallState.SELECT_PHONE_ACCOUNT,
+                                    "needs account selection");
+                            // Create our own instance to modify (since extras may be Bundle.EMPTY)
+                            Bundle newExtras = new Bundle(extras);
+                            List<PhoneAccountHandle> accountsFromSuggestions = accountSuggestions
+                                    .stream()
+                                    .map(PhoneAccountSuggestion::getPhoneAccountHandle)
+                                    .collect(Collectors.toList());
+                            newExtras.putParcelableList(
+                                    android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS,
+                                    accountsFromSuggestions);
+                            newExtras.putParcelableList(
+                                    android.telecom.Call.EXTRA_SUGGESTED_PHONE_ACCOUNTS,
+                                    accountSuggestions);
+                            // Set a future in place so that we can proceed once the dialer replies.
+                            mPendingAccountSelection = new CompletableFuture<>();
+                            callToPlace.setIntentExtras(newExtras);
+
+                            addCall(callToPlace);
+                            return mPendingAccountSelection;
+                        }, new LoggedHandlerExecutor(outgoingCallHandler, "CM.dSPA", mLock));
+
+        // Potentially perform call identification for dialed TEL scheme numbers.
+        if (PhoneAccount.SCHEME_TEL.equals(handle.getScheme())) {
+            // Perform an asynchronous contacts lookup in this stage; ensure post-dial digits are
+            // not included.
+            CompletableFuture<Pair<Uri, CallerInfo>> contactLookupFuture =
+                    mCallerInfoLookupHelper.startLookup(Uri.fromParts(handle.getScheme(),
+                            PhoneNumberUtils.extractNetworkPortion(handle.getSchemeSpecificPart()),
+                            null));
+
+            // Once the phone account selection stage has completed, we can handle the results from
+            // that with the contacts lookup in order to determine if we should lookup bind to the
+            // CallScreeningService in order for it to potentially provide caller ID.
+            dialerSelectPhoneAccountFuture.thenAcceptBothAsync(contactLookupFuture,
+                    (callPhoneAccountHandlePair, uriCallerInfoPair) -> {
+                        Call theCall = callPhoneAccountHandlePair.first;
+                        boolean isInContacts = uriCallerInfoPair.second != null
+                                && uriCallerInfoPair.second.contactExists;
+                        Log.d(CallsManager.this, "outgoingCallIdStage: isInContacts=%s",
+                                isInContacts);
+
+                        // We only want to provide a CallScreeningService with a call if its not in
+                        // contacts.
+                        if (!isInContacts) {
+                            performCallIdentification(theCall);
+                        }
+            }, new LoggedHandlerExecutor(outgoingCallHandler, "CM.pCSB", mLock));
         }
-        call.setTargetPhoneAccount(phoneAccountHandle);
 
-        boolean isPotentialInCallMMICode = isPotentialInCallMMICode(handle) && !isSelfManaged;
+        // Finally, after all user interaction is complete, we execute this code to finish setting
+        // up the outgoing call. The inner method always returns a completed future containing the
+        // call that we've finished setting up.
+        mLatestPostSelectionProcessingFuture = dialerSelectPhoneAccountFuture
+                .thenComposeAsync(args -> {
+                    if (args == null) {
+                        return CompletableFuture.completedFuture(null);
+                    }
+                    Log.i(CallsManager.this, "post acct selection stage");
+                    Call callToUse = args.first;
+                    PhoneAccountHandle phoneAccountHandle = args.second;
+                    PhoneAccount accountToUse = mPhoneAccountRegistrar
+                            .getPhoneAccount(phoneAccountHandle, initiatingUser);
+                    callToUse.setTargetPhoneAccount(phoneAccountHandle);
+                    if (accountToUse != null && accountToUse.getExtras() != null) {
+                        if (accountToUse.getExtras()
+                                .getBoolean(PhoneAccount.EXTRA_ALWAYS_USE_VOIP_AUDIO_MODE)) {
+                            Log.d(this, "startOutgoingCall: defaulting to voip mode for call %s",
+                                    callToUse.getId());
+                            callToUse.setIsVoipAudioMode(true);
+                        }
+                    }
 
-        // 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 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()))) {
-            Call foregroundCall = getForegroundCall();
-            Log.d(this, "No more room for outgoing call %s ", call);
-            if (foregroundCall.isSelfManaged()) {
-                // If the ongoing call is a self-managed call, then prompt the user to ask if they'd
-                // like to disconnect their ongoing call and place the outgoing call.
-                call.setOriginalCallIntent(originalIntent);
-                startCallConfirmation(call);
-            } else {
-                // If the ongoing call is a managed call, we will prevent the outgoing call from
-                // dialing.
-                notifyCreateConnectionFailed(call.getTargetPhoneAccount(), call);
+                    callToUse.setState(
+                            CallState.CONNECTING,
+                            phoneAccountHandle == null ? "no-handle"
+                                    : phoneAccountHandle.toString());
+
+                    boolean isVoicemail = isVoicemail(callToUse.getHandle(), accountToUse);
+
+                    if (!isVoicemail && (isRttSettingOn() || (extras != null
+                            && extras.getBoolean(TelecomManager.EXTRA_START_CALL_WITH_RTT,
+                            false)))) {
+                        Log.d(this, "Outgoing call requesting RTT, rtt setting is %b",
+                                isRttSettingOn());
+                        if (accountToUse != null
+                                && accountToUse.hasCapabilities(PhoneAccount.CAPABILITY_RTT)) {
+                            callToUse.createRttStreams();
+                        }
+                        // Even if the phone account doesn't support RTT yet,
+                        // the connection manager might change that. Set this to check it later.
+                        callToUse.setRequestedToStartWithRtt();
+                    }
+
+                    setIntentExtrasAndStartTime(callToUse, extras);
+                    setCallSourceToAnalytics(callToUse, originalIntent);
+
+                    if (isPotentialMMICode(handle) && !isSelfManaged) {
+                        // Do not add the call if it is a potential MMI code.
+                        callToUse.addListener(this);
+                    } else if (!mCalls.contains(callToUse)) {
+                        // We check if mCalls already contains the call because we could
+                        // potentially be reusing
+                        // a call which was previously added (See {@link #reuseOutgoingCall}).
+                        addCall(callToUse);
+                    }
+                    return CompletableFuture.completedFuture(callToUse);
+                }, new LoggedHandlerExecutor(outgoingCallHandler, "CM.pASP", mLock));
+        return mLatestPostSelectionProcessingFuture;
+    }
+
+    /**
+     * Performs call identification for an outgoing phone call.
+     * @param theCall The outgoing call to perform identification.
+     */
+    private void performCallIdentification(Call theCall) {
+        // Find the user chosen call screening app.
+        String callScreeningApp =
+                mRoleManagerAdapter.getDefaultCallScreeningApp();
+
+        CompletableFuture<CallIdentification> future =
+                new CallScreeningServiceHelper(mContext,
+                mLock,
+                callScreeningApp,
+                new ParcelableCallUtils.Converter(),
+                mCurrentUserHandle,
+                theCall,
+                new CallScreeningServiceHelper.AppLabelProxy() {
+                    @Override
+                    public CharSequence getAppLabel(String packageName) {
+                        PackageManager pm = mContext.getPackageManager();
+                        try {
+                            ApplicationInfo info = pm.getApplicationInfo(
+                                    packageName, 0);
+                            return pm.getApplicationLabel(info);
+                        } catch (PackageManager.NameNotFoundException nnfe) {
+                            Log.w(this, "Could not determine package name.");
+                        }
+
+                        return null;
+                    }
+                }).process();
+
+        // When we are done, apply call identification to the call.
+        future.thenApply(v -> {
+            Log.i(CallsManager.this, "setting caller ID: %s", v);
+            if (v != null) {
+                synchronized (mLock) {
+                    theCall.setCallIdentification(v);
+                }
             }
             return null;
-        }
-
-        // The outgoing call can be placed, go forward.
-
-        boolean needsAccountSelection =
-                phoneAccountHandle == null && potentialPhoneAccounts.size() > 1
-                        && !call.isEmergencyCall() && !isSelfManaged;
-        if (needsAccountSelection) {
-            // This is the state where the user is expected to select an account
-            call.setState(CallState.SELECT_PHONE_ACCOUNT, "needs account selection");
-            // Create our own instance to modify (since extras may be Bundle.EMPTY)
-            extras = new Bundle(extras);
-            extras.putParcelableList(android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS,
-                    potentialPhoneAccounts);
-        } else {
-            PhoneAccount accountToUse =
-                    mPhoneAccountRegistrar.getPhoneAccount(phoneAccountHandle, initiatingUser);
-            if (accountToUse != null && accountToUse.getExtras() != null) {
-                if (accountToUse.getExtras()
-                        .getBoolean(PhoneAccount.EXTRA_ALWAYS_USE_VOIP_AUDIO_MODE)) {
-                    Log.d(this, "startOutgoingCall: defaulting to voip mode for call %s",
-                            call.getId());
-                    call.setIsVoipAudioMode(true);
-                }
-            }
-
-            call.setState(
-                    CallState.CONNECTING,
-                    phoneAccountHandle == null ? "no-handle" : phoneAccountHandle.toString());
-
-            boolean isVoicemail = (call.getHandle() != null)
-                    && (PhoneAccount.SCHEME_VOICEMAIL.equals(call.getHandle().getScheme())
-                    || (accountToUse != null && mPhoneAccountRegistrar.isVoiceMailNumber(
-                    accountToUse.getAccountHandle(), call.getHandle().getSchemeSpecificPart())));
-
-            if (!isVoicemail && (isRttSettingOn() || (extras != null
-                    && extras.getBoolean(TelecomManager.EXTRA_START_CALL_WITH_RTT, false)))) {
-                Log.d(this, "Outgoing call requesting RTT, rtt setting is %b", isRttSettingOn());
-                if (accountToUse != null
-                        && accountToUse.hasCapabilities(PhoneAccount.CAPABILITY_RTT)) {
-                    call.createRttStreams();
-                }
-                // Even if the phone account doesn't support RTT yet, the connection manager might
-                // change that. Set this to check it later.
-                call.setRequestedToStartWithRtt();
-            }
-        }
-        setIntentExtrasAndStartTime(call, extras);
-        setCallSourceToAnalytics(call, originalIntent);
-
-        if ((isPotentialMMICode(handle) || isPotentialInCallMMICode) && !needsAccountSelection) {
-            // Do not add the call if it is a potential MMI code.
-            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 #reuseOutgoingCall}).
-            addCall(call);
-        }
-
-        return call;
+        });
     }
 
     /**
@@ -1302,55 +1592,80 @@
      * @return
      */
     @VisibleForTesting
-    public List<PhoneAccountHandle> findOutgoingCallPhoneAccount(
+    public CompletableFuture<List<PhoneAccountHandle>> findOutgoingCallPhoneAccount(
             PhoneAccountHandle targetPhoneAccountHandle, Uri handle, boolean isVideo,
             UserHandle initiatingUser) {
-        boolean isSelfManaged = isSelfManaged(targetPhoneAccountHandle, initiatingUser);
+        if (isSelfManaged(targetPhoneAccountHandle, initiatingUser)) {
+            return CompletableFuture.completedFuture(Arrays.asList(targetPhoneAccountHandle));
+        }
 
         List<PhoneAccountHandle> accounts;
-        if (!isSelfManaged) {
-            // Try to find a potential phone account, taking into account whether this is a video
-            // call.
-            accounts = constructPossiblePhoneAccounts(handle, initiatingUser, isVideo);
-            if (isVideo && accounts.size() == 0) {
-                // Placing a video call but no video capable accounts were found, so consider any
-                // call capable accounts (we can fallback to audio).
-                accounts = constructPossiblePhoneAccounts(handle, initiatingUser,
-                        false /* isVideo */);
-            }
-            Log.v(this, "findOutgoingCallPhoneAccount: accounts = " + accounts);
-
-            // Only dial with the requested phoneAccount if it is still valid. Otherwise treat this
-            // call as if a phoneAccount was not specified (does the default behavior instead).
-            // Note: We will not attempt to dial with a requested phoneAccount if it is disabled.
-            if (targetPhoneAccountHandle != null) {
-                if (!accounts.contains(targetPhoneAccountHandle)) {
-                    targetPhoneAccountHandle = null;
-                } else {
-                    // The target phone account is valid and was found.
-                    return Arrays.asList(targetPhoneAccountHandle);
-                }
-            }
-
-            if (targetPhoneAccountHandle == null && accounts.size() > 0) {
-                // No preset account, check if default exists that supports the URI scheme for the
-                // handle and verify it can be used.
-                if (accounts.size() > 1) {
-                    PhoneAccountHandle defaultPhoneAccountHandle =
-                            mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(
-                                    handle.getScheme(), initiatingUser);
-                    if (defaultPhoneAccountHandle != null &&
-                            accounts.contains(defaultPhoneAccountHandle)) {
-                        accounts.clear();
-                        accounts.add(defaultPhoneAccountHandle);
-                    }
-                }
-            }
-        } else {
-            // Self-managed ConnectionServices can only have a single potential account.
-            accounts = Arrays.asList(targetPhoneAccountHandle);
+        // Try to find a potential phone account, taking into account whether this is a video
+        // call.
+        accounts = constructPossiblePhoneAccounts(handle, initiatingUser, isVideo);
+        if (isVideo && accounts.size() == 0) {
+            // Placing a video call but no video capable accounts were found, so consider any
+            // call capable accounts (we can fallback to audio).
+            accounts = constructPossiblePhoneAccounts(handle, initiatingUser,
+                    false /* isVideo */);
         }
-        return accounts;
+        Log.v(this, "findOutgoingCallPhoneAccount: accounts = " + accounts);
+
+        // Only dial with the requested phoneAccount if it is still valid. Otherwise treat this
+        // call as if a phoneAccount was not specified (does the default behavior instead).
+        // Note: We will not attempt to dial with a requested phoneAccount if it is disabled.
+        if (targetPhoneAccountHandle != null) {
+            if (accounts.contains(targetPhoneAccountHandle)) {
+                // The target phone account is valid and was found.
+                return CompletableFuture.completedFuture(Arrays.asList(targetPhoneAccountHandle));
+            }
+        }
+        if (accounts.isEmpty() || accounts.size() == 1) {
+            return CompletableFuture.completedFuture(accounts);
+        }
+
+        // Do the query for whether there's a preferred contact
+        final CompletableFuture<PhoneAccountHandle> userPreferredAccountForContact =
+                new CompletableFuture<>();
+        final List<PhoneAccountHandle> possibleAccounts = accounts;
+        mCallerInfoLookupHelper.startLookup(handle,
+                new CallerInfoLookupHelper.OnQueryCompleteListener() {
+                    @Override
+                    public void onCallerInfoQueryComplete(Uri handle, CallerInfo info) {
+                        if (info.preferredPhoneAccountComponent != null &&
+                                info.preferredPhoneAccountId != null &&
+                                !info.preferredPhoneAccountId.isEmpty()) {
+                            PhoneAccountHandle contactDefaultHandle = new PhoneAccountHandle(
+                                    info.preferredPhoneAccountComponent,
+                                    info.preferredPhoneAccountId,
+                                    initiatingUser);
+                            userPreferredAccountForContact.complete(contactDefaultHandle);
+                        } else {
+                            userPreferredAccountForContact.complete(null);
+                        }
+                    }
+
+                    @Override
+                    public void onContactPhotoQueryComplete(Uri handle, CallerInfo info) {
+                        // ignore this
+                    }
+                });
+
+        return userPreferredAccountForContact.thenApply(phoneAccountHandle -> {
+            if (phoneAccountHandle != null) {
+                return Collections.singletonList(phoneAccountHandle);
+            }
+            // No preset account, check if default exists that supports the URI scheme for the
+            // handle and verify it can be used.
+            PhoneAccountHandle defaultPhoneAccountHandle =
+                    mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(
+                            handle.getScheme(), initiatingUser);
+            if (defaultPhoneAccountHandle != null &&
+                    possibleAccounts.contains(defaultPhoneAccountHandle)) {
+                return Collections.singletonList(defaultPhoneAccountHandle);
+            }
+            return possibleAccounts;
+        });
     }
 
     /**
@@ -1366,6 +1681,57 @@
         return targetPhoneAccount != null && targetPhoneAccount.isSelfManaged();
     }
 
+    public void onCallRedirectionComplete(Call call, Uri handle,
+                                          PhoneAccountHandle phoneAccountHandle,
+                                          GatewayInfo gatewayInfo, boolean speakerphoneOn,
+                                          int videoState, boolean shouldCancelCall,
+                                          String uiAction) {
+        Log.i(this, "onCallRedirectionComplete for Call %s with handle %s" +
+                        " and phoneAccountHandle %s", call, handle, phoneAccountHandle);
+
+        boolean endEarly = false;
+        String disconnectReason = "";
+
+        if (shouldCancelCall) {
+            Log.w(this, "onCallRedirectionComplete: call is canceled");
+            endEarly = true;
+            disconnectReason = "Canceled from Call Redirection Service";
+            // TODO show UI uiAction is CallRedirectionProcessor#UI_TYPE_USER_DEFINED_TIMEOUT
+        } else if (handle == null) {
+            Log.w(this, "onCallRedirectionComplete: handle is null");
+            endEarly = true;
+            disconnectReason = "Null handle from Call Redirection Service";
+        } else if (phoneAccountHandle == null) {
+            Log.w(this, "onCallRedirectionComplete: phoneAccountHandle is null");
+            endEarly = true;
+            disconnectReason = "Null phoneAccountHandle from Call Redirection Service";
+        } else if (mPhoneNumberUtilsAdapter.isPotentialLocalEmergencyNumber(mContext,
+                handle.getSchemeSpecificPart())) {
+            Log.w(this, "onCallRedirectionComplete: emergency number %s is redirected from Call"
+                    + " Redirection Service", handle.getSchemeSpecificPart());
+            endEarly = true;
+            disconnectReason = "Emergency number is redirected from Call Redirection Service";
+        }
+        if (endEarly) {
+            if (call != null) {
+                call.disconnect(disconnectReason);
+            }
+            return;
+        }
+
+        // If this call is already disconnected then we have nothing more to do.
+        if (call.isDisconnected()) {
+            Log.w(this, "onCallRedirectionComplete: Call has already been disconnected,"
+                    + " ignore the call redirection %s", call);
+            return;
+        }
+
+        // TODO show UI uiAction is CallRedirectionProcessor#UI_TYPE_USER_DEFINED_ASK_FOR_CONFIRM
+
+        call.setTargetPhoneAccount(phoneAccountHandle);
+        placeOutgoingCall(call, handle, gatewayInfo, speakerphoneOn, videoState);
+    }
+
     /**
      * Attempts to issue/connect the specified call.
      *
@@ -1609,6 +1975,15 @@
         } else {
             mLocallyDisconnectingCalls.add(call);
             call.disconnect();
+            // Cancel any of the outgoing call futures if they're still around.
+            if (mPendingCallConfirm != null && !mPendingCallConfirm.isDone()) {
+                mPendingCallConfirm.complete(null);
+                mPendingCallConfirm = null;
+            }
+            if (mPendingAccountSelection != null && !mPendingAccountSelection.isDone()) {
+                mPendingAccountSelection.complete(null);
+                mPendingAccountSelection = null;
+            }
         }
     }
 
@@ -1664,7 +2039,7 @@
         } else {
             Call activeCall = (Call) mConnectionSvrFocusMgr.getCurrentFocusCall();
             String activeCallId = null;
-            if (activeCall != null) {
+            if (activeCall != null && !activeCall.isLocallyDisconnecting()) {
                 activeCallId = activeCall.getId();
                 if (canHold(activeCall)) {
                     activeCall.hold("Swap to " + call.getId());
@@ -1830,51 +2205,15 @@
         if (!mCalls.contains(call)) {
             Log.i(this, "Attempted to add account to unknown call %s", call);
         } else {
-            call.setTargetPhoneAccount(account);
-            PhoneAccount realPhoneAccount =
-                    mPhoneAccountRegistrar.getPhoneAccountUnchecked(account);
-            if (realPhoneAccount != null && realPhoneAccount.getExtras() != null
-                    && realPhoneAccount.getExtras()
-                    .getBoolean(PhoneAccount.EXTRA_ALWAYS_USE_VOIP_AUDIO_MODE)) {
-                Log.d("phoneAccountSelected: default to voip mode for call %s", call.getId());
-                call.setIsVoipAudioMode(true);
-            }
-
-            boolean isVoicemail = (call.getHandle() != null)
-                    && (PhoneAccount.SCHEME_VOICEMAIL.equals(call.getHandle().getScheme())
-                    || (realPhoneAccount != null && mPhoneAccountRegistrar.isVoiceMailNumber(
-                    realPhoneAccount.getAccountHandle(),
-                    call.getHandle().getSchemeSpecificPart())));
-
-            if (!isVoicemail && (isRttSettingOn() || call.getIntentExtras()
-                    .getBoolean(TelecomManager.EXTRA_START_CALL_WITH_RTT, false))) {
-                Log.d(this, "Outgoing call after account selection requesting RTT," +
-                        " rtt setting is %b", isRttSettingOn());
-                if (realPhoneAccount != null
-                        && realPhoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_RTT)) {
-                    call.createRttStreams();
-                }
-                // Even if the phone account doesn't support RTT yet, the connection manager might
-                // change that. Set this to check it later.
-                call.setRequestedToStartWithRtt();
-            }
-
-            if (!call.isNewOutgoingCallIntentBroadcastDone()) {
-                return;
-            }
-
-            // Note: emergency calls never go through account selection dialog so they never
-            // arrive here.
-            if (makeRoomForOutgoingCall(call, false /* isEmergencyCall */)) {
-                call.startCreateConnection(mPhoneAccountRegistrar);
-            } else {
-                call.disconnect("no room");
-            }
-
             if (setDefault) {
                 mPhoneAccountRegistrar
                         .setUserSelectedOutgoingPhoneAccount(account, call.getInitiatingUser());
             }
+
+            if (mPendingAccountSelection != null) {
+                mPendingAccountSelection.complete(Pair.create(call, account));
+                mPendingAccountSelection = null;
+            }
         }
     }
 
@@ -2039,7 +2378,7 @@
      * indicate to the user that the call cannot me placed due to an ongoing call in another app.
      *
      * Used when there are ongoing self-managed calls and the user tries to make an outgoing managed
-     * call.  Called by {@link #startCallConfirmation(Call)} when the user is already confirming an
+     * call.  Called by {@link #startCallConfirmation} when the user is already confirming an
      * outgoing call.  Realistically this should almost never be called since in practice the user
      * won't make multiple outgoing calls at the same time.
      *
@@ -2074,7 +2413,8 @@
                 if (call.getConnectionService() == service) {
                     if (call.getState() != CallState.DISCONNECTED) {
                         markCallAsDisconnected(call, new DisconnectCause(DisconnectCause.ERROR,
-                                "CS_DEATH"));
+                                null /* message */, null /* description */, "CS_DEATH",
+                                ToneGenerator.TONE_PROP_PROMPT));
                     }
                     markCallAsRemoved(call);
                 }
@@ -2105,14 +2445,25 @@
     }
 
     boolean hasRingingCall() {
-        return getFirstCallWithState(CallState.RINGING) != null;
+        return getFirstCallWithState(CallState.RINGING, CallState.ANSWERED) != null;
     }
 
-    boolean onMediaButton(int type) {
+    @VisibleForTesting
+    public boolean onMediaButton(int type) {
         if (hasAnyCalls()) {
             Call ringingCall = getFirstCallWithState(CallState.RINGING);
             if (HeadsetMediaButton.SHORT_PRESS == type) {
                 if (ringingCall == null) {
+                    Call activeCall = getFirstCallWithState(CallState.ACTIVE);
+                    Call onHoldCall = getFirstCallWithState(CallState.ON_HOLD);
+                    if (activeCall != null && onHoldCall != null) {
+                        // Two calls, short-press -> switch calls
+                        Log.addEvent(onHoldCall, LogUtils.Events.INFO,
+                                "two calls, media btn short press - switch call.");
+                        unholdCall(onHoldCall);
+                        return true;
+                    }
+
                     Call callToHangup = getFirstCallWithState(CallState.RINGING, CallState.DIALING,
                             CallState.PULLING, CallState.ACTIVE, CallState.ON_HOLD);
                     Log.addEvent(callToHangup, LogUtils.Events.INFO,
@@ -2131,6 +2482,16 @@
                             LogUtils.Events.INFO, "media btn long press - reject");
                     ringingCall.reject(false, null);
                 } else {
+                    Call activeCall = getFirstCallWithState(CallState.ACTIVE);
+                    Call onHoldCall = getFirstCallWithState(CallState.ON_HOLD);
+                    if (activeCall != null && onHoldCall != null) {
+                        // Two calls, long-press -> end current call
+                        Log.addEvent(activeCall, LogUtils.Events.INFO,
+                                "two calls, media btn long press - end current call.");
+                        disconnectCall(activeCall);
+                        return true;
+                    }
+
                     Log.addEvent(getForegroundCall(), LogUtils.Events.INFO,
                             "media btn long press - mute");
                     mCallAudioManager.toggleMute();
@@ -2190,7 +2551,7 @@
 
     @VisibleForTesting
     public Call getRingingCall() {
-        return getFirstCallWithState(CallState.RINGING);
+        return getFirstCallWithState(CallState.RINGING, CallState.ANSWERED);
     }
 
     public Call getActiveCall() {
@@ -2209,6 +2570,7 @@
     public Call getHeldCallByConnectionService(ConnectionServiceWrapper connSvr) {
         Optional<Call> heldCall = mCalls.stream()
                 .filter(call -> call.getConnectionService() == connSvr
+                        && call.getParentCall() == null
                         && call.getState() == CallState.ON_HOLD)
                 .findFirst();
         return heldCall.isPresent() ? heldCall.get() : null;
@@ -2240,6 +2602,16 @@
         return mPhoneNumberUtilsAdapter;
     }
 
+    @VisibleForTesting
+    public CompletableFuture<Call> getLatestPostSelectionProcessingFuture() {
+        return mLatestPostSelectionProcessingFuture;
+    }
+
+    @VisibleForTesting
+    public CompletableFuture getLatestPreAccountSelectionFuture() {
+        return mLatestPreAccountSelectionFuture;
+    }
+
     /**
      * Returns the first call that it finds with the given states. The states are treated as having
      * priority order so that any call with the first state will be returned before any call with
@@ -2299,8 +2671,6 @@
                 this,
                 mLock,
                 mConnectionServiceRepository,
-                mContactsAsyncHelper,
-                mCallerInfoAsyncQueryFactory,
                 mPhoneNumberUtilsAdapter,
                 null /* handle */,
                 null /* gatewayInfo */,
@@ -2346,7 +2716,8 @@
      *
      * @return The {@link PhoneAccountRegistrar}.
      */
-    PhoneAccountRegistrar getPhoneAccountRegistrar() {
+    @VisibleForTesting
+    public PhoneAccountRegistrar getPhoneAccountRegistrar() {
         return mPhoneAccountRegistrar;
     }
 
@@ -2370,7 +2741,7 @@
      * 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) {
+    private void rejectCallAndLog(Call incomingCall, CallFilteringResult result) {
         if (incomingCall.getConnectionService() != null) {
             // Only reject the call if it has not already been destroyed.  If a call ends while
             // incoming call filtering is taking place, it is possible that the call has already
@@ -2385,7 +2756,7 @@
         // call notifier and the call logger manually.
         // Do we need missed call notification for direct to Voicemail calls?
         mCallLogManager.logCall(incomingCall, Calls.MISSED_TYPE,
-                true /*showNotificationForMissedCall*/);
+                true /*showNotificationForMissedCall*/, result);
     }
 
     /**
@@ -2480,27 +2851,31 @@
             // into a well-defined state machine.
             // TODO: Define expected state transitions here, and log when an
             // unexpected transition occurs.
-            call.setState(newState, tag);
-            maybeShowErrorDialogOnDisconnect(call);
+            if (call.setState(newState, tag)) {
+                maybeShowErrorDialogOnDisconnect(call);
 
-            Trace.beginSection("onCallStateChanged");
+                Trace.beginSection("onCallStateChanged");
 
-            maybeHandleHandover(call, newState);
+                maybeHandleHandover(call, newState);
 
-            // Only broadcast state change for calls that are being tracked.
-            if (mCalls.contains(call)) {
-                updateCanAddCall();
-                for (CallsManagerListener listener : mListeners) {
-                    if (LogUtils.SYSTRACE_DEBUG) {
-                        Trace.beginSection(listener.getClass().toString() + " onCallStateChanged");
-                    }
-                    listener.onCallStateChanged(call, oldState, newState);
-                    if (LogUtils.SYSTRACE_DEBUG) {
-                        Trace.endSection();
+                // Only broadcast state change for calls that are being tracked.
+                if (mCalls.contains(call)) {
+                    updateCanAddCall();
+                    for (CallsManagerListener listener : mListeners) {
+                        if (LogUtils.SYSTRACE_DEBUG) {
+                            Trace.beginSection(listener.getClass().toString() +
+                                    " onCallStateChanged");
+                        }
+                        listener.onCallStateChanged(call, oldState, newState);
+                        if (LogUtils.SYSTRACE_DEBUG) {
+                            Trace.endSection();
+                        }
                     }
                 }
+                Trace.endSection();
+            } else {
+                Log.i(this, "failed in setting the state to new state");
             }
-            Trace.endSection();
         }
     }
 
@@ -2746,13 +3121,13 @@
 
     private boolean hasMaximumManagedRingingCalls(Call exceptCall) {
         return MAXIMUM_RINGING_CALLS <= getNumCallsWithState(false /* isSelfManaged */, exceptCall,
-                null /* phoneAccountHandle */, CallState.RINGING);
+                null /* phoneAccountHandle */, CallState.RINGING, CallState.ANSWERED);
     }
 
     private boolean hasMaximumSelfManagedRingingCalls(Call exceptCall,
                                                       PhoneAccountHandle phoneAccountHandle) {
         return MAXIMUM_RINGING_CALLS <= getNumCallsWithState(true /* isSelfManaged */, exceptCall,
-                phoneAccountHandle, CallState.RINGING);
+                phoneAccountHandle, CallState.RINGING, CallState.ANSWERED);
     }
 
     private boolean hasMaximumOutgoingCalls(Call exceptCall) {
@@ -3011,8 +3386,6 @@
                 this,
                 mLock,
                 mConnectionServiceRepository,
-                mContactsAsyncHelper,
-                mCallerInfoAsyncQueryFactory,
                 mPhoneNumberUtilsAdapter,
                 connection.getHandle() /* handle */,
                 null /* gatewayInfo */,
@@ -3030,6 +3403,7 @@
 
         setCallState(call, Call.getStateFromConnectionState(connection.getState()),
                 "existing connection");
+        call.setVideoState(connection.getVideoState());
         call.setConnectionCapabilities(connection.getConnectionCapabilities());
         call.setConnectionProperties(connection.getConnectionProperties());
         call.setHandle(connection.getHandle(), connection.getHandlePresentation());
@@ -3118,6 +3492,7 @@
     public void onUserSwitch(UserHandle userHandle) {
         mCurrentUserHandle = userHandle;
         mMissedCallNotifier.setCurrentUserHandle(userHandle);
+        mRoleManagerAdapter.setCurrentUserHandle(userHandle);
         final UserManager userManager = UserManager.get(mContext);
         List<UserInfo> profiles = userManager.getEnabledProfiles(userHandle.getIdentifier());
         for (UserInfo profile : profiles) {
@@ -3139,6 +3514,14 @@
         return mLock;
     }
 
+    public Timeouts.Adapter getTimeoutsAdapter() {
+        return mTimeoutsAdapter;
+    }
+
+    public SystemStateHelper getSystemStateHelper() {
+        return mSystemStateHelper;
+    }
+
     private void reloadMissedCallsOfUser(UserHandle userHandle) {
         mMissedCallNotifier.reloadFromDatabase(mCallerInfoLookupHelper,
                 new MissedCallNotifier.CallInfoFactory(), userHandle);
@@ -3246,7 +3629,7 @@
 
     /**
      * Used to confirm creation of an outgoing call which was marked as pending confirmation in
-     * {@link #startOutgoingCall(Uri, PhoneAccountHandle, Bundle, UserHandle, Intent)}.
+     * {@link #startOutgoingCall(Uri, PhoneAccountHandle, Bundle, UserHandle, Intent, String)}.
      * Called via {@link TelecomBroadcastIntentProcessor} for a call which was confirmed via
      * {@link ConfirmCallDialogActivity}.
      * @param callId The call ID of the call to confirm.
@@ -3255,23 +3638,20 @@
         Log.i(this, "confirmPendingCall: callId=%s", callId);
         if (mPendingCall != null && mPendingCall.getId().equals(callId)) {
             Log.addEvent(mPendingCall, LogUtils.Events.USER_CONFIRMED);
-            addCall(mPendingCall);
 
             // We are going to place the new outgoing call, so disconnect any ongoing self-managed
             // calls which are ongoing at this time.
             disconnectSelfManagedCalls("outgoing call " + callId);
 
-            // Kick of the new outgoing call intent from where it left off prior to confirming the
-            // call.
-            CallIntentProcessor.sendNewOutgoingCallIntent(mContext, mPendingCall, this,
-                    mPendingCall.getOriginalCallIntent());
+            mPendingCallConfirm.complete(mPendingCall);
+            mPendingCallConfirm = null;
             mPendingCall = null;
         }
     }
 
     /**
      * Used to cancel an outgoing call which was marked as pending confirmation in
-     * {@link #startOutgoingCall(Uri, PhoneAccountHandle, Bundle, UserHandle, Intent)}.
+     * {@link #startOutgoingCall(Uri, PhoneAccountHandle, Bundle, UserHandle, Intent, String)}.
      * Called via {@link TelecomBroadcastIntentProcessor} for a call which was confirmed via
      * {@link ConfirmCallDialogActivity}.
      * @param callId The call ID of the call to cancel.
@@ -3283,25 +3663,29 @@
             markCallAsDisconnected(mPendingCall, new DisconnectCause(DisconnectCause.CANCELED));
             markCallAsRemoved(mPendingCall);
             mPendingCall = null;
+            mPendingCallConfirm.complete(null);
+            mPendingCallConfirm = null;
         }
     }
 
     /**
-     * Called from {@link #startOutgoingCall(Uri, PhoneAccountHandle, Bundle, UserHandle, Intent)} when
+     * Called from {@link #startOutgoingCall(Uri, PhoneAccountHandle, Bundle, UserHandle, Intent, String)} when
      * a managed call is added while there are ongoing self-managed calls.  Starts
      * {@link ConfirmCallDialogActivity} to prompt the user to see if they wish to place the
      * outgoing call or not.
      * @param call The call to confirm.
      */
-    private void startCallConfirmation(Call call) {
+    private void startCallConfirmation(Call call, CompletableFuture<Call> confirmationFuture) {
         if (mPendingCall != null) {
             Log.i(this, "startCallConfirmation: call %s is already pending; disconnecting %s",
                     mPendingCall.getId(), call.getId());
             markCallDisconnectedDueToSelfManagedCall(call);
+            confirmationFuture.complete(null);
             return;
         }
         Log.addEvent(call, LogUtils.Events.USER_CONFIRMATION);
         mPendingCall = call;
+        mPendingCallConfirm = confirmationFuture;
 
         // Figure out the name of the app in charge of the self-managed call(s).
         Call activeCall = (Call) mConnectionSvrFocusMgr.getCurrentFocusCall();
@@ -3392,6 +3776,14 @@
             mConnectionServiceRepository.dump(pw);
             pw.decreaseIndent();
         }
+
+        if (mRoleManagerAdapter != null && mRoleManagerAdapter instanceof RoleManagerAdapterImpl) {
+            RoleManagerAdapterImpl impl = (RoleManagerAdapterImpl) mRoleManagerAdapter;
+            pw.println("mRoleManager:");
+            pw.increaseIndent();
+            impl.dump(pw);
+            pw.decreaseIndent();
+        }
     }
 
     /**
@@ -3442,6 +3834,18 @@
         call.getAnalytics().setCallSource(callSource);
     }
 
+    private boolean isVoicemail(Uri callHandle, PhoneAccount phoneAccount) {
+        if (callHandle == null) {
+            return false;
+        }
+        if (PhoneAccount.SCHEME_VOICEMAIL.equals(callHandle.getScheme())) {
+            return true;
+        }
+        return phoneAccount != null && mPhoneAccountRegistrar.isVoiceMailNumber(
+                phoneAccount.getAccountHandle(),
+                callHandle.getSchemeSpecificPart());
+    }
+
     /**
      * Notifies the {@link android.telecom.ConnectionService} associated with a
      * {@link PhoneAccountHandle} that the attempt to create a new connection has failed.
@@ -3517,8 +3921,12 @@
         }
         extras.putParcelable(TelecomManager.EXTRA_CALL_AUDIO_STATE,
                 mCallAudioManager.getCallAudioState());
-        Call handoverToCall = startOutgoingCall(handoverFromCall.getHandle(), handoverToHandle,
-                extras, getCurrentUserHandle(), null /* originalIntent */);
+        Call handoverToCall = createHandoverCall(handoverFromCall.getHandle(), handoverToHandle,
+                extras, getCurrentUserHandle());
+        if (handoverToCall == null) {
+            handoverFromCall.sendCallEvent(android.telecom.Call.EVENT_HANDOVER_FAILED, null);
+            return;
+        }
         Log.addEvent(handoverFromCall, LogUtils.Events.START_HANDOVER,
                 "handOverFrom=%s, handOverTo=%s", handoverFromCall.getId(), handoverToCall.getId());
         handoverFromCall.setHandoverDestinationCall(handoverToCall);
@@ -3531,6 +3939,110 @@
                 videoState);
     }
 
+    public Call createHandoverCall(Uri handle, PhoneAccountHandle handoverToHandle,
+            Bundle extras, UserHandle initiatingUser) {
+        boolean isReusedCall = true;
+        Call call = reuseOutgoingCall(handle);
+
+        PhoneAccount account =
+                mPhoneAccountRegistrar.getPhoneAccount(handoverToHandle, initiatingUser);
+        boolean isSelfManaged = account != null && account.isSelfManaged();
+
+        // 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,
+                    mPhoneNumberUtilsAdapter,
+                    handle,
+                    null /* gatewayInfo */,
+                    null /* connectionManagerPhoneAccount */,
+                    null /* handoverToHandle */,
+                    Call.CALL_DIRECTION_OUTGOING /* callDirection */,
+                    false /* forceAttachToExistingConnection */,
+                    false, /* isConference */
+                    mClockProxy);
+            call.initAnalytics(null);
+
+            // Ensure new calls related to self-managed calls/connections are set as such.  This
+            // will be overridden when the actual connection is returned in startCreateConnection,
+            // however doing this now ensures the logs and any other logic will treat this call as
+            // self-managed from the moment it is created.
+            call.setIsSelfManaged(isSelfManaged);
+            if (isSelfManaged) {
+                // Self-managed calls will ALWAYS use voip audio mode.
+                call.setIsVoipAudioMode(true);
+            }
+            call.setInitiatingUser(initiatingUser);
+            isReusedCall = false;
+        }
+
+        int videoState = VideoProfile.STATE_AUDIO_ONLY;
+        if (extras != null) {
+            // Set the video state on the call early so that when it is added to the InCall UI the
+            // UI knows to configure itself as a video call immediately.
+            videoState = extras.getInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
+                    VideoProfile.STATE_AUDIO_ONLY);
+
+            // If this is an emergency video call, we need to check if the phone account supports
+            // emergency video calling.
+            // Also, ensure we don't try to place an outgoing call with video if video is not
+            // supported.
+            if (VideoProfile.isVideo(videoState)) {
+                if (call.isEmergencyCall() && account != null &&
+                        !account.hasCapabilities(PhoneAccount.CAPABILITY_EMERGENCY_VIDEO_CALLING)) {
+                    // Phone account doesn't support emergency video calling, so fallback to
+                    // audio-only now to prevent the InCall UI from setting up video surfaces
+                    // needlessly.
+                    Log.i(this, "startOutgoingCall - emergency video calls not supported; " +
+                            "falling back to audio-only");
+                    videoState = VideoProfile.STATE_AUDIO_ONLY;
+                } else if (account != null &&
+                        !account.hasCapabilities(PhoneAccount.CAPABILITY_VIDEO_CALLING)) {
+                    // Phone account doesn't support video calling, so fallback to audio-only.
+                    Log.i(this, "startOutgoingCall - video calls not supported; fallback to " +
+                            "audio-only.");
+                    videoState = VideoProfile.STATE_AUDIO_ONLY;
+                }
+            }
+
+            call.setVideoState(videoState);
+        }
+
+        call.setTargetPhoneAccount(handoverToHandle);
+
+        // If there's no more room for a handover, just fail.
+        if ((!isReusedCall && !makeRoomForOutgoingCall(call, call.isEmergencyCall()))) {
+            return null;
+        }
+
+        PhoneAccount accountToUse =
+                mPhoneAccountRegistrar.getPhoneAccount(handoverToHandle, initiatingUser);
+        if (accountToUse != null && accountToUse.getExtras() != null) {
+            if (accountToUse.getExtras()
+                    .getBoolean(PhoneAccount.EXTRA_ALWAYS_USE_VOIP_AUDIO_MODE)) {
+                Log.d(this, "startOutgoingCall: defaulting to voip mode for call %s",
+                        call.getId());
+                call.setIsVoipAudioMode(true);
+            }
+        }
+
+        call.setState(
+                CallState.CONNECTING,
+                handoverToHandle == null ? "no-handle" : handoverToHandle.toString());
+
+        setIntentExtrasAndStartTime(call, extras);
+
+        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 #reuseOutgoingCall}).
+            addCall(call);
+        }
+
+        return call;
+    }
     /**
      * Called in response to a {@link Call} receiving a {@link Call#handoverTo(PhoneAccountHandle,
      * int, Bundle)} indicating the {@link android.telecom.InCallService} has requested a
@@ -3573,7 +4085,7 @@
 
         Call call = new Call(getNextCallId(), mContext,
                 this, mLock, mConnectionServiceRepository,
-                mContactsAsyncHelper, mCallerInfoAsyncQueryFactory, mPhoneNumberUtilsAdapter,
+                mPhoneNumberUtilsAdapter,
                 handoverFromCall.getHandle(), null,
                 null, null,
                 Call.CALL_DIRECTION_OUTGOING, false,
@@ -3773,8 +4285,6 @@
                 this,
                 mLock,
                 mConnectionServiceRepository,
-                mContactsAsyncHelper,
-                mCallerInfoAsyncQueryFactory,
                 mPhoneNumberUtilsAdapter,
                 srcAddr,
                 null /* gatewayInfo */,
@@ -3839,7 +4349,7 @@
         call.startCreateConnection(mPhoneAccountRegistrar);
     }
 
-    ConnectionServiceFocusManager getConnectionServiceFocusManager() {
+    public ConnectionServiceFocusManager getConnectionServiceFocusManager() {
         return mConnectionSvrFocusMgr;
     }
 
@@ -3905,6 +4415,9 @@
             // We do not update the UI until we get confirmation of the answer() through
             // {@link #markCallAsActive}.
             mCall.answer(mVideoState);
+            if (mCall.getState() == CallState.RINGING) {
+                setCallState(mCall, CallState.ANSWERED, "answered");
+            }
             if (isSpeakerphoneAutoEnabledForVideoCalls(mVideoState)) {
                 mCall.setStartWithSpeakerphoneOn(true);
             }
@@ -3927,4 +4440,55 @@
             }
         }
     }
+
+    public void resetConnectionTime(Call call) {
+        call.setConnectTimeMillis(System.currentTimeMillis());
+        call.setConnectElapsedTimeMillis(SystemClock.elapsedRealtime());
+        if (mCalls.contains(call)) {
+            for (CallsManagerListener listener : mListeners) {
+                listener.onConnectionTimeChanged(call);
+            }
+        }
+    }
+
+    /**
+     * Determines if there is an ongoing emergency call. This can be either an outgoing emergency
+     * call, or a number which has been identified by the number as an emergency call.
+     * @return {@code true} if there is an ongoing emergency call, {@code false} otherwise.
+     */
+    public boolean isInEmergencyCall() {
+        return mCalls.stream().filter(c -> c.isEmergencyCall()
+                || c.isNetworkIdentifiedEmergencyCall()).count() > 0;
+    }
+
+    /**
+     * Trigger display of an error message to the user; we do this outside of dialer for calls which
+     * fail to be created and added to Dialer.
+     * @param messageId The string resource id.
+     */
+    private void showErrorMessage(int messageId) {
+        final Intent errorIntent = new Intent(mContext, ErrorDialogActivity.class);
+        errorIntent.putExtra(ErrorDialogActivity.ERROR_MESSAGE_ID_EXTRA, messageId);
+        errorIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        mContext.startActivityAsUser(errorIntent, UserHandle.CURRENT);
+    }
+
+    /**
+     * Handles changes to a {@link PhoneAccount}.
+     *
+     * Checks for changes to video calling availability and updates whether calls for that phone
+     * account are video capable.
+     *
+     * @param registrar The {@link PhoneAccountRegistrar} originating the change.
+     * @param phoneAccount The {@link PhoneAccount} which changed.
+     */
+    private void handlePhoneAccountChanged(PhoneAccountRegistrar registrar,
+            PhoneAccount phoneAccount) {
+        Log.i(this, "handlePhoneAccountChanged: phoneAccount=%s", phoneAccount);
+        boolean isVideoNowSupported = phoneAccount.hasCapabilities(
+                PhoneAccount.CAPABILITY_VIDEO_CALLING);
+        mCalls.stream()
+                .filter(c -> phoneAccount.getAccountHandle().equals(c.getTargetPhoneAccount()))
+                .forEach(c -> c.setVideoCallingSupportedByPhoneAccount(isVideoNowSupported));
+    }
 }
diff --git a/src/com/android/server/telecom/CallsManagerListenerBase.java b/src/com/android/server/telecom/CallsManagerListenerBase.java
index c0a71eb..4fa2ee5 100644
--- a/src/com/android/server/telecom/CallsManagerListenerBase.java
+++ b/src/com/android/server/telecom/CallsManagerListenerBase.java
@@ -92,4 +92,12 @@
     @Override
     public void onDisconnectedTonePlaying(boolean isTonePlaying) {
     }
+
+    @Override
+    public void onConnectionTimeChanged(Call call) {
+    }
+
+    @Override
+    public void onConferenceStateChanged(Call call, boolean isConference) {
+    }
 }
diff --git a/src/com/android/server/telecom/ConnectionServiceFocusManager.java b/src/com/android/server/telecom/ConnectionServiceFocusManager.java
index 92570a0..9c0bfa2 100644
--- a/src/com/android/server/telecom/ConnectionServiceFocusManager.java
+++ b/src/com/android/server/telecom/ConnectionServiceFocusManager.java
@@ -19,6 +19,7 @@
 import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Message;
 import android.telecom.Log;
@@ -30,14 +31,19 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
 public class ConnectionServiceFocusManager {
     private static final String TAG = "ConnectionSvrFocusMgr";
+    private static final int GET_CURRENT_FOCUS_TIMEOUT_MILLIS = 1000;
 
     /** Factory interface used to create the {@link ConnectionServiceFocusManager} instance. */
     public interface ConnectionServiceFocusManagerFactory {
-        ConnectionServiceFocusManager create(CallsManagerRequester requester, Looper looper);
+        ConnectionServiceFocusManager create(CallsManagerRequester requester);
     }
 
     /**
@@ -271,10 +277,12 @@
     private FocusManagerHandler mEventHandler;
 
     public ConnectionServiceFocusManager(
-            CallsManagerRequester callsManagerRequester, Looper looper) {
+            CallsManagerRequester callsManagerRequester) {
         mCallsManagerRequester = callsManagerRequester;
         mCallsManagerRequester.setCallsManagerListener(mCallsManagerListener);
-        mEventHandler = new FocusManagerHandler(looper);
+        HandlerThread handlerThread = new HandlerThread(TAG);
+        handlerThread.start();
+        mEventHandler = new FocusManagerHandler(handlerThread.getLooper());
         mCalls = new ArrayList<>();
     }
 
@@ -298,8 +306,32 @@
      * call is the current connection service focus. Also the state of the focus call must be one
      * of {@link #PRIORITY_FOCUS_CALL_STATE}.
      */
-    public CallFocus getCurrentFocusCall() {
-        return mCurrentFocusCall;
+    public @Nullable CallFocus getCurrentFocusCall() {
+        if (mEventHandler.getLooper().isCurrentThread()) {
+            // return synchronously if we're on the same thread.
+            return mCurrentFocusCall;
+        }
+        final BlockingQueue<Optional<CallFocus>> currentFocusedCallQueue =
+                new LinkedBlockingQueue<>(1);
+        mEventHandler.post(() -> {
+            currentFocusedCallQueue.offer(
+                    mCurrentFocusCall == null ? Optional.empty() : Optional.of(mCurrentFocusCall));
+        });
+        try {
+            Optional<CallFocus> syncCallFocus = currentFocusedCallQueue.poll(
+                    GET_CURRENT_FOCUS_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+            if (syncCallFocus != null) {
+                return syncCallFocus.orElse(null);
+            } else {
+                Log.w(TAG, "Timed out waiting for synchronous current focus. Returning possibly"
+                        + " inaccurate result");
+                return mCurrentFocusCall;
+            }
+        } catch (InterruptedException e) {
+            Log.w(TAG, "Interrupted when waiting for synchronous current focus."
+                    + " Returning possibly inaccurate result.");
+            return mCurrentFocusCall;
+        }
     }
 
     /** Returns the current connection service focus. */
diff --git a/src/com/android/server/telecom/ConnectionServiceWrapper.java b/src/com/android/server/telecom/ConnectionServiceWrapper.java
index 6dd9a3a..eb2e233 100644
--- a/src/com/android/server/telecom/ConnectionServiceWrapper.java
+++ b/src/com/android/server/telecom/ConnectionServiceWrapper.java
@@ -145,6 +145,26 @@
         }
 
         @Override
+        public void resetConnectionTime(String callId, Session.Info sessionInfo) {
+            Log.startSession(sessionInfo, "CSW.rCCT");
+            long token = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    logIncoming("resetConnectionTime %s", callId);
+                    Call call = mCallIdMapper.getCall(callId);
+                    if (call != null) {
+                        mCallsManager.resetConnectionTime(call);
+                    } else {
+                        // Log.w(this, "resetConnectionTime, unknown call id: %s", msg.obj);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(token);
+                Log.endSession();
+            }
+        }
+
+        @Override
         public void setVideoProvider(String callId, IVideoProvider videoProvider,
                 Session.Info sessionInfo) {
             Log.startSession(sessionInfo, "CSW.sVP");
@@ -970,6 +990,27 @@
                 Log.endSession();
             }
         }
+
+        @Override
+        public void setConferenceState(String callId, boolean isConference,
+                Session.Info sessionInfo) throws RemoteException {
+            Log.startSession(sessionInfo, "CSW.sCS");
+            long token = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    Call call = mCallIdMapper.getCall(callId);
+                    if (call != null) {
+                        call.setConferenceState(isConference);
+                    }
+                }
+            } catch (Throwable t) {
+                Log.e(ConnectionServiceWrapper.this, t, "");
+                throw t;
+            } finally {
+                Binder.restoreCallingIdentity(token);
+                Log.endSession();
+            }
+        }
     }
 
     private final Adapter mAdapter = new Adapter();
@@ -1621,6 +1662,14 @@
         if (connection.getState() == Connection.STATE_DISCONNECTED) {
             // A connection that begins in the DISCONNECTED state is an indication of
             // failure to connect; we handle all failures uniformly
+            Call foundCall = mCallIdMapper.getCall(callId);
+            if (foundCall != null) {
+                // The post-dial digits are created when the call is first created.  Normally
+                // the ConnectionService is responsible for stripping them from the address, but
+                // since a failed connection will not have done this, we could end up with duplicate
+                // post-dial digits.
+                foundCall.clearPostDialDigits();
+            }
             removeCall(callId, connection.getDisconnectCause());
         } else {
             // Successful connection
diff --git a/src/com/android/server/telecom/CreateConnectionProcessor.java b/src/com/android/server/telecom/CreateConnectionProcessor.java
index 7eb3801..d3c74ec 100644
--- a/src/com/android/server/telecom/CreateConnectionProcessor.java
+++ b/src/com/android/server/telecom/CreateConnectionProcessor.java
@@ -311,6 +311,38 @@
         }
     }
 
+    // This function is used after previous attempts to find emergency PSTN connections
+    // do not find any SIM phone accounts with emergency capability.
+    // It attempts to add any accounts with CAPABILITY_PLACE_EMERGENCY_CALLS even if
+    // accounts are not SIM accounts.
+    private void adjustAttemptsForEmergencyNoSimRequired(
+            PhoneAccountHandle preferredPAH,
+            List<PhoneAccount> allAccounts) {
+        // First, possibly add the phone account that the user prefers.
+        PhoneAccount preferredPA = mPhoneAccountRegistrar.getPhoneAccountUnchecked(preferredPAH);
+        if (preferredPA != null
+                && preferredPA.hasCapabilities(PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS)) {
+            Log.i(this, "Will try account %s for emergency", preferredPA.getAccountHandle());
+            mAttemptRecords.add(new CallAttemptRecord(preferredPAH, preferredPAH));
+        }
+
+        // Next, add all phone accounts which can place emergency calls.
+        // If preferredPA already has an emergency PhoneAccount, do not add others since the
+        // emergency call be redialed in Telephony.
+        if (mAttemptRecords.isEmpty()) {
+            for (PhoneAccount phoneAccount : allAccounts) {
+                if (phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS)) {
+                    PhoneAccountHandle phoneAccountHandle = phoneAccount.getAccountHandle();
+                    Log.i(this, "Will try account %s for emergency", phoneAccountHandle);
+                    mAttemptRecords.add(
+                            new CallAttemptRecord(phoneAccountHandle, phoneAccountHandle));
+                    // Add only one emergency PhoneAccount to the attempt list.
+                    break;
+                }
+            }
+        }
+    }
+
     // 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(PhoneAccountHandle preferredPAH) {
@@ -383,6 +415,12 @@
                     }
                 }
             }
+
+            if (mAttemptRecords.isEmpty()) {
+                // Last best-effort attempt: choose any account with emergency capability even without
+                // sim capability.
+                adjustAttemptsForEmergencyNoSimRequired(preferredPAH, allAccounts);
+            }
         }
     }
 
diff --git a/src/com/android/server/telecom/DialerCodeReceiver.java b/src/com/android/server/telecom/DialerCodeReceiver.java
index 57f84a0..1cd922a 100644
--- a/src/com/android/server/telecom/DialerCodeReceiver.java
+++ b/src/com/android/server/telecom/DialerCodeReceiver.java
@@ -19,9 +19,12 @@
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
+import android.os.UserHandle;
 import android.telecom.Log;
 import android.telecom.TelecomManager;
 
+import com.android.server.telecom.ui.TelecomDeveloperMenu;
+
 /**
  * Receiver for "secret codes" broadcast by Dialer.
  */
@@ -38,6 +41,9 @@
     // Writes a MARK to the Telecom log.
     public static final String TELECOM_SECRET_CODE_MARK = "826275";
 
+    // Opens the Telecom developer menu.
+    public static final String TELECOM_SECRET_CODE_MENU = "828282";
+
     private final CallsManager mCallsManager;
 
     DialerCodeReceiver(CallsManager callsManager) {
@@ -61,6 +67,11 @@
                 // add a non-call event.
                 Call currentCall = mCallsManager.getActiveCall();
                 Log.addEvent(currentCall, LogUtils.Events.USER_LOG_MARK);
+            } else if (intent.getData().getHost().equals(TELECOM_SECRET_CODE_MENU)) {
+                Log.i("DialerCodeReceiver", "Secret code used to open developer menu.");
+                Intent confirmIntent = new Intent(context, TelecomDeveloperMenu.class);
+                confirmIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                context.startActivityAsUser(confirmIntent, UserHandle.CURRENT);
             }
         }
     }
diff --git a/src/com/android/server/telecom/HeadsetMediaButton.java b/src/com/android/server/telecom/HeadsetMediaButton.java
index ec77289..ad95c34 100644
--- a/src/com/android/server/telecom/HeadsetMediaButton.java
+++ b/src/com/android/server/telecom/HeadsetMediaButton.java
@@ -26,14 +26,18 @@
 import android.telecom.Log;
 import android.view.KeyEvent;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 /**
  * Static class to handle listening to the headset media buttons.
  */
 public class HeadsetMediaButton extends CallsManagerListenerBase {
 
     // Types of media button presses
-    static final int SHORT_PRESS = 1;
-    static final int LONG_PRESS = 2;
+    @VisibleForTesting
+    public static final int SHORT_PRESS = 1;
+    @VisibleForTesting
+    public static final int LONG_PRESS = 2;
 
     private static final AudioAttributes AUDIO_ATTRIBUTES = new AudioAttributes.Builder()
             .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java
index 9d20d4a..7d4ee13 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -47,7 +47,7 @@
 // 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 com.android.server.telecom.SystemStateHelper.SystemStateListener;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -696,6 +696,11 @@
         public void onRemoteRttRequest(Call call, int requestId) {
             notifyRemoteRttRequest(call, requestId);
         }
+
+        @Override
+        public void onCallIdentificationChanged(Call call) {
+            updateCall(call);
+        }
     };
 
     private final SystemStateListener mSystemStateListener = new SystemStateListener() {
@@ -716,11 +721,6 @@
     /** The in-call app implementations, see {@link IInCallService}. */
     private final Map<InCallServiceInfo, IInCallService> mInCallServices = new ArrayMap<>();
 
-    /**
-     * The {@link ComponentName} of the bound In-Call UI Service.
-     */
-    private ComponentName mInCallUIComponentName;
-
     private final CallIdMapper mCallIdMapper = new CallIdMapper(Call::getId);
 
     /** The {@link ComponentName} of the default InCall UI. */
@@ -729,7 +729,7 @@
     private final Context mContext;
     private final TelecomSystem.SyncRoot mLock;
     private final CallsManager mCallsManager;
-    private final SystemStateProvider mSystemStateProvider;
+    private final SystemStateHelper mSystemStateHelper;
     private final Timeouts.Adapter mTimeoutsAdapter;
     private final DefaultDialerCache mDefaultDialerCache;
     private final EmergencyCallHelper mEmergencyCallHelper;
@@ -737,13 +737,13 @@
     private NonUIInCallServiceConnectionCollection mNonUIInCallServiceConnections;
 
     public InCallController(Context context, TelecomSystem.SyncRoot lock, CallsManager callsManager,
-            SystemStateProvider systemStateProvider,
+            SystemStateHelper systemStateHelper,
             DefaultDialerCache defaultDialerCache, Timeouts.Adapter timeoutsAdapter,
             EmergencyCallHelper emergencyCallHelper) {
         mContext = context;
         mLock = lock;
         mCallsManager = callsManager;
-        mSystemStateProvider = systemStateProvider;
+        mSystemStateHelper = systemStateHelper;
         mTimeoutsAdapter = timeoutsAdapter;
         mDefaultDialerCache = defaultDialerCache;
         mEmergencyCallHelper = emergencyCallHelper;
@@ -753,7 +753,7 @@
                 resources.getString(R.string.ui_default_package),
                 resources.getString(R.string.incall_default_class));
 
-        mSystemStateProvider.addListener(mSystemStateListener);
+        mSystemStateHelper.addListener(mSystemStateListener);
     }
 
     @Override
@@ -797,7 +797,8 @@
 
                 ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(call,
                         true /* includeVideoProvider */, mCallsManager.getPhoneAccountRegistrar(),
-                        info.isExternalCallsSupported(), includeRttCall);
+                        info.isExternalCallsSupported(), includeRttCall,
+                        info.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI);
                 try {
                     inCallService.addCall(parcelableCall);
                 } catch (RemoteException ignored) {
@@ -861,7 +862,8 @@
 
                 ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(call,
                         true /* includeVideoProvider */, mCallsManager.getPhoneAccountRegistrar(),
-                        info.isExternalCallsSupported(), includeRttCall);
+                        info.isExternalCallsSupported(), includeRttCall,
+                        info.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI);
                 try {
                     inCallService.addCall(parcelableCall);
                 } catch (RemoteException ignored) {
@@ -872,15 +874,7 @@
             // The call was regular but it is now external.  We must now remove it from any
             // InCallServices which do not support external calls.
             // Remove the call by sending a call update indicating the call was disconnected.
-            ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(
-                    call,
-                    false /* includeVideoProvider */,
-                    mCallsManager.getPhoneAccountRegistrar(),
-                    false /* supportsExternalCalls */,
-                    android.telecom.Call.STATE_DISCONNECTED /* overrideState */,
-                    false /* includeRttCall */);
-
-            Log.i(this, "Removing external call %s ==> %s", call, parcelableCall);
+            Log.i(this, "Removing external call %", call);
             for (Map.Entry<InCallServiceInfo, IInCallService> entry : mInCallServices.entrySet()) {
                 InCallServiceInfo info = entry.getKey();
                 if (info.isExternalCallsSupported()) {
@@ -892,6 +886,16 @@
                 componentsUpdated.add(info.getComponentName());
                 IInCallService inCallService = entry.getValue();
 
+                ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(
+                        call,
+                        false /* includeVideoProvider */,
+                        mCallsManager.getPhoneAccountRegistrar(),
+                        false /* supportsExternalCalls */,
+                        android.telecom.Call.STATE_DISCONNECTED /* overrideState */,
+                        false /* includeRttCall */,
+                        info.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI
+                        );
+
                 try {
                     inCallService.updateCall(parcelableCall);
                 } catch (RemoteException ignored) {
@@ -960,6 +964,24 @@
         updateCall(call);
     }
 
+    @Override
+    public void onConnectionTimeChanged(Call call) {
+        Log.d(this, "onConnectionTimeChanged %s", call);
+        updateCall(call);
+    }
+
+    @Override
+    public void onIsVoipAudioModeChanged(Call call) {
+        Log.d(this, "onIsVoipAudioModeChanged %s", call);
+        updateCall(call);
+    }
+
+    @Override
+    public void onConferenceStateChanged(Call call, boolean isConference) {
+        Log.d(this, "onConferenceStateChanged %s ,isConf=%b", call, isConference);
+        updateCall(call);
+    }
+
     void bringToForeground(boolean showDialpad) {
         if (!mInCallServices.isEmpty()) {
             for (IInCallService inCallService : mInCallServices.values()) {
@@ -1221,7 +1243,7 @@
     }
 
     private boolean shouldUseCarModeUI() {
-        return mSystemStateProvider.isCarMode();
+        return mSystemStateHelper.isCarMode();
     }
 
     /**
@@ -1342,7 +1364,8 @@
                         true /* includeVideoProvider */,
                         mCallsManager.getPhoneAccountRegistrar(),
                         info.isExternalCallsSupported(),
-                        includeRttCall));
+                        includeRttCall,
+                        info.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI));
             } catch (RemoteException ignored) {
             }
         }
@@ -1404,7 +1427,8 @@
                         videoProviderChanged /* includeVideoProvider */,
                         mCallsManager.getPhoneAccountRegistrar(),
                         info.isExternalCallsSupported(),
-                        rttInfoChanged && info.equals(mInCallServiceConnection.getInfo()));
+                        rttInfoChanged && info.equals(mInCallServiceConnection.getInfo()),
+                        info.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI);
                 ComponentName componentName = info.getComponentName();
                 IInCallService inCallService = entry.getValue();
                 componentsUpdated.add(componentName);
@@ -1457,18 +1481,42 @@
         pw.decreaseIndent();
     }
 
+    /**
+     * @return The package name of the UI which is currently bound, or null if none.
+     */
+    private ComponentName getConnectedUi() {
+        InCallServiceInfo connectedUi = mInCallServices.keySet().stream().filter(
+                i -> i.getType() == IN_CALL_SERVICE_TYPE_DIALER_UI
+                        || i.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI)
+                .findAny()
+                .orElse(null);
+        if (connectedUi != null) {
+            return connectedUi.mComponentName;
+        }
+        return null;
+    }
+
     public boolean doesConnectedDialerSupportRinging() {
         String ringingPackage =  null;
-        if (mInCallUIComponentName != null) {
-            ringingPackage = mInCallUIComponentName.getPackageName().trim();
+
+        ComponentName connectedPackage = getConnectedUi();
+        if (connectedPackage != null) {
+            ringingPackage = connectedPackage.getPackageName().trim();
+            Log.d(this, "doesConnectedDialerSupportRinging: alreadyConnectedPackage=%s",
+                    ringingPackage);
         }
 
         if (TextUtils.isEmpty(ringingPackage)) {
             // The current in-call UI returned nothing, so lets use the default dialer.
-            ringingPackage = DefaultDialerManager.getDefaultDialerApplication(
-                    mContext, UserHandle.USER_CURRENT);
+            ringingPackage = mDefaultDialerCache.getDefaultDialerApplication(
+                    mCallsManager.getCurrentUserHandle().getIdentifier());
+            if (ringingPackage != null) {
+                Log.d(this, "doesConnectedDialerSupportRinging: notCurentlyConnectedPackage=%s",
+                        ringingPackage);
+            }
         }
         if (TextUtils.isEmpty(ringingPackage)) {
+            Log.w(this, "doesConnectedDialerSupportRinging: no default dialer found; oh no!");
             return false;
         }
 
@@ -1478,11 +1526,15 @@
                 intent, PackageManager.GET_META_DATA,
                 mCallsManager.getCurrentUserHandle().getIdentifier());
         if (entries.isEmpty()) {
+            Log.w(this, "doesConnectedDialerSupportRinging: couldn't find dialer's package info"
+                    + " <sad trombone>");
             return false;
         }
 
         ResolveInfo info = entries.get(0);
         if (info.serviceInfo == null || info.serviceInfo.metaData == null) {
+            Log.w(this, "doesConnectedDialerSupportRinging: couldn't find dialer's metadata"
+                    + " <even sadder trombone>");
             return false;
         }
 
diff --git a/src/com/android/server/telecom/InCallControllerFactory.java b/src/com/android/server/telecom/InCallControllerFactory.java
index e384af7..c3a7831 100644
--- a/src/com/android/server/telecom/InCallControllerFactory.java
+++ b/src/com/android/server/telecom/InCallControllerFactory.java
@@ -23,6 +23,6 @@
  */
 public interface InCallControllerFactory {
     InCallController create(Context context, TelecomSystem.SyncRoot lock, CallsManager callsManager,
-            SystemStateProvider systemStateProvider, DefaultDialerCache defaultDialerCache,
+            SystemStateHelper systemStateHelper, DefaultDialerCache defaultDialerCache,
             Timeouts.Adapter timeoutsAdapter, EmergencyCallHelper emergencyCallHelper);
 }
diff --git a/src/com/android/server/telecom/InCallTonePlayer.java b/src/com/android/server/telecom/InCallTonePlayer.java
index a258aee..5864ce0 100644
--- a/src/com/android/server/telecom/InCallTonePlayer.java
+++ b/src/com/android/server/telecom/InCallTonePlayer.java
@@ -16,8 +16,13 @@
 
 package com.android.server.telecom;
 
+import android.annotation.Nullable;
+import android.content.Context;
+import android.media.AudioAttributes;
 import android.media.AudioManager;
+import android.media.MediaPlayer;
 import android.media.ToneGenerator;
+import android.net.Uri;
 import android.os.Handler;
 import android.os.Looper;
 import android.telecom.Log;
@@ -26,10 +31,15 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
 /**
- * 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.
+ * Play a call-related tone (ringback, busy signal, etc.) either through ToneGenerator, or using a
+ * media resource file.
+ * 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 class InCallTonePlayer extends Thread {
 
@@ -41,12 +51,17 @@
         private final CallAudioRoutePeripheralAdapter mCallAudioRoutePeripheralAdapter;
         private final TelecomSystem.SyncRoot mLock;
         private final ToneGeneratorFactory mToneGeneratorFactory;
+        private final MediaPlayerFactory mMediaPlayerFactory;
+        private final AudioManagerAdapter mAudioManagerAdapter;
 
-        Factory(CallAudioRoutePeripheralAdapter callAudioRoutePeripheralAdapter,
-                TelecomSystem.SyncRoot lock, ToneGeneratorFactory toneGeneratorFactory) {
+        public Factory(CallAudioRoutePeripheralAdapter callAudioRoutePeripheralAdapter,
+                TelecomSystem.SyncRoot lock, ToneGeneratorFactory toneGeneratorFactory,
+                MediaPlayerFactory mediaPlayerFactory, AudioManagerAdapter audioManagerAdapter) {
             mCallAudioRoutePeripheralAdapter = callAudioRoutePeripheralAdapter;
             mLock = lock;
             mToneGeneratorFactory = toneGeneratorFactory;
+            mMediaPlayerFactory = mediaPlayerFactory;
+            mAudioManagerAdapter = audioManagerAdapter;
         }
 
         public void setCallAudioManager(CallAudioManager callAudioManager) {
@@ -55,7 +70,8 @@
 
         public InCallTonePlayer createPlayer(int tone) {
             return new InCallTonePlayer(tone, mCallAudioManager,
-                    mCallAudioRoutePeripheralAdapter, mLock, mToneGeneratorFactory);
+                    mCallAudioRoutePeripheralAdapter, mLock, mToneGeneratorFactory,
+                    mMediaPlayerFactory, mAudioManagerAdapter);
         }
     }
 
@@ -63,6 +79,73 @@
         ToneGenerator get (int streamType, int volume);
     }
 
+    public interface MediaPlayerAdapter {
+        void setLooping(boolean isLooping);
+        void setOnCompletionListener(MediaPlayer.OnCompletionListener listener);
+        void start();
+        void release();
+        int getDuration();
+    }
+
+    public static class MediaPlayerAdapterImpl implements MediaPlayerAdapter {
+        private MediaPlayer mMediaPlayer;
+
+        /**
+         * Create new media player adapter backed by a real mediaplayer.
+         * Note: Its possible for the mediaplayer to be null if
+         * {@link MediaPlayer#create(Context, Uri)} fails for some reason; in this case we can
+         * continue but not bother playing the audio.
+         * @param mediaPlayer The media player.
+         */
+        public MediaPlayerAdapterImpl(@Nullable MediaPlayer mediaPlayer) {
+            mMediaPlayer = mediaPlayer;
+        }
+
+        @Override
+        public void setLooping(boolean isLooping) {
+            if (mMediaPlayer != null) {
+                mMediaPlayer.setLooping(isLooping);
+            }
+        }
+
+        @Override
+        public void setOnCompletionListener(MediaPlayer.OnCompletionListener listener) {
+            if (mMediaPlayer != null) {
+                mMediaPlayer.setOnCompletionListener(listener);
+            }
+        }
+
+        @Override
+        public void start() {
+            if (mMediaPlayer != null) {
+                mMediaPlayer.start();
+            }
+        }
+
+        @Override
+        public void release() {
+            if (mMediaPlayer != null) {
+                mMediaPlayer.release();
+            }
+        }
+
+        @Override
+        public int getDuration() {
+            if (mMediaPlayer != null) {
+                return mMediaPlayer.getDuration();
+            }
+            return 0;
+        }
+    }
+
+    public interface MediaPlayerFactory {
+        MediaPlayerAdapter get (int resourceId, AudioAttributes attributes);
+    }
+
+    public interface AudioManagerAdapter {
+        boolean isVolumeOverZero();
+    }
+
     // The possible tones that we can play.
     public static final int TONE_INVALID = 0;
     public static final int TONE_BUSY = 1;
@@ -80,9 +163,12 @@
     public static final int TONE_VOICE_PRIVACY = 13;
     public static final int TONE_VIDEO_UPGRADE = 14;
 
+    private static final int TONE_RESOURCE_ID_UNDEFINED = -1;
+
     private static final int RELATIVE_VOLUME_EMERGENCY = 100;
     private static final int RELATIVE_VOLUME_HIPRI = 80;
     private static final int RELATIVE_VOLUME_LOPRI = 50;
+    private static final int RELATIVE_VOLUME_UNDEFINED = -1;
 
     // Buffer time (in msec) to add on to the tone timeout value. Needed mainly when the timeout
     // value for a tone is exact duration of the tone itself.
@@ -111,6 +197,9 @@
     /** Current state of the tone player. */
     private int mState;
 
+    /** For tones which are not generated using ToneGenerator. */
+    private MediaPlayerAdapter mToneMediaPlayer = null;
+
     /** Telecom lock object. */
     private final TelecomSystem.SyncRoot mLock;
 
@@ -118,6 +207,8 @@
     private final Object mSessionLock = new Object();
 
     private final ToneGeneratorFactory mToneGenerator;
+    private final MediaPlayerFactory mMediaPlayerFactory;
+    private final AudioManagerAdapter mAudioManagerAdapter;
 
     /**
      * Initializes the tone player. Private; use the {@link Factory} to create tone players.
@@ -129,19 +220,22 @@
             CallAudioManager callAudioManager,
             CallAudioRoutePeripheralAdapter callAudioRoutePeripheralAdapter,
             TelecomSystem.SyncRoot lock,
-            ToneGeneratorFactory toneGeneratorFactory) {
+            ToneGeneratorFactory toneGeneratorFactory,
+            MediaPlayerFactory mediaPlayerFactor,
+            AudioManagerAdapter audioManagerAdapter) {
         mState = STATE_OFF;
         mToneId = toneId;
         mCallAudioManager = callAudioManager;
         mCallAudioRoutePeripheralAdapter = callAudioRoutePeripheralAdapter;
         mLock = lock;
         mToneGenerator = toneGeneratorFactory;
+        mMediaPlayerFactory = mediaPlayerFactor;
+        mAudioManagerAdapter = audioManagerAdapter;
     }
 
     /** {@inheritDoc} */
     @Override
     public void run() {
-        ToneGenerator toneGenerator = null;
         try {
             synchronized (mSessionLock) {
                 if (mSession != null) {
@@ -154,6 +248,8 @@
             final int toneType;  // Passed to ToneGenerator.startTone.
             final int toneVolume;  // Passed to the ToneGenerator constructor.
             final int toneLengthMillis;
+            final int mediaResourceId; // The resourceId of the tone to play.  Used for media-based
+                                      // tones.
 
             switch (mToneId) {
                 case TONE_BUSY:
@@ -161,11 +257,16 @@
                     toneType = ToneGenerator.TONE_SUP_BUSY;
                     toneVolume = RELATIVE_VOLUME_HIPRI;
                     toneLengthMillis = 4000;
+                    mediaResourceId = TONE_RESOURCE_ID_UNDEFINED;
                     break;
                 case TONE_CALL_ENDED:
-                    toneType = ToneGenerator.TONE_PROP_PROMPT;
-                    toneVolume = RELATIVE_VOLUME_HIPRI;
-                    toneLengthMillis = 200;
+                    // Don't use tone generator
+                    toneType = ToneGenerator.TONE_UNKNOWN;
+                    toneVolume = RELATIVE_VOLUME_UNDEFINED;
+                    toneLengthMillis = 0;
+
+                    // Use a tone resource file for a more rich, full-bodied tone experience.
+                    mediaResourceId = R.raw.endcall;
                     break;
                 case TONE_OTA_CALL_ENDED:
                     // TODO: fill in
@@ -174,46 +275,55 @@
                     toneType = ToneGenerator.TONE_SUP_CALL_WAITING;
                     toneVolume = RELATIVE_VOLUME_HIPRI;
                     toneLengthMillis = Integer.MAX_VALUE - TIMEOUT_BUFFER_MILLIS;
+                    mediaResourceId = TONE_RESOURCE_ID_UNDEFINED;
                     break;
                 case TONE_CDMA_DROP:
                     toneType = ToneGenerator.TONE_CDMA_CALLDROP_LITE;
                     toneVolume = RELATIVE_VOLUME_LOPRI;
                     toneLengthMillis = 375;
+                    mediaResourceId = TONE_RESOURCE_ID_UNDEFINED;
                     break;
                 case TONE_CONGESTION:
                     toneType = ToneGenerator.TONE_SUP_CONGESTION;
                     toneVolume = RELATIVE_VOLUME_HIPRI;
                     toneLengthMillis = 4000;
+                    mediaResourceId = TONE_RESOURCE_ID_UNDEFINED;
                     break;
                 case TONE_INTERCEPT:
                     toneType = ToneGenerator.TONE_CDMA_ABBR_INTERCEPT;
                     toneVolume = RELATIVE_VOLUME_LOPRI;
                     toneLengthMillis = 500;
+                    mediaResourceId = TONE_RESOURCE_ID_UNDEFINED;
                     break;
                 case TONE_OUT_OF_SERVICE:
                     toneType = ToneGenerator.TONE_CDMA_CALLDROP_LITE;
                     toneVolume = RELATIVE_VOLUME_LOPRI;
                     toneLengthMillis = 375;
+                    mediaResourceId = TONE_RESOURCE_ID_UNDEFINED;
                     break;
                 case TONE_REDIAL:
                     toneType = ToneGenerator.TONE_CDMA_ALERT_AUTOREDIAL_LITE;
                     toneVolume = RELATIVE_VOLUME_LOPRI;
                     toneLengthMillis = 5000;
+                    mediaResourceId = TONE_RESOURCE_ID_UNDEFINED;
                     break;
                 case TONE_REORDER:
                     toneType = ToneGenerator.TONE_CDMA_REORDER;
                     toneVolume = RELATIVE_VOLUME_HIPRI;
                     toneLengthMillis = 4000;
+                    mediaResourceId = TONE_RESOURCE_ID_UNDEFINED;
                     break;
                 case TONE_RING_BACK:
                     toneType = ToneGenerator.TONE_SUP_RINGTONE;
                     toneVolume = RELATIVE_VOLUME_HIPRI;
                     toneLengthMillis = Integer.MAX_VALUE - TIMEOUT_BUFFER_MILLIS;
+                    mediaResourceId = TONE_RESOURCE_ID_UNDEFINED;
                     break;
                 case TONE_UNOBTAINABLE_NUMBER:
                     toneType = ToneGenerator.TONE_SUP_ERROR;
                     toneVolume = RELATIVE_VOLUME_HIPRI;
                     toneLengthMillis = 4000;
+                    mediaResourceId = TONE_RESOURCE_ID_UNDEFINED;
                     break;
                 case TONE_VOICE_PRIVACY:
                     // TODO: fill in.
@@ -223,6 +333,7 @@
                     toneType = ToneGenerator.TONE_SUP_CALL_WAITING;
                     toneVolume = RELATIVE_VOLUME_HIPRI;
                     toneLengthMillis = 4000;
+                    mediaResourceId = TONE_RESOURCE_ID_UNDEFINED;
                     break;
                 default:
                     throw new IllegalStateException("Bad toneId: " + mToneId);
@@ -233,16 +344,38 @@
                 stream = AudioManager.STREAM_BLUETOOTH_SCO;
             }
 
+            if (toneType != ToneGenerator.TONE_UNKNOWN) {
+                playToneGeneratorTone(stream, toneVolume, toneType, toneLengthMillis);
+            } else if (mediaResourceId != TONE_RESOURCE_ID_UNDEFINED) {
+                playMediaTone(stream, mediaResourceId);
+            }
+        } finally {
+            cleanUpTonePlayer();
+            Log.endSession();
+        }
+    }
+
+    /**
+     * Play a tone generated by the {@link ToneGenerator}.
+     * @param stream The stream on which the tone will be played.
+     * @param toneVolume The volume of the tone.
+     * @param toneType The type of tone to play.
+     * @param toneLengthMillis How long to play the tone.
+     */
+    private void playToneGeneratorTone(int stream, int toneVolume, int toneType,
+            int toneLengthMillis) {
+        ToneGenerator toneGenerator = null;
+        try {
             // If the ToneGenerator creation fails, just continue without it. It is a local audio
             // signal, and is not as important.
             try {
-                Log.v(this, "Creating generator");
                 toneGenerator = mToneGenerator.get(stream, toneVolume);
             } catch (RuntimeException e) {
                 Log.w(this, "Failed to create ToneGenerator.", e);
                 return;
             }
 
+            Log.i(this, "playToneGeneratorTone: toneType=%d", toneType);
             // TODO: Certain CDMA tones need to check the ringer-volume state before
             // playing. See CallNotifier.InCallTonePlayer.
 
@@ -267,13 +400,61 @@
             if (toneGenerator != null) {
                 toneGenerator.release();
             }
-            cleanUpTonePlayer();
-            Log.endSession();
         }
     }
-    
+
+    /**
+     * Plays an audio-file based media tone.
+     * @param stream The audio stream on which to play the tone.
+     * @param toneResourceId The resource ID of the tone to play.
+     */
+    private void playMediaTone(int stream, int toneResourceId) {
+        synchronized (this) {
+            if (mState != STATE_STOPPED) {
+                mState = STATE_ON;
+            }
+            Log.i(this, "playMediaTone: toneResourceId=%d", toneResourceId);
+            AudioAttributes attributes = new AudioAttributes.Builder()
+                    .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
+                    .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
+                    .setLegacyStreamType(stream)
+                    .build();
+            mToneMediaPlayer = mMediaPlayerFactory.get(toneResourceId, attributes);
+            mToneMediaPlayer.setLooping(false);
+            int durationMillis = mToneMediaPlayer.getDuration();
+            final CountDownLatch toneLatch = new CountDownLatch(1);
+            mToneMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
+                @Override
+                public void onCompletion(MediaPlayer mp) {
+                    Log.i(this, "playMediaTone: toneResourceId=%d completed.", toneResourceId);
+                    synchronized (this) {
+                        mState = STATE_OFF;
+                    }
+                    mToneMediaPlayer.release();
+                    mToneMediaPlayer = null;
+                    toneLatch.countDown();
+                }
+            });
+            mToneMediaPlayer.start();
+            try {
+                // Wait for the tone to stop playing; timeout at 2x the length of the file just to
+                // be on the safe side.
+                toneLatch.await(durationMillis * 2, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException ie) {
+                Log.e(this, ie, "playMediaTone: tone playback interrupted.");
+            }
+        }
+
+    }
+
     @VisibleForTesting
-    public void startTone() {
+    public boolean startTone() {
+        // Skip playing the end call tone if the volume is silenced.
+        if (mToneId == TONE_CALL_ENDED && !mAudioManagerAdapter.isVolumeOverZero()) {
+            Log.i(this, "startTone: skip end-call tone as device is silenced.");
+            return false;
+        }
+
         sTonesPlaying++;
         if (sTonesPlaying == 1) {
             mCallAudioManager.setIsTonePlaying(true);
@@ -287,6 +468,7 @@
         }
 
         super.start();
+        return true;
     }
 
     @Override
@@ -308,6 +490,11 @@
         }
     }
 
+    @VisibleForTesting
+    public void cleanup() {
+        sTonesPlaying = 0;
+    }
+
     private void cleanUpTonePlayer() {
         // Release focus on the main thread.
         mMainThreadHandler.post(new Runnable("ICTP.cUTP", mLock) {
diff --git a/src/com/android/server/telecom/LogUtils.java b/src/com/android/server/telecom/LogUtils.java
index 02a3b77..71ed67f 100644
--- a/src/com/android/server/telecom/LogUtils.java
+++ b/src/com/android/server/telecom/LogUtils.java
@@ -65,6 +65,7 @@
         public static final String SET_ACTIVE = "SET_ACTIVE";
         public static final String SET_HOLD = "SET_HOLD";
         public static final String SET_RINGING = "SET_RINGING";
+        public static final String SET_ANSWERED = "SET_ANSWERED";
         public static final String SET_DISCONNECTED = "SET_DISCONNECTED";
         public static final String SET_DISCONNECTING = "SET_DISCONNECTING";
         public static final String SET_SELECT_PHONE_ACCOUNT = "SET_SELECT_PHONE_ACCOUNT";
@@ -94,6 +95,7 @@
         public static final String ADD_CHILD = "ADD_CHILD";
         public static final String REMOVE_CHILD = "REMOVE_CHILD";
         public static final String SET_PARENT = "SET_PARENT";
+        public static final String CONF_STATE_CHANGED = "CONF_STATE_CHANGED";
         public static final String MUTE = "MUTE";
         public static final String UNMUTE = "UNMUTE";
         public static final String AUDIO_ROUTE = "AUDIO_ROUTE";
@@ -107,7 +109,10 @@
         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 CONTROLLER_SCREENING_COMPLETED =
+                "CONTROLLER_SCREENING_COMPLETED";
         public static final String SCREENING_COMPLETED = "SCREENING_COMPLETED";
+        public static final String CALL_IDENTIFICATION_SET = "CALL_IDENTIFICATION_SET";
         public static final String BLOCK_CHECK_INITIATED = "BLOCK_CHECK_INITIATED";
         public static final String BLOCK_CHECK_FINISHED = "BLOCK_CHECK_FINISHED";
         public static final String DIRECT_TO_VM_INITIATED = "DIRECT_TO_VM_INITIATED";
@@ -136,6 +141,14 @@
         public static final String HANDOVER_FAILED = "HANDOVER_FAILED";
         public static final String START_RINBACK = "START_RINGBACK";
         public static final String STOP_RINGBACK = "STOP_RINGBACK";
+        public static final String REDIRECTION_BOUND_USER = "REDIRECTION_BOUND_USER";
+        public static final String REDIRECTION_BOUND_CARRIER = "REDIRECTION_BOUND_CARRIER";
+        public static final String REDIRECTION_SENT_USER = "REDIRECTION_SENT_USER";
+        public static final String REDIRECTION_SENT_CARRIER = "REDIRECTION_SENT_CARRIER";
+        public static final String REDIRECTION_COMPLETED_USER = "REDIRECTION_COMPLETED_USER";
+        public static final String REDIRECTION_COMPLETED_CARRIER = "REDIRECTION_COMPLETED_CARRIER";
+        public static final String REDIRECTION_TIMED_OUT_USER = "REDIRECTION_TIMED_OUT_USER";
+        public static final String REDIRECTION_TIMED_OUT_CARRIER = "REDIRECTION_TIMED_OUT_CARRIER";
 
         public static class Timings {
             public static final String ACCEPT_TIMING = "accept";
diff --git a/src/com/android/server/telecom/LoggedHandlerExecutor.java b/src/com/android/server/telecom/LoggedHandlerExecutor.java
new file mode 100644
index 0000000..cc4dd28
--- /dev/null
+++ b/src/com/android/server/telecom/LoggedHandlerExecutor.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2019 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.Handler;
+import android.telecom.Logging.Runnable;
+
+import java.util.concurrent.Executor;
+
+/** An executor that starts a log session before executing a runnable */
+public class LoggedHandlerExecutor implements Executor {
+    private Handler mHandler;
+    private String mSessionName;
+    private TelecomSystem.SyncRoot mLock;
+
+    public LoggedHandlerExecutor(Handler handler, String sessionName,
+            TelecomSystem.SyncRoot lock) {
+        mHandler = handler;
+        mSessionName = sessionName;
+        mLock = lock;
+    }
+
+    @Override
+    public void execute(java.lang.Runnable command) {
+        mHandler.post(new Runnable(mSessionName, mLock) {
+            @Override
+            public void loggedRun() {
+                command.run();
+            }
+        }.prepare());
+    }
+}
diff --git a/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java b/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
index 3797c68..9a58c4b 100644
--- a/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
+++ b/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
@@ -37,6 +37,7 @@
 import android.text.TextUtils;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.telecom.callredirection.CallRedirectionProcessor;
 
 // TODO: Needed for move to system service: import com.android.internal.R;
 
@@ -81,6 +82,19 @@
      */
     private final boolean mIsDefaultOrSystemPhoneApp;
 
+    public static class CallDisposition {
+        // True for certain types of numbers that are not intended to be intercepted or modified
+        // by third parties (e.g. emergency numbers).
+        public boolean callImmediately = false;
+        // True for all managed calls, false for self-managed calls.
+        public boolean sendBroadcast = true;
+        // True for requesting call redirection, false for not requesting it.
+        public boolean requestRedirection = true;
+        public int disconnectCause = DisconnectCause.NOT_DISCONNECTED;
+        String number;
+        Uri callingAddress;
+    }
+
     @VisibleForTesting
     public NewOutgoingCallIntentBroadcaster(Context context, CallsManager callsManager, Call call,
             Intent intent, PhoneNumberUtilsAdapter phoneNumberUtilsAdapter,
@@ -192,8 +206,8 @@
      *         {@link DisconnectCause} if the call did not, describing why it failed.
      */
     @VisibleForTesting
-    public int processIntent() {
-        Log.v(this, "Processing call intent in OutgoingCallIntentBroadcaster.");
+    public CallDisposition evaluateCall() {
+        CallDisposition result = new CallDisposition();
 
         Intent intent = mIntent;
         String action = intent.getAction();
@@ -201,7 +215,8 @@
 
         if (handle == null) {
             Log.w(this, "Empty handle obtained from the call intent.");
-            return DisconnectCause.INVALID_NUMBER;
+            result.disconnectCause = DisconnectCause.INVALID_NUMBER;
+            return result;
         }
 
         boolean isVoicemailNumber = PhoneAccount.SCHEME_VOICEMAIL.equals(handle.getScheme());
@@ -209,16 +224,18 @@
             if (Intent.ACTION_CALL.equals(action)
                     || Intent.ACTION_CALL_PRIVILEGED.equals(action)) {
                 // Voicemail calls will be handled directly by the telephony connection manager
-
-                boolean speakerphoneOn = mIntent.getBooleanExtra(
-                        TelecomManager.EXTRA_START_CALL_WITH_SPEAKERPHONE, false);
-                placeOutgoingCallImmediately(mCall, handle, null, speakerphoneOn,
+                Log.i(this, "Voicemail number dialed. Skipping redirection and broadcast", intent);
+                mIntent.putExtra(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
                         VideoProfile.STATE_AUDIO_ONLY);
-
-                return DisconnectCause.NOT_DISCONNECTED;
+                result.callImmediately = true;
+                result.requestRedirection = false;
+                result.sendBroadcast = false;
+                result.callingAddress = handle;
+                return result;
             } else {
                 Log.i(this, "Unhandled intent %s. Ignoring and not placing call.", intent);
-                return DisconnectCause.OUTGOING_CANCELED;
+                result.disconnectCause = DisconnectCause.OUTGOING_CANCELED;
+                return result;
             }
         }
 
@@ -234,76 +251,88 @@
             }
         }
 
-        String number = "";
-        // True for certain types of numbers that are not intended to be intercepted or modified
-        // by third parties (e.g. emergency numbers).
-        boolean callImmediately = false;
-        // True for all managed calls, false for self-managed calls.
-        boolean sendNewOutgoingCallBroadcast = true;
-        Uri callingAddress = handle;
+        result.number = "";
+        result.callingAddress = handle;
 
-        if (!isSelfManaged) {
-            // Placing a managed call
-            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;
-            }
-
-            // TODO: Cleanup this dialing code; it makes the assumption that we're dialing with a
-            // SIP or TEL URI.
-            boolean isUriNumber = mPhoneNumberUtilsAdapter.isUriNumber(number);
-            if (!isUriNumber) {
-                number = mPhoneNumberUtilsAdapter.convertKeypadLettersToDigits(number);
-                number = mPhoneNumberUtilsAdapter.stripSeparators(number);
-            }
-
-            final boolean isPotentialEmergencyNumber = isPotentialEmergencyNumber(number);
-            Log.v(this, "isPotentialEmergencyNumber = %s", isPotentialEmergencyNumber);
-
-            rewriteCallIntentAction(intent, isPotentialEmergencyNumber);
-            action = intent.getAction();
-
-            if (Intent.ACTION_CALL.equals(action)) {
-                if (isPotentialEmergencyNumber) {
-                    if (!mIsDefaultOrSystemPhoneApp) {
-                        Log.w(this, "Cannot call potential emergency number %s with CALL Intent %s "
-                                + "unless caller is system or default dialer.", number, intent);
-                        launchSystemDialer(intent.getData());
-                        return DisconnectCause.OUTGOING_CANCELED;
-                    } else {
-                        callImmediately = true;
-                    }
-                }
-            } else if (Intent.ACTION_CALL_EMERGENCY.equals(action)) {
-                if (!isPotentialEmergencyNumber) {
-                    Log.w(this, "Cannot call non-potential-emergency number %s with EMERGENCY_CALL "
-                            + "Intent %s.", number, intent);
-                    return DisconnectCause.OUTGOING_CANCELED;
-                }
-                callImmediately = true;
-            } else {
-                Log.w(this, "Unhandled Intent %s. Ignoring and not placing call.", intent);
-                return DisconnectCause.INVALID_NUMBER;
-            }
-
-            // TODO: Support dialing using URIs instead of just assuming SIP or TEL.
-            String scheme = isUriNumber ? PhoneAccount.SCHEME_SIP : PhoneAccount.SCHEME_TEL;
-            callingAddress = Uri.fromParts(scheme, number, null);
-        } else {
+        if (isSelfManaged) {
             // Self-managed call.
-            callImmediately = true;
-            sendNewOutgoingCallBroadcast = false;
+            result.callImmediately = true;
+            result.sendBroadcast = false;
+            result.requestRedirection = false;
             Log.i(this, "Skipping NewOutgoingCallBroadcast for self-managed call.");
+            return result;
         }
 
-        if (callImmediately) {
+        // Placing a managed call
+        String number = getNumberFromCallIntent(intent);
+        result.number = number;
+        if (number == null) {
+            result.disconnectCause = DisconnectCause.NO_PHONE_NUMBER_SUPPLIED;
+            return result;
+        }
+
+        final boolean isPotentialEmergencyNumber = isPotentialEmergencyNumber(number);
+        Log.v(this, "isPotentialEmergencyNumber = %s", isPotentialEmergencyNumber);
+
+        action = calculateCallIntentAction(intent, isPotentialEmergencyNumber);
+        intent.setAction(action);
+
+        if (Intent.ACTION_CALL.equals(action)) {
+            if (isPotentialEmergencyNumber) {
+                if (!mIsDefaultOrSystemPhoneApp) {
+                    Log.w(this, "Cannot call potential emergency number %s with CALL Intent %s "
+                            + "unless caller is system or default dialer.", number, intent);
+                    launchSystemDialer(intent.getData());
+                    result.disconnectCause = DisconnectCause.OUTGOING_CANCELED;
+                    return result;
+                } else {
+                    result.callImmediately = true;
+                }
+            }
+        } else if (Intent.ACTION_CALL_EMERGENCY.equals(action)) {
+            if (!isPotentialEmergencyNumber) {
+                Log.w(this, "Cannot call non-potential-emergency number %s with EMERGENCY_CALL "
+                        + "Intent %s.", number, intent);
+                result.disconnectCause = DisconnectCause.OUTGOING_CANCELED;
+                return result;
+            }
+            result.callImmediately = true;
+        } else {
+            Log.w(this, "Unhandled Intent %s. Ignoring and not placing call.", intent);
+            result.disconnectCause = DisconnectCause.INVALID_NUMBER;
+            return result;
+        }
+
+        String scheme = mPhoneNumberUtilsAdapter.isUriNumber(number)
+                ? PhoneAccount.SCHEME_SIP : PhoneAccount.SCHEME_TEL;
+        result.callingAddress = Uri.fromParts(scheme, number, null);
+        return result;
+    }
+
+    private String getNumberFromCallIntent(Intent intent) {
+        String number;
+        number = mPhoneNumberUtilsAdapter.getNumberFromIntent(intent, mContext);
+        if (TextUtils.isEmpty(number)) {
+            Log.w(this, "Empty number obtained from the call intent.");
+            return null;
+        }
+
+        boolean isUriNumber = mPhoneNumberUtilsAdapter.isUriNumber(number);
+        if (!isUriNumber) {
+            number = mPhoneNumberUtilsAdapter.convertKeypadLettersToDigits(number);
+            number = mPhoneNumberUtilsAdapter.stripSeparators(number);
+        }
+        return number;
+    }
+
+    public void processCall(CallDisposition disposition) {
+        if (disposition.callImmediately) {
             boolean speakerphoneOn = mIntent.getBooleanExtra(
                     TelecomManager.EXTRA_START_CALL_WITH_SPEAKERPHONE, false);
             int videoState = mIntent.getIntExtra(
                     TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
                     VideoProfile.STATE_AUDIO_ONLY);
-            placeOutgoingCallImmediately(mCall, callingAddress, null,
+            placeOutgoingCallImmediately(mCall, disposition.callingAddress, null,
                     speakerphoneOn, videoState);
 
             // Don't return but instead continue and send the ACTION_NEW_OUTGOING_CALL broadcast
@@ -312,12 +341,34 @@
             // initiate the call again because of the presence of the EXTRA_ALREADY_CALLED extra.
         }
 
-        if (sendNewOutgoingCallBroadcast) {
+        boolean callRedirectionWithService = false;
+        if (disposition.requestRedirection) {
+            CallRedirectionProcessor callRedirectionProcessor = new CallRedirectionProcessor(
+                    mContext, mCallsManager, mCall, disposition.callingAddress,
+                    mCallsManager.getPhoneAccountRegistrar(),
+                    getGateWayInfoFromIntent(mIntent, mIntent.getData()),
+                    mIntent.getBooleanExtra(TelecomManager.EXTRA_START_CALL_WITH_SPEAKERPHONE,
+                            false),
+                    mIntent.getIntExtra(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
+                            VideoProfile.STATE_AUDIO_ONLY));
+            /**
+             * If there is an available {@link android.telecom.CallRedirectionService}, use the
+             * {@link CallRedirectionProcessor} to perform call redirection instead of using
+             * broadcasting.
+             */
+            callRedirectionWithService = callRedirectionProcessor
+                    .canMakeCallRedirectionWithService();
+            if (callRedirectionWithService) {
+                callRedirectionProcessor.performCallRedirection();
+            }
+        }
+
+        if (disposition.sendBroadcast) {
             UserHandle targetUser = mCall.getInitiatingUser();
             Log.i(this, "Sending NewOutgoingCallBroadcast for %s to %s", mCall, targetUser);
-            broadcastIntent(intent, number, !callImmediately, targetUser);
+            broadcastIntent(mIntent, disposition.number,
+                    !disposition.callImmediately && !callRedirectionWithService, targetUser);
         }
-        return DisconnectCause.NOT_DISCONNECTED;
     }
 
     /**
@@ -467,14 +518,15 @@
     }
 
     /**
-     * Given a call intent and whether or not the number to dial is an emergency number, rewrite
-     * the call intent action to an appropriate one.
+     * Given a call intent and whether or not the number to dial is an emergency number, determine
+     * the appropriate call intent action.
      *
-     * @param intent Intent to rewrite the action for
+     * @param intent Intent to evaluate
      * @param isPotentialEmergencyNumber Whether or not the number is potentially an emergency
      * number.
+     * @return The appropriate action.
      */
-    private void rewriteCallIntentAction(Intent intent, boolean isPotentialEmergencyNumber) {
+    private String calculateCallIntentAction(Intent intent, boolean isPotentialEmergencyNumber) {
         String action = intent.getAction();
 
         /* Change CALL_PRIVILEGED into CALL or CALL_EMERGENCY as needed. */
@@ -487,8 +539,8 @@
                 action = Intent.ACTION_CALL;
             }
             Log.v(this, " - updating action from CALL_PRIVILEGED to %s", action);
-            intent.setAction(action);
         }
+        return action;
     }
 
     private long getDisconnectTimeoutFromApp(Bundle resultExtras, long defaultTimeout) {
diff --git a/src/com/android/server/telecom/NuisanceCallReporter.java b/src/com/android/server/telecom/NuisanceCallReporter.java
new file mode 100644
index 0000000..4140a18
--- /dev/null
+++ b/src/com/android/server/telecom/NuisanceCallReporter.java
@@ -0,0 +1,271 @@
+/*
+ * Copyright (C) 2019 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 android.content.ComponentName;
+import android.content.ContentProvider;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Process;
+import android.os.UserHandle;
+import android.provider.CallLog;
+import android.telecom.CallScreeningService;
+import android.telecom.Log;
+import android.telecom.PhoneAccountHandle;
+import android.text.TextUtils;
+
+import java.util.Arrays;
+
+/**
+ * Responsible for handling reports via
+ * {@link android.telecom.TelecomManager#reportNuisanceCallStatus(Uri, boolean)} as to whether the
+ * user has indicated a call is a nuisance call.
+ *
+ * Since nuisance reports can be initiated from the call log, potentially long after a call has
+ * completed, calls are identified by the {@link Call#getHandle()}.  A nuisance report for a call is
+ * only accepted if:
+ * <ul>
+ *     <li>A missed, incoming, or rejected call to that number exists in the call log.  We want to
+ *     avoid a scenario where a user reports a single outgoing call as a nuisance call.</li>
+ *     <li>The call occurred via a sim-based phone account; we do not want to report nuisance calls
+ *     which only ever took place via a self-managed ConnectionService.  It is, however, valid for
+ *     a number to be contacted both via a sim-based phone account and a self-managed one.</li>
+ *     <li>The {@link CallScreeningService} has provided call identification for calls in the past.
+ *     This provides an incentive for {@link CallScreeningService} implementations to use the caller
+ *     ID APIs appropriately if they are going to benefit from use reports of nuisance and
+ *     non-nuisance calls.</li>
+ * </ul>
+ */
+public class NuisanceCallReporter {
+    /**
+     * Columns we want to retrieve from the call log.
+     */
+    private static final String[] CALL_LOG_PROJECTION = new String[] {
+            CallLog.Calls._ID,
+            CallLog.Calls.DURATION,
+            CallLog.Calls.TYPE,
+            CallLog.Calls.PHONE_ACCOUNT_COMPONENT_NAME,
+            CallLog.Calls.PHONE_ACCOUNT_ID
+    };
+
+    public static final int CALL_LOG_COLUMN_ID =
+            Arrays.asList(CALL_LOG_PROJECTION).indexOf(CallLog.Calls._ID);
+    public static final int CALL_LOG_COLUMN_DURATION =
+            Arrays.asList(CALL_LOG_PROJECTION).indexOf(CallLog.Calls.DURATION);
+    public static final int CALL_LOG_COLUMN_TYPE =
+            Arrays.asList(CALL_LOG_PROJECTION).indexOf(CallLog.Calls.TYPE);
+    public static final int CALL_LOG_COLUMN_PHONE_ACCOUNT_COMPONENT_NAME =
+            Arrays.asList(CALL_LOG_PROJECTION).indexOf(CallLog.Calls.PHONE_ACCOUNT_COMPONENT_NAME);
+    public static final int CALL_LOG_COLUMN_PHONE_ACCOUNT_ID =
+            Arrays.asList(CALL_LOG_PROJECTION).indexOf(CallLog.Calls.PHONE_ACCOUNT_ID);
+
+    /**
+     * Represents information about a nuisance report via
+     * {@link android.telecom.TelecomManager#reportNuisanceCallStatus(Uri, boolean)}.
+     */
+    private static class NuisanceReport {
+        public String callScreeningPackageName;
+        public Uri handle;
+        public boolean isNuisance;
+        public NuisanceReport(String packageName, Uri handle, boolean isNuisance) {
+            this.callScreeningPackageName = packageName;
+            this.handle = handle;
+            this.isNuisance = isNuisance;
+        }
+    }
+
+    /**
+     * Proxy interface to abstract calls to
+     * {@link android.telephony.PhoneNumberUtils#formatNumberToE164(String, String)}.
+     * Facilitates testing.
+     */
+    public interface PhoneNumberUtilsProxy {
+        String formatNumberToE164(String number);
+    }
+
+    /**
+     * Proxy interface to abstract queries to the package manager to determine if a
+     * {@link PhoneAccountHandle} is for a self-managed connection service.
+     */
+    public interface PhoneAccountRegistrarProxy {
+        boolean isSelfManagedConnectionService(PhoneAccountHandle handle);
+    }
+
+    /**
+     * Restrict to call log entries for the specified number where its an incoming, missed, blocked
+     * or rejected call.
+     */
+    private static final String NUMBER_WHERE_CLAUSE =
+            "(" + CallLog.Calls.CACHED_NORMALIZED_NUMBER + " = ? OR "
+                    + CallLog.Calls.NUMBER + " = ?) AND " + CallLog.Calls.TYPE + " IN ("
+                    + CallLog.Calls.INCOMING_TYPE + "," + CallLog.Calls.MISSED_TYPE + ","
+                    + CallLog.Calls.BLOCKED_TYPE + "," + CallLog.Calls.REJECTED_TYPE + ")";
+
+    /**
+     * Call log where clause to find entries with call identification reported by a specified call
+     * screening service.
+     */
+    private static final String CALL_ID_PACKAGE_WHERE_CLAUSE =
+            CallLog.Calls.CALL_ID_PACKAGE_NAME + " = ? ";
+
+    private final Context mContext;
+    private final PhoneNumberUtilsProxy mPhoneNumberUtilsProxy;
+    private final PhoneAccountRegistrarProxy mPhoneAccountRegistrarProxy;
+    private UserHandle mCurrentUserHandle;
+
+    public NuisanceCallReporter(Context context, PhoneNumberUtilsProxy phoneNumberUtilsProxy,
+            PhoneAccountRegistrarProxy phoneAccountRegistrarProxy) {
+        mContext = context;
+        mPhoneNumberUtilsProxy = phoneNumberUtilsProxy;
+        mPhoneAccountRegistrarProxy = phoneAccountRegistrarProxy;
+    }
+
+    public void setCurrentUserHandle(UserHandle userHandle) {
+        if (userHandle == null) {
+            Log.d(this, "setCurrentUserHandle, userHandle = null");
+            userHandle = Process.myUserHandle();
+        }
+        Log.d(this, "setCurrentUserHandle, %s", userHandle);
+        mCurrentUserHandle = userHandle;
+    }
+
+    /**
+     * Given a call handle reported by the default dialer, inform the
+     * {@link android.telecom.CallScreeningService} of whether the user has indicated a call is
+     * or is not a nuisance call.
+     *
+     * @param callScreeningPackageName the package name of the call screening service.
+     * @param handle the handle of the call to report nuisance status on.
+     * @param isNuisance {@code true} if the call is a nuisance call, {@code false} otherwise.
+     */
+    public void reportNuisanceCallStatus(@NonNull String callScreeningPackageName,
+            @NonNull Uri handle, boolean isNuisance) {
+
+        // Don't report the nuisance status to a call screening app if it has not provided any
+        // caller id info in the past.
+        if (!hasCallScreeningServiceProvidedCallId(callScreeningPackageName)) {
+            Log.i(this, "reportNuisanceCallStatus: app %s has not provided caller ID; skipping.",
+                    callScreeningPackageName);
+            return;
+        }
+
+        maybeSendNuisanceReport(new NuisanceReport(callScreeningPackageName, handle, isNuisance));
+    }
+
+    private void maybeSendNuisanceReport(@NonNull NuisanceReport nuisanceReport) {
+        Uri callsUri = CallLog.Calls.CONTENT_URI;
+        if (mCurrentUserHandle == null || nuisanceReport.handle == null) {
+            return;
+        }
+
+        ContentProvider.maybeAddUserId(CallLog.Calls.CONTENT_URI,
+                mCurrentUserHandle.getIdentifier());
+
+        String normalizedNumber = mPhoneNumberUtilsProxy.formatNumberToE164(
+                nuisanceReport.handle.getSchemeSpecificPart());
+        if (TextUtils.isEmpty(normalizedNumber)) {
+            normalizedNumber = nuisanceReport.handle.getSchemeSpecificPart();
+        }
+        Log.d(this, "maybeSendNuisanceReport:  rawNumber=%s, number=%s, isNuisance=%b",
+                Log.piiHandle(nuisanceReport.handle), Log.piiHandle(normalizedNumber),
+                nuisanceReport.isNuisance);
+        // Query the call log for the most recent information about this call.
+        Cursor cursor = mContext.getContentResolver().query(callsUri, CALL_LOG_PROJECTION,
+                NUMBER_WHERE_CLAUSE, new String[] { normalizedNumber,
+                        nuisanceReport.handle.getSchemeSpecificPart() },
+                CallLog.Calls.DEFAULT_SORT_ORDER);
+        if (cursor != null) {
+            try {
+                while (cursor.moveToNext()) {
+                    final long duration = cursor.getLong(CALL_LOG_COLUMN_DURATION);
+                    final int callType = cursor.getInt(CALL_LOG_COLUMN_TYPE);
+                    final String phoneAccountComponentName = cursor.getString(
+                            CALL_LOG_COLUMN_PHONE_ACCOUNT_COMPONENT_NAME);
+                    final String phoneAccountId = cursor.getString(
+                            CALL_LOG_COLUMN_PHONE_ACCOUNT_ID);
+
+                    PhoneAccountHandle handle = new PhoneAccountHandle(
+                            ComponentName.unflattenFromString(phoneAccountComponentName),
+                            phoneAccountId);
+
+                    if (mPhoneAccountRegistrarProxy.isSelfManagedConnectionService(handle)) {
+                        // Skip this call log entry; it was made via a self-managed CS.
+                        Log.d(this, "maybeSendNuisanceReport: skip self-mgd CS %s",
+                                phoneAccountComponentName);
+                        continue;
+                    }
+
+                    sendNuisanceReportIntent(nuisanceReport, duration, callType);
+                    // Stop when we send a nuisance report.
+                    break;
+                }
+            } finally {
+                cursor.close();
+            }
+        }
+    }
+
+    /**
+     * Determines if a {@link CallScreeningService} has provided
+     * {@link android.telecom.CallIdentification} for calls in the past.
+     * @param packageName The package name of the {@link CallScreeningService}.
+     * @return {@code true} if the app has provided call identification, {@code false} otherwise.
+     */
+    private boolean hasCallScreeningServiceProvidedCallId(@NonNull String packageName) {
+        // Query the call log for any entries which have call identification provided by the call
+        // screening package.
+        Cursor cursor = mContext.getContentResolver().query(CallLog.Calls.CONTENT_URI,
+                CALL_LOG_PROJECTION, CALL_ID_PACKAGE_WHERE_CLAUSE, new String[] { packageName },
+                CallLog.Calls.DEFAULT_SORT_ORDER + " LIMIT 1");
+
+        return cursor.getCount() > 0;
+    }
+
+    private void sendNuisanceReportIntent(@NonNull NuisanceReport nuisanceReport, long duration,
+            int callType) {
+        Log.i(this, "handleCallLogResult: number=%s, duration=%d, type=%d",
+                Log.piiHandle(nuisanceReport.handle), duration, callType);
+
+        Intent intent = new Intent(CallScreeningService.ACTION_NUISANCE_CALL_STATUS_CHANGED);
+        intent.setPackage(nuisanceReport.callScreeningPackageName);
+        intent.putExtra(CallScreeningService.EXTRA_CALL_HANDLE, nuisanceReport.handle);
+        intent.putExtra(CallScreeningService.EXTRA_IS_NUISANCE, nuisanceReport.isNuisance);
+        intent.putExtra(CallScreeningService.EXTRA_CALL_TYPE, callType);
+        intent.putExtra(CallScreeningService.EXTRA_CALL_DURATION, getCallDurationBucket(duration));
+        mContext.sendBroadcastAsUser(intent, mCurrentUserHandle);
+    }
+
+    /**
+     * Maps a call duration in milliseconds to a call duration bucket.
+     * @param callDuration Call duration, in milliseconds.
+     * @return The call duration bucket
+     */
+    public @CallScreeningService.CallDuration int getCallDurationBucket(long callDuration) {
+        if (callDuration < 3000L) {
+            return CallScreeningService.CALL_DURATION_VERY_SHORT;
+        } else if (callDuration >= 3000L && callDuration < 60000L) {
+            return CallScreeningService.CALL_DURATION_SHORT;
+        } else if (callDuration >= 6000L && callDuration < 120000L) {
+            return CallScreeningService.CALL_DURATION_MEDIUM;
+        } else {
+            return CallScreeningService.CALL_DURATION_LONG;
+        }
+    }
+}
diff --git a/src/com/android/server/telecom/ParcelableCallUtils.java b/src/com/android/server/telecom/ParcelableCallUtils.java
index 77598c8..3e3ce3d 100644
--- a/src/com/android/server/telecom/ParcelableCallUtils.java
+++ b/src/com/android/server/telecom/ParcelableCallUtils.java
@@ -16,13 +16,22 @@
 
 package com.android.server.telecom;
 
+import static android.telecom.Call.Details.DIRECTION_INCOMING;
+import static android.telecom.Call.Details.DIRECTION_OUTGOING;
+import static android.telecom.Call.Details.DIRECTION_UNKNOWN;
+
 import android.net.Uri;
+import android.os.Bundle;
 import android.telecom.Connection;
+import android.telecom.DisconnectCause;
 import android.telecom.ParcelableCall;
 import android.telecom.ParcelableRttCall;
 import android.telecom.TelecomManager;
+import android.text.TextUtils;
 
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
 import java.util.List;
 
 /**
@@ -31,11 +40,27 @@
 public class ParcelableCallUtils {
     private static final int CALL_STATE_OVERRIDE_NONE = -1;
 
+    /**
+     * A list of extra keys which should be removed from a {@link ParcelableCall} when it is being
+     * generated for the purpose of sending to a dialer other than the system dialer.
+     * By convention we only pass keys namespaced with android.*, however there are some keys which
+     * should not be passed to non-system dialer apps either.
+     */
+    private static List<String> EXTRA_KEYS_TO_SANITIZE;
+    static {
+        EXTRA_KEYS_TO_SANITIZE = new ArrayList<>();
+        EXTRA_KEYS_TO_SANITIZE.add(android.telecom.Connection.EXTRA_SIP_INVITE);
+    }
+
     public static class Converter {
         public ParcelableCall toParcelableCall(Call call, boolean includeVideoProvider,
                 PhoneAccountRegistrar phoneAccountRegistrar) {
             return ParcelableCallUtils.toParcelableCall(
-                    call, includeVideoProvider, phoneAccountRegistrar, false, false);
+                    call, includeVideoProvider, phoneAccountRegistrar, false, false, false);
+        }
+
+        public ParcelableCall toParcelableCallForScreening(Call call) {
+            return ParcelableCallUtils.toParcelableCallForScreening(call);
         }
     }
 
@@ -50,16 +75,23 @@
      * @param phoneAccountRegistrar The {@link PhoneAccountRegistrar}.
      * @param supportsExternalCalls Indicates whether the call should be parcelled for an
      *      {@link InCallService} which supports external calls or not.
+     * @param includeRttCall {@code true} if the RTT call should be included, {@code false}
+     *      otherwise.
+     * @param isForSystemDialer {@code true} if this call is being parcelled for the system dialer,
+     *      {@code false} otherwise.  When parceling for the system dialer, the entire call extras
+     *      is included.  When parceling for anything other than the system dialer, some extra key
+     *      values will be stripped for privacy sake.
      */
     public static ParcelableCall toParcelableCall(
             Call call,
             boolean includeVideoProvider,
             PhoneAccountRegistrar phoneAccountRegistrar,
             boolean supportsExternalCalls,
-            boolean includeRttCall) {
+            boolean includeRttCall,
+            boolean isForSystemDialer) {
         return toParcelableCall(call, includeVideoProvider, phoneAccountRegistrar,
                 supportsExternalCalls, CALL_STATE_OVERRIDE_NONE /* overrideState */,
-                includeRttCall);
+                includeRttCall, isForSystemDialer);
     }
 
     /**
@@ -75,6 +107,10 @@
      *      {@link InCallService} which supports external calls or not.
      * @param overrideState When not {@link #CALL_STATE_OVERRIDE_NONE}, use the provided state as an
      *      override to whatever is defined in the call.
+     * @param isForSystemDialer {@code true} if this call is being parcelled for the system dialer,
+     *      {@code false} otherwise.  When parceling for the system dialer, the entire call extras
+     *      is included.  When parceling for anything other than the system dialer, some extra key
+     *      values will be stripped for privacy sake.
      * @return The {@link ParcelableCall} containing all call information from the {@link Call}.
      */
     public static ParcelableCall toParcelableCall(
@@ -83,7 +119,8 @@
             PhoneAccountRegistrar phoneAccountRegistrar,
             boolean supportsExternalCalls,
             int overrideState,
-            boolean includeRttCall) {
+            boolean includeRttCall,
+            boolean isForSystemDialer) {
         int state;
         if (overrideState == CALL_STATE_OVERRIDE_NONE) {
             state = getParcelableState(call, supportsExternalCalls);
@@ -102,6 +139,10 @@
             properties |= android.telecom.Call.Details.PROPERTY_ENTERPRISE_CALL;
         }
 
+        if (call.getIsVoipAudioMode()) {
+            properties |= android.telecom.Call.Details.PROPERTY_VOIP_AUDIO_MODE;
+        }
+
         // If this is a single-SIM device, the "default SIM" will always be the only SIM.
         boolean isDefaultSmsAccount = phoneAccountRegistrar != null &&
                 phoneAccountRegistrar.isUserSelectedSmsPhoneAccount(call.getTargetPhoneAccount());
@@ -157,6 +198,21 @@
         }
 
         ParcelableRttCall rttCall = includeRttCall ? getParcelableRttCall(call) : null;
+        int callDirection;
+        if (call.isIncoming()) {
+            callDirection = DIRECTION_INCOMING;
+        } else if (call.isUnknown()) {
+            callDirection = DIRECTION_UNKNOWN;
+        } else {
+            callDirection = DIRECTION_OUTGOING;
+        }
+
+        Bundle extras;
+        if (isForSystemDialer) {
+            extras = call.getExtras();
+        } else {
+            extras = sanitizeExtras(call.getExtras());
+        }
 
         return new ParcelableCall(
                 call.getId(),
@@ -183,8 +239,93 @@
                 call.getVideoState(),
                 conferenceableCallIds,
                 call.getIntentExtras(),
-                call.getExtras(),
-                call.getCreationTimeMillis());
+                extras,
+                call.getCreationTimeMillis(),
+                call.getCallIdentification(),
+                callDirection);
+    }
+
+    /**
+     * Creates a ParcelableCall with the bare minimum properties required for a
+     * {@link android.telecom.CallScreeningService}.  We ONLY expose the following:
+     * <ul>
+     *     <li>Call Id (not exposed to public, but needed to associated calls)</li>
+     *     <li>Call directoin</li>
+     *     <li>Creation time</li>
+     *     <li>Connection time</li>
+     *     <li>Handle (phone number)</li>
+     *     <li>Handle (phone number) presentation</li>
+     * </ul>
+     * All other fields are nulled or set to 0 values.
+     * @param call The telecom call to send to a call screening service.
+     * @return Minimal {@link ParcelableCall} to send to the call screening service.
+     */
+    public static ParcelableCall toParcelableCallForScreening(Call call) {
+        Uri handle = call.getHandlePresentation() == TelecomManager.PRESENTATION_ALLOWED ?
+                call.getHandle() : null;
+        int callDirection;
+        if (call.isIncoming()) {
+            callDirection = DIRECTION_INCOMING;
+        } else if (call.isUnknown()) {
+            callDirection = DIRECTION_UNKNOWN;
+        } else {
+            callDirection = DIRECTION_OUTGOING;
+        }
+        return new ParcelableCall(
+                call.getId(),
+                getParcelableState(call, false /* supportsExternalCalls */),
+                new DisconnectCause(DisconnectCause.UNKNOWN),
+                null, /* cannedSmsResponses */
+                0, /* capabilities */
+                0, /* properties */
+                0, /* supportedAudioRoutes */
+                call.getConnectTimeMillis(),
+                handle,
+                call.getHandlePresentation(),
+                null, /* callerDisplayName */
+                0 /* callerDisplayNamePresentation */,
+                null, /* gatewayInfo */
+                null, /* targetPhoneAccount */
+                false, /* includeVideoProvider */
+                null, /* videoProvider */
+                false, /* includeRttCall */
+                null, /* rttCall */
+                null, /* parentCallId */
+                null, /* childCallIds */
+                null, /* statusHints */
+                0, /* videoState */
+                Collections.emptyList(), /* conferenceableCallIds */
+                null, /* intentExtras */
+                null, /* callExtras */
+                call.getCreationTimeMillis(),
+                null /* callIdentification */,
+                callDirection);
+    }
+
+    /**
+     * Sanitize the extras bundle passed in, removing keys which should not be sent to non-system
+     * dialer apps.
+     * @param extras Extras bundle to sanitize.
+     * @return The sanitized extras bundle.
+     */
+    private static Bundle sanitizeExtras(Bundle oldExtras) {
+        if (oldExtras == null) {
+            return new Bundle();
+        }
+        Bundle extras = new Bundle(oldExtras);
+        for (String key : EXTRA_KEYS_TO_SANITIZE) {
+            extras.remove(key);
+        }
+
+        // As a catch-all remove any that don't start with android namespace.
+        Iterator<String> toCheck = extras.keySet().iterator();
+        while (toCheck.hasNext()) {
+            String extraKey = toCheck.next();
+            if (TextUtils.isEmpty(extraKey) || !extraKey.startsWith("android.")) {
+                toCheck.remove();
+            }
+        }
+        return extras;
     }
 
     private static int getParcelableState(Call call, boolean supportsExternalCalls) {
@@ -226,6 +367,8 @@
                 state = android.telecom.Call.STATE_HOLDING;
                 break;
             case CallState.RINGING:
+            case CallState.ANSWERED:
+                // TODO: does in-call UI need to see ANSWERED?
                 state = android.telecom.Call.STATE_RINGING;
                 break;
             case CallState.SELECT_PHONE_ACCOUNT:
@@ -345,7 +488,10 @@
         android.telecom.Call.Details.PROPERTY_ASSISTED_DIALING_USED,
 
         Connection.PROPERTY_IS_RTT,
-        android.telecom.Call.Details.PROPERTY_RTT
+        android.telecom.Call.Details.PROPERTY_RTT,
+
+        Connection.PROPERTY_NETWORK_IDENTIFIED_EMERGENCY_CALL,
+        android.telecom.Call.Details.PROPERTY_NETWORK_IDENTIFIED_EMERGENCY_CALL
     };
 
     private static int convertConnectionToCallProperties(int connectionProperties) {
diff --git a/src/com/android/server/telecom/PhoneAccountRegistrar.java b/src/com/android/server/telecom/PhoneAccountRegistrar.java
index d84fca5..951a5ed 100644
--- a/src/com/android/server/telecom/PhoneAccountRegistrar.java
+++ b/src/com/android/server/telecom/PhoneAccountRegistrar.java
@@ -17,6 +17,7 @@
 package com.android.server.telecom;
 
 import android.Manifest;
+import android.annotation.NonNull;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -125,6 +126,8 @@
                                              PhoneAccountHandle handle) {}
         public void onPhoneAccountUnRegistered(PhoneAccountRegistrar registrar,
                                              PhoneAccountHandle handle) {}
+        public void onPhoneAccountChanged(PhoneAccountRegistrar registrar,
+                PhoneAccount phoneAccount) {}
     }
 
     /**
@@ -135,7 +138,7 @@
         CharSequence getAppLabel(String packageName);
     }
 
-    private static final String FILE_NAME = "phone-account-registrar-state.xml";
+    public static final String FILE_NAME = "phone-account-registrar-state.xml";
     @VisibleForTesting
     public static final int EXPECTED_STATE_VERSION = 9;
 
@@ -332,7 +335,7 @@
                 int subId = getSubscriptionIdForPhoneAccount(accountHandle);
                 mSubscriptionManager.setDefaultVoiceSubId(subId);
             }
-
+            Log.i(this, "setUserSelectedOutgoingPhoneAccount: %s", accountHandle);
             mState.defaultOutgoingAccountHandles
                     .put(userHandle, new DefaultPhoneAccountHandle(userHandle, accountHandle,
                             account.getGroupId()));
@@ -650,6 +653,21 @@
         return getPhoneAccountHandles(0, null, packageName, false, userHandle);
     }
 
+    /**
+     * Determines if a {@link PhoneAccountHandle} is for a self-managed {@link ConnectionService}.
+     * @param handle The handle.
+     * @return {@code true} if for a self-managed {@link ConnectionService}, {@code false}
+     * otherwise.
+     */
+    public boolean isSelfManagedPhoneAccount(@NonNull PhoneAccountHandle handle) {
+        PhoneAccount account = getPhoneAccountUnchecked(handle);
+        if (account == null) {
+            return false;
+        }
+
+        return account.isSelfManaged();
+    }
+
     // TODO: Should we implement an artificial limit for # of accounts associated with a single
     // ComponentName?
     public void registerPhoneAccount(PhoneAccount account) {
@@ -727,6 +745,8 @@
         fireAccountsChanged();
         if (isNewAccount) {
             fireAccountRegistered(account.getAccountHandle());
+        } else {
+            fireAccountChanged(account);
         }
     }
 
@@ -788,6 +808,12 @@
         }
     }
 
+    private void fireAccountChanged(PhoneAccount account) {
+        for (Listener l : mListeners) {
+            l.onPhoneAccountChanged(this, account);
+        }
+    }
+
     private void fireAccountUnRegistered(PhoneAccountHandle handle) {
         for (Listener l : mListeners) {
             l.onPhoneAccountUnRegistered(this, handle);
@@ -815,7 +841,7 @@
         sb.append("[").append(account1.getAccountHandle());
         appendDiff(sb, "addr", Log.piiHandle(account1.getAddress()),
                 Log.piiHandle(account2.getAddress()));
-        appendDiff(sb, "cap", account1.getCapabilities(), account2.getCapabilities());
+        appendDiff(sb, "cap", account1.capabilitiesToString(), account2.capabilitiesToString());
         appendDiff(sb, "hl", account1.getHighlightColor(), account2.getHighlightColor());
         appendDiff(sb, "lbl", account1.getLabel(), account2.getLabel());
         appendDiff(sb, "desc", account1.getShortDescription(), account2.getShortDescription());
diff --git a/src/com/android/server/telecom/PhoneAccountSuggestionHelper.java b/src/com/android/server/telecom/PhoneAccountSuggestionHelper.java
new file mode 100644
index 0000000..438ee68
--- /dev/null
+++ b/src/com/android/server/telecom/PhoneAccountSuggestionHelper.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2018 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.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.content.pm.ServiceInfo;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.telecom.Log;
+import android.telecom.Logging.Session;
+import android.telecom.PhoneAccountHandle;
+import android.telecom.PhoneAccountSuggestion;
+import android.telecom.PhoneAccountSuggestionService;
+import android.telephony.PhoneNumberUtils;
+import android.text.TextUtils;
+
+import com.android.internal.telecom.IPhoneAccountSuggestionCallback;
+import com.android.internal.telecom.IPhoneAccountSuggestionService;
+
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+public class PhoneAccountSuggestionHelper {
+    private static final String TAG = PhoneAccountSuggestionHelper.class.getSimpleName();
+    private static ComponentName sOverrideComponent;
+
+    /**
+     * @return A future (possible already complete) that contains a list of suggestions.
+     */
+    public static CompletableFuture<List<PhoneAccountSuggestion>>
+    bindAndGetSuggestions(Context context, Uri handle,
+            List<PhoneAccountHandle> availablePhoneAccounts) {
+        // Use the default list if there's no handle
+        if (handle == null) {
+            return CompletableFuture.completedFuture(getDefaultSuggestions(availablePhoneAccounts));
+        }
+        String number = PhoneNumberUtils.extractNetworkPortion(handle.getSchemeSpecificPart());
+
+        // Use the default list if there's no service on the device.
+        ServiceInfo suggestionServiceInfo = getSuggestionServiceInfo(context);
+        if (suggestionServiceInfo == null) {
+            return CompletableFuture.completedFuture(getDefaultSuggestions(availablePhoneAccounts));
+        }
+
+        Intent bindIntent = new Intent();
+        bindIntent.setComponent(new ComponentName(suggestionServiceInfo.packageName,
+                suggestionServiceInfo.name));
+
+        final CompletableFuture<List<PhoneAccountSuggestion>> future = new CompletableFuture<>();
+
+        final Session logSession = Log.createSubsession();
+        ServiceConnection serviceConnection = new ServiceConnection() {
+            @Override
+            public void onServiceConnected(ComponentName name, IBinder _service) {
+                Log.continueSession(logSession, "PASH.oSC");
+                try {
+                    IPhoneAccountSuggestionService service =
+                            IPhoneAccountSuggestionService.Stub.asInterface(_service);
+                    // Set up the callback to complete the future once the remote side comes
+                    // back with suggestions
+                    IPhoneAccountSuggestionCallback callback =
+                            new IPhoneAccountSuggestionCallback.Stub() {
+                                @Override
+                                public void suggestPhoneAccounts(String suggestResultNumber,
+                                        List<PhoneAccountSuggestion> suggestions) {
+                                    if (TextUtils.equals(number, suggestResultNumber)) {
+                                        if (suggestions == null) {
+                                            future.complete(
+                                                    getDefaultSuggestions(availablePhoneAccounts));
+                                        } else {
+                                            future.complete(
+                                                    addDefaultsToProvidedSuggestions(
+                                                            suggestions, availablePhoneAccounts));
+                                        }
+                                    }
+                                }
+                            };
+                    try {
+                        service.onAccountSuggestionRequest(callback, number);
+                    } catch (RemoteException e) {
+                        Log.w(TAG, "Cancelling suggestion process due to remote exception");
+                        future.complete(getDefaultSuggestions(availablePhoneAccounts));
+                    }
+                } finally {
+                    Log.endSession();
+                }
+            }
+
+            @Override
+            public void onServiceDisconnected(ComponentName name) {
+                // No locking needed -- CompletableFuture only lets one thread call complete.
+                Log.continueSession(logSession, "PASH.oSD");
+                try {
+                    if (!future.isDone()) {
+                        Log.w(TAG, "Cancelling suggestion process due to service disconnect");
+                    }
+                    future.complete(getDefaultSuggestions(availablePhoneAccounts));
+                } finally {
+                    Log.endSession();
+                }
+            }
+        };
+
+        if (!context.bindService(bindIntent, serviceConnection, Context.BIND_AUTO_CREATE)) {
+            Log.i(TAG, "Cancelling suggestion process due to bind failure.");
+            future.complete(getDefaultSuggestions(availablePhoneAccounts));
+        }
+
+        // Set up a timeout so that we're not waiting forever for the suggestion service.
+        Handler handler = new Handler();
+        handler.postDelayed(() -> {
+                    // No locking needed -- CompletableFuture only lets one thread call complete.
+                    Log.continueSession(logSession, "PASH.timeout");
+                    try {
+                        if (!future.isDone()) {
+                            Log.w(TAG, "Cancelling suggestion process due to timeout");
+                        }
+                        future.complete(getDefaultSuggestions(availablePhoneAccounts));
+                    } finally {
+                        Log.endSession();
+                    }
+                },
+                Timeouts.getPhoneAccountSuggestionServiceTimeout(context.getContentResolver()));
+        return future;
+    }
+
+    private static List<PhoneAccountSuggestion> addDefaultsToProvidedSuggestions(
+            List<PhoneAccountSuggestion> providedSuggestions,
+            List<PhoneAccountHandle> availableAccountHandles) {
+        List<PhoneAccountHandle> handlesInSuggestions = providedSuggestions.stream()
+                .map(PhoneAccountSuggestion::getPhoneAccountHandle)
+                .collect(Collectors.toList());
+        List<PhoneAccountHandle> handlesToFillIn = availableAccountHandles.stream()
+                .filter(handle -> !handlesInSuggestions.contains(handle))
+                .collect(Collectors.toList());
+        List<PhoneAccountSuggestion> suggestionsToAppend = getDefaultSuggestions(handlesToFillIn);
+        return Stream.concat(suggestionsToAppend.stream(), providedSuggestions.stream())
+                .collect( Collectors.toList());
+    }
+
+    private static ServiceInfo getSuggestionServiceInfo(Context context) {
+        PackageManager packageManager = context.getPackageManager();
+        Intent queryIntent = new Intent();
+        queryIntent.setAction(PhoneAccountSuggestionService.SERVICE_INTERFACE);
+
+        List<ResolveInfo> services;
+        if (sOverrideComponent == null) {
+            services = packageManager.queryIntentServices(queryIntent,
+                    PackageManager.MATCH_SYSTEM_ONLY);
+        } else {
+            Log.i(TAG, "Using override component %s", sOverrideComponent);
+            queryIntent.setComponent(sOverrideComponent);
+            services = packageManager.queryIntentServices(queryIntent,
+                    PackageManager.MATCH_ALL);
+        }
+
+        if (services == null || services.size() == 0) {
+            Log.i(TAG, "No acct suggestion services found. Using defaults.");
+            return null;
+        }
+
+        if (services.size() > 1) {
+            Log.w(TAG, "More than acct suggestion service found, cannot get unique service");
+            return null;
+        }
+        return services.get(0).serviceInfo;
+    }
+
+    static void setOverrideServiceName(String flattenedComponentName) {
+        try {
+            sOverrideComponent = TextUtils.isEmpty(flattenedComponentName)
+                    ? null : ComponentName.unflattenFromString(flattenedComponentName);
+        } catch (Exception e) {
+            sOverrideComponent = null;
+            throw e;
+        }
+    }
+
+    private static List<PhoneAccountSuggestion> getDefaultSuggestions(
+            List<PhoneAccountHandle> phoneAccountHandles) {
+        return phoneAccountHandles.stream().map(phoneAccountHandle ->
+                new PhoneAccountSuggestion(phoneAccountHandle,
+                        PhoneAccountSuggestion.REASON_NONE, false)
+        ).collect(Collectors.toList());
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/server/telecom/QuickResponseUtils.java b/src/com/android/server/telecom/QuickResponseUtils.java
index 5f8269d..84d2636 100644
--- a/src/com/android/server/telecom/QuickResponseUtils.java
+++ b/src/com/android/server/telecom/QuickResponseUtils.java
@@ -117,4 +117,48 @@
         Log.d(LOG_TAG, "maybeMigrateLegacyQuickResponses() - Done.");
         return;
     }
+
+    /**
+     * Determine if the user has changed any of the quick responses back to exactly the same text as
+     * the default text.  If they did, clear the preference so we'll rely on the default value and
+     * still be able to re-translate automatically when language changes occur.
+     *
+     * @param context The current context.
+     * @param prefs   The quick response shared prefs.
+     */
+    public static void maybeResetQuickResponses(Context context, SharedPreferences prefs) {
+        final Resources res = context.getResources();
+
+        String defaultResponse1 = res.getString(R.string.respond_via_sms_canned_response_1);
+        String currentValue1 = prefs.getString(QuickResponseUtils.KEY_CANNED_RESPONSE_PREF_1, "");
+        if (currentValue1.equals(defaultResponse1)) {
+            prefs.edit().remove(QuickResponseUtils.KEY_CANNED_RESPONSE_PREF_1).apply();
+            Log.i(QuickResponseUtils.class,
+                    "maybeResetQuickResponses: response 1 is identical to default; clear pref.");
+        }
+
+        String defaultResponse2 = res.getString(R.string.respond_via_sms_canned_response_2);
+        String currentValue2 = prefs.getString(QuickResponseUtils.KEY_CANNED_RESPONSE_PREF_2, "");
+        if (currentValue2.equals(defaultResponse2)) {
+            prefs.edit().remove(QuickResponseUtils.KEY_CANNED_RESPONSE_PREF_2).apply();
+            Log.i(QuickResponseUtils.class,
+                    "maybeResetQuickResponses: response 2 is identical to default; clear pref.");
+        }
+
+        String defaultResponse3 = res.getString(R.string.respond_via_sms_canned_response_3);
+        String currentValue3 = prefs.getString(QuickResponseUtils.KEY_CANNED_RESPONSE_PREF_3, "");
+        if (currentValue3.equals(defaultResponse3)) {
+            prefs.edit().remove(QuickResponseUtils.KEY_CANNED_RESPONSE_PREF_3).apply();
+            Log.i(QuickResponseUtils.class,
+                    "maybeResetQuickResponses: response 3 is identical to default; clear pref.");
+        }
+
+        String defaultResponse4 = res.getString(R.string.respond_via_sms_canned_response_4);
+        String currentValue4 = prefs.getString(QuickResponseUtils.KEY_CANNED_RESPONSE_PREF_4, "");
+        if (currentValue4.equals(defaultResponse4)) {
+            prefs.edit().remove(QuickResponseUtils.KEY_CANNED_RESPONSE_PREF_4).apply();
+            Log.i(QuickResponseUtils.class,
+                    "maybeResetQuickResponses: response 4 is identical to default; clear pref.");
+        }
+    }
 }
diff --git a/src/com/android/server/telecom/RespondViaSmsManager.java b/src/com/android/server/telecom/RespondViaSmsManager.java
index 964f6ad..4c13222 100644
--- a/src/com/android/server/telecom/RespondViaSmsManager.java
+++ b/src/com/android/server/telecom/RespondViaSmsManager.java
@@ -18,10 +18,14 @@
 
 // TODO: Needed for move to system service: import com.android.internal.R;
 import com.android.internal.os.SomeArgs;
-import com.android.internal.telephony.SmsApplication;
 
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.SharedPreferences;
 import android.content.res.Resources;
 import android.os.Handler;
@@ -45,30 +49,37 @@
  * Helper class to manage the "Respond via Message" feature for incoming calls.
  */
 public class RespondViaSmsManager extends CallsManagerListenerBase {
-    private static final int MSG_SHOW_SENT_TOAST = 2;
+    private static final String ACTION_MESSAGE_SENT = "com.android.server.telecom.MESSAGE_SENT";
+
+    private static final class MessageSentReceiver extends BroadcastReceiver {
+        private final String mContactName;
+        private final int mNumMessageParts;
+        private int mNumMessagesSent = 0;
+        MessageSentReceiver(String contactName, int numMessageParts) {
+            mContactName = contactName;
+            mNumMessageParts = numMessageParts;
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (getResultCode() == Activity.RESULT_OK) {
+                mNumMessagesSent++;
+                if (mNumMessagesSent == mNumMessageParts) {
+                    showMessageResultToast(mContactName, context, true);
+                    context.unregisterReceiver(this);
+                }
+            } else {
+                context.unregisterReceiver(this);
+                showMessageResultToast(mContactName, context, false);
+                Log.w(RespondViaSmsManager.class.getSimpleName(),
+                        "Message failed with error %s", getResultCode());
+            }
+        }
+    }
 
     private final CallsManager mCallsManager;
     private final TelecomSystem.SyncRoot mLock;
 
-    private final Handler mHandler = new Handler(Looper.getMainLooper()) {
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MSG_SHOW_SENT_TOAST: {
-                    SomeArgs args = (SomeArgs) msg.obj;
-                    try {
-                        String toastMessage = (String) args.arg1;
-                        Context context = (Context) args.arg2;
-                        showMessageSentToast(toastMessage, context);
-                    } finally {
-                        args.recycle();
-                    }
-                    break;
-                }
-            }
-        }
-    };
-
     public RespondViaSmsManager(CallsManager callsManager, TelecomSystem.SyncRoot lock) {
         mCallsManager = callsManager;
         mLock = lock;
@@ -105,6 +116,11 @@
                 final ArrayList<String> textMessages = new ArrayList<>(
                         QuickResponseUtils.NUM_CANNED_RESPONSES);
 
+                // Where the user has changed a quick response back to the same text as the
+                // original text, clear the shared pref.  This ensures we always load the resource
+                // in the current active language.
+                QuickResponseUtils.maybeResetQuickResponses(context, prefs);
+
                 // Note the default values here must agree with the corresponding
                 // android:defaultValue attributes in respond_via_sms_settings.xml.
                 textMessages.add(0, prefs.getString(QuickResponseUtils.KEY_CANNED_RESPONSE_PREF_1,
@@ -139,13 +155,15 @@
         }
     }
 
-    private void showMessageSentToast(final String phoneNumber, final Context context) {
+    private static void showMessageResultToast(final String phoneNumber,
+            final Context context, boolean success) {
         // ...and show a brief confirmation to the user (since
         // otherwise it's hard to be sure that anything actually
         // happened.)
         final Resources res = context.getResources();
-        final String formatString = res.getString(
-                R.string.respond_via_sms_confirmation_format);
+        final String formatString = res.getString(success
+                ? R.string.respond_via_sms_confirmation_format
+                : R.string.respond_via_sms_failure_format);
         final String confirmationMsg = String.format(formatString, phoneNumber);
         int startingPosition = confirmationMsg.indexOf(phoneNumber);
         int endingPosition = startingPosition + phoneNumber.length();
@@ -187,13 +205,20 @@
 
         SmsManager smsManager = SmsManager.getSmsManagerForSubscriptionId(subId);
         try {
-            smsManager.sendTextMessage(phoneNumber, null, textMessage, null /*sentIntent*/,
-                    null /*deliveryIntent*/);
-
-            SomeArgs args = SomeArgs.obtain();
-            args.arg1 = !TextUtils.isEmpty(contactName) ? contactName : phoneNumber;
-            args.arg2 = context;
-            mHandler.obtainMessage(MSG_SHOW_SENT_TOAST, args).sendToTarget();
+            ArrayList<String> messageParts = smsManager.divideMessage(textMessage);
+            ArrayList<PendingIntent> sentIntents = new ArrayList<>(messageParts.size());
+            for (int i = 0; i < messageParts.size(); i++) {
+                Intent intent = new Intent(ACTION_MESSAGE_SENT);
+                PendingIntent pendingIntent = PendingIntent.getBroadcast(context, i, intent,
+                        PendingIntent.FLAG_ONE_SHOT);
+                sentIntents.add(pendingIntent);
+            }
+            MessageSentReceiver receiver = new MessageSentReceiver(
+                    !TextUtils.isEmpty(contactName) ? contactName : phoneNumber,
+                    messageParts.size());
+            context.registerReceiver(receiver, new IntentFilter(ACTION_MESSAGE_SENT));
+            smsManager.sendMultipartTextMessage(phoneNumber, null, messageParts,
+                    sentIntents/*sentIntent*/, null /*deliveryIntent*/);
         } catch (IllegalArgumentException e) {
             Log.w(RespondViaSmsManager.this, "Couldn't send SMS message: " +
                     e.getMessage());
diff --git a/src/com/android/server/telecom/RespondViaSmsSettings.java b/src/com/android/server/telecom/RespondViaSmsSettings.java
index 2ea204a..3bee5f7 100644
--- a/src/com/android/server/telecom/RespondViaSmsSettings.java
+++ b/src/com/android/server/telecom/RespondViaSmsSettings.java
@@ -54,6 +54,7 @@
 
         getPreferenceManager().setSharedPreferencesName(QuickResponseUtils.SHARED_PREFERENCES_NAME);
         mPrefs = getPreferenceManager().getSharedPreferences();
+        QuickResponseUtils.maybeResetQuickResponses(this, mPrefs);
     }
 
     @Override
@@ -108,6 +109,9 @@
         SharedPreferences.Editor editor = mPrefs.edit();
         editor.putString(pref.getKey(), (String) newValue).commit();
 
+        // If the user just reset the quick response to its original text, clear the pref.
+        QuickResponseUtils.maybeResetQuickResponses(this, mPrefs);
+
         return true;  // means it's OK to update the state of the Preference with the new value
     }
 
diff --git a/src/com/android/server/telecom/Ringer.java b/src/com/android/server/telecom/Ringer.java
index 1d27f45..9baaef0 100644
--- a/src/com/android/server/telecom/Ringer.java
+++ b/src/com/android/server/telecom/Ringer.java
@@ -39,6 +39,15 @@
  */
 @VisibleForTesting
 public class Ringer {
+    public static class VibrationEffectProxy {
+        public VibrationEffect createWaveform(long[] timings, int[] amplitudes, int repeat) {
+            return VibrationEffect.createWaveform(timings, amplitudes, repeat);
+        }
+
+        public VibrationEffect get(Uri ringtoneUri, Context context) {
+            return VibrationEffect.get(ringtoneUri, context);
+        }
+    }
     @VisibleForTesting
     public VibrationEffect mDefaultVibrationEffect;
 
@@ -89,6 +98,7 @@
     private final Context mContext;
     private final Vibrator mVibrator;
     private final InCallController mInCallController;
+    private final VibrationEffectProxy mVibrationEffectProxy;
 
     private InCallTonePlayer mCallWaitingPlayer;
     private RingtoneFactory mRingtoneFactory;
@@ -115,6 +125,7 @@
             AsyncRingtonePlayer asyncRingtonePlayer,
             RingtoneFactory ringtoneFactory,
             Vibrator vibrator,
+            VibrationEffectProxy vibrationEffectProxy,
             InCallController inCallController) {
 
         mSystemSettingsUtil = systemSettingsUtil;
@@ -126,12 +137,13 @@
         mRingtonePlayer = asyncRingtonePlayer;
         mRingtoneFactory = ringtoneFactory;
         mInCallController = inCallController;
+        mVibrationEffectProxy = vibrationEffectProxy;
 
         if (mContext.getResources().getBoolean(R.bool.use_simple_vibration_pattern)) {
-            mDefaultVibrationEffect = VibrationEffect.createWaveform(SIMPLE_VIBRATION_PATTERN,
+            mDefaultVibrationEffect = mVibrationEffectProxy.createWaveform(SIMPLE_VIBRATION_PATTERN,
                     SIMPLE_VIBRATION_AMPLITUDE, REPEAT_SIMPLE_VIBRATION_AT);
         } else {
-            mDefaultVibrationEffect = VibrationEffect.createWaveform(PULSE_PATTERN,
+            mDefaultVibrationEffect = mVibrationEffectProxy.createWaveform(PULSE_PATTERN,
                     PULSE_AMPLITUDE, REPEAT_VIBRATION_AT);
         }
     }
@@ -167,7 +179,7 @@
 
         if (endEarly) {
             if (letDialerHandleRinging) {
-                Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING);
+                Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING, "Dialer handles");
             }
             Log.i(this, "Ending early -- isTheaterModeOn=%s, letDialerHandleRinging=%s, " +
                     "isSelfManaged=%s, hasExternalRinger=%s", isTheaterModeOn,
@@ -188,9 +200,11 @@
             mRingtonePlayer.play(mRingtoneFactory, foregroundCall);
             effect = getVibrationEffectForCall(mRingtoneFactory, foregroundCall);
         } else {
-            Log.i(this, "startRinging: skipping because ringer would not be audible. " +
+            String reason = String.format(
                     "isVolumeOverZero=%s, shouldRingForContact=%s, isRingtonePresent=%s",
                     isVolumeOverZero, shouldRingForContact, isRingtonePresent);
+            Log.i(this, "startRinging: skipping because ringer would not be audible. " + reason);
+            Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING, "Inaudible: " + reason);
             effect = mDefaultVibrationEffect;
         }
 
@@ -209,7 +223,16 @@
         Ringtone ringtone = factory.getRingtone(call);
         Uri ringtoneUri = ringtone != null ? ringtone.getUri() : null;
         if (ringtoneUri != null) {
-            effect = VibrationEffect.get(ringtoneUri, mContext);
+            try {
+                effect = mVibrationEffectProxy.get(ringtoneUri, mContext);
+            } catch (IllegalArgumentException iae) {
+                // Deep in the bowels of the VibrationEffect class it is possible for an
+                // IllegalArgumentException to be thrown if there is an invalid URI specified in the
+                // device config, or a content provider failure.  Rather than crashing the Telecom
+                // process we will just use the default vibration effect.
+                Log.e(this, iae, "getVibrationEffectForCall: failed to get vibration effect");
+                effect = null;
+            }
         }
 
         if (effect == null) {
@@ -219,12 +242,16 @@
     }
 
     public void startCallWaiting(Call call) {
+        startCallWaiting(call, null);
+    }
+
+    public void startCallWaiting(Call call, String reason) {
         if (mSystemSettingsUtil.isTheaterModeOn(mContext)) {
             return;
         }
 
         if (mInCallController.doesConnectedDialerSupportRinging()) {
-            Log.addEvent(call, LogUtils.Events.SKIP_RINGING);
+            Log.addEvent(call, LogUtils.Events.SKIP_RINGING, "Dialer handles");
             return;
         }
 
@@ -238,7 +265,7 @@
         stopRinging();
 
         if (mCallWaitingPlayer == null) {
-            Log.addEvent(call, LogUtils.Events.START_CALL_WAITING_TONE);
+            Log.addEvent(call, LogUtils.Events.START_CALL_WAITING_TONE, reason);
             mCallWaitingCall = call;
             mCallWaitingPlayer =
                     mPlayerFactory.createPlayer(InCallTonePlayer.TONE_CALL_WAITING);
diff --git a/src/com/android/server/telecom/RoleManagerAdapter.java b/src/com/android/server/telecom/RoleManagerAdapter.java
new file mode 100644
index 0000000..f6cdc6c
--- /dev/null
+++ b/src/com/android/server/telecom/RoleManagerAdapter.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2018 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.UserHandle;
+
+import java.util.List;
+
+/**
+ * Provides a means of wrapping {@code RoleManager} operations which Telecom uses to aid in testing
+ * and remove direct dependencies.
+ */
+public interface RoleManagerAdapter {
+    /**
+     * Returns the package name of the app which fills the {@link android.app.role.RoleManager} call
+     * redirection role.
+     * @return the package name of the app filling the role, {@code null} otherwise}.
+     */
+    String getDefaultCallRedirectionApp();
+
+    /**
+     * Override the {@link android.app.role.RoleManager} call redirection app with another value.
+     * Used for testing purposes only.
+     * @param packageName Package name of the app to fill the call redirection role.  Where
+     *                    {@code null}, the override is removed.
+     */
+    void setTestDefaultCallRedirectionApp(String packageName);
+
+    /**
+     * Returns the package name of the app which fills the {@link android.app.role.RoleManager} call
+     * screening role.
+     * @return the package name of the app filling the role, {@code null} otherwise}.
+     */
+    String getDefaultCallScreeningApp();
+
+    /**
+     * Override the {@link android.app.role.RoleManager} call screening app with another value.
+     * Used for testing purposes only.
+     * @param packageName Package name of the app to fill the call screening role.  Where
+     *                    {@code null}, the override is removed.
+     */
+    void setTestDefaultCallScreeningApp(String packageName);
+
+    /**
+     * Retrieves a list of package names of the app(s) which fill the
+     * {@link android.app.role.RoleManager} companion device role.
+     * @return List of package names filling the role, or empty list if there are none.
+     */
+    List<String> getCallCompanionApps();
+
+    /**
+     * Set a package to be added to the list of the {@link android.app.role.RoleManager} companion
+     * apps.  Used for testing purposes only.
+     * @param packageName Package name of the app to be added or removed as an override call
+     *                    companion app.
+     * @param isAdded {@code true} if the specified package should be added, {@code false} if it
+     *                            should be removed.
+     */
+    void addOrRemoveTestCallCompanionApp(String packageName, boolean isAdded);
+
+    /**
+     * Returns the package name of the app which fills the {@link android.app.role.RoleManager}
+     * projection mode role.
+     * @return Package name of the car more app or {@code null} if there are no apps that fill this
+     * role.
+     */
+    String getCarModeDialerApp();
+
+    /**
+     * Override the {@link android.app.role.RoleManager} automotive app with another value.
+     * Used for testing purposes only.
+     * @param packageName Package name of the app to fill the automotive app role.  Where
+     *                    {@code null}, the override is removed.
+     */
+    void setTestAutoModeApp(String packageName);
+
+    /**
+     * Using role manager needs to know the current user handle.  Need to make sure the role manager
+     * adapter can pass this to role manager.  As it changes, we'll pass it in.
+     * @param currentUserHandle The new user handle.
+     */
+    void setCurrentUserHandle(UserHandle currentUserHandle);
+}
diff --git a/src/com/android/server/telecom/RoleManagerAdapterImpl.java b/src/com/android/server/telecom/RoleManagerAdapterImpl.java
new file mode 100644
index 0000000..a53f475
--- /dev/null
+++ b/src/com/android/server/telecom/RoleManagerAdapterImpl.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2018 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.UserHandle;
+
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class RoleManagerAdapterImpl implements RoleManagerAdapter {
+    // TODO: replace with actual role manager const.
+    private static final String ROLE_CALL_REDIRECTION_APP = "android.app.role.PROXY_CALLING_APP";
+    // TODO: replace with actual role manager const.
+    private static final String ROLE_CAR_MODE_DIALER = "android.app.role.ROLE_CAR_MODE_DIALER";
+    // TODO: replace with actual role manager const.
+    private static final String ROLE_CALL_SCREENING = "android.app.role.CALL_SCREENING_APP";
+    // TODO: replace with actual role manager const.
+    private static final String ROLE_CALL_COMPANION_APP =
+            "android.app.role.ROLE_CALL_COMPANION_APP";
+
+    private String mOverrideDefaultCallRedirectionApp = null;
+    private String mOverrideDefaultCallScreeningApp = null;
+    private String mOverrideDefaultCarModeApp = null;
+    private List<String> mOverrideCallCompanionApps = new ArrayList<>();
+    private UserHandle mCurrentUserHandle;
+
+    public RoleManagerAdapterImpl() {
+    }
+
+    @Override
+    public String getDefaultCallRedirectionApp() {
+        if (mOverrideDefaultCallRedirectionApp != null) {
+            return mOverrideDefaultCallRedirectionApp;
+        }
+        return getRoleManagerCallRedirectionApp();
+    }
+
+    @Override
+    public void setTestDefaultCallRedirectionApp(String packageName) {
+        mOverrideDefaultCallRedirectionApp = packageName;
+    }
+
+    @Override
+    public String getDefaultCallScreeningApp() {
+        if (mOverrideDefaultCallScreeningApp != null) {
+            return mOverrideDefaultCallScreeningApp;
+        }
+        return getRoleManagerCallScreeningApp();
+    }
+
+    @Override
+    public void setTestDefaultCallScreeningApp(String packageName) {
+        mOverrideDefaultCallScreeningApp = packageName;
+    }
+
+    @Override
+    public List<String> getCallCompanionApps() {
+        List<String> callCompanionApps = getRoleManagerCallCompanionApps();
+        callCompanionApps.addAll(mOverrideCallCompanionApps);
+        return callCompanionApps;
+    }
+
+    @Override
+    public void addOrRemoveTestCallCompanionApp(String packageName, boolean isAdded) {
+        if (isAdded) {
+            mOverrideCallCompanionApps.add(packageName);
+        } else {
+            mOverrideCallCompanionApps.remove(packageName);
+        }
+    }
+
+    @Override
+    public String getCarModeDialerApp() {
+        if (mOverrideDefaultCarModeApp != null) {
+            return mOverrideDefaultCarModeApp;
+        }
+        return getRoleManagerCarModeDialerApp();
+    }
+
+    @Override
+    public void setTestAutoModeApp(String packageName) {
+        mOverrideDefaultCarModeApp = packageName;
+    }
+
+    @Override
+    public void setCurrentUserHandle(UserHandle currentUserHandle) {
+        mCurrentUserHandle = currentUserHandle;
+    }
+
+    private String getRoleManagerCallScreeningApp() {
+        // TODO: Link in RoleManager
+        return null;
+    }
+
+    private String getRoleManagerCarModeDialerApp() {
+        // TODO: Link in RoleManager
+        return null;
+    }
+
+    private List<String> getRoleManagerCallCompanionApps() {
+        // TODO: Link in RoleManager
+        return Collections.emptyList();
+    }
+
+    private String getRoleManagerCallRedirectionApp() {
+        // TODO: Link in RoleManager
+        return null;
+    }
+
+    /**
+     * Dumps the state of the {@link InCallController}.
+     *
+     * @param pw The {@code IndentingPrintWriter} to write the state to.
+     */
+    public void dump(IndentingPrintWriter pw) {
+        pw.print("DefaultCallRedirectionApp: ");
+        if (mOverrideDefaultCallRedirectionApp != null) {
+            pw.print("(override ");
+            pw.print(mOverrideDefaultCallRedirectionApp);
+            pw.print(") ");
+            pw.print(getRoleManagerCallRedirectionApp());
+        }
+        pw.println();
+
+        pw.print("DefaultCallScreeningApp: ");
+        if (mOverrideDefaultCallScreeningApp != null) {
+            pw.print("(override ");
+            pw.print(mOverrideDefaultCallScreeningApp);
+            pw.print(") ");
+            pw.print(getRoleManagerCallScreeningApp());
+        }
+        pw.println();
+
+        pw.print("DefaultCarModeDialerApp: ");
+        if (mOverrideDefaultCallScreeningApp != null) {
+            pw.print("(override ");
+            pw.print(mOverrideDefaultCarModeApp);
+            pw.print(") ");
+            pw.print(getRoleManagerCarModeDialerApp());
+        }
+        pw.println();
+
+        pw.print("DefaultCallCompanionApps: ");
+        if (mOverrideDefaultCallScreeningApp != null) {
+            pw.print("(override ");
+            pw.print(mOverrideCallCompanionApps.stream().collect(Collectors.joining(", ")));
+            pw.print(") ");
+            List<String> appsInRole = getRoleManagerCallCompanionApps();
+            if (appsInRole != null) {
+                pw.print(appsInRole.stream().collect(Collectors.joining(", ")));
+            }
+        }
+        pw.println();
+    }
+}
diff --git a/src/com/android/server/telecom/ServiceBinder.java b/src/com/android/server/telecom/ServiceBinder.java
index f15570b..a322a5e 100644
--- a/src/com/android/server/telecom/ServiceBinder.java
+++ b/src/com/android/server/telecom/ServiceBinder.java
@@ -39,7 +39,7 @@
  * Subclasses supply the service intent and component name and this class will invoke protected
  * methods when the class is bound, unbound, or upon failure.
  */
-abstract class ServiceBinder {
+public abstract class ServiceBinder {
 
     /**
      * Callback to notify after a binding succeeds or fails.
@@ -73,13 +73,15 @@
             // Reset any abort request if we're asked to bind again.
             clearAbort();
 
-            if (!mCallbacks.isEmpty()) {
-                // Binding already in progress, append to the list of callbacks and bail out.
+            synchronized (mCallbacks) {
+                if (!mCallbacks.isEmpty()) {
+                    // Binding already in progress, append to the list of callbacks and bail out.
+                    mCallbacks.add(callback);
+                    return;
+                }
                 mCallbacks.add(callback);
-                return;
             }
 
-            mCallbacks.add(callback);
             if (mServiceConnection == null) {
                 Intent serviceIntent = new Intent(mServiceAction).setComponent(mComponentName);
                 ServiceConnection connection = new ServiceBinderConnection(call);
@@ -351,10 +353,16 @@
      * outstanding callbacks is cleared afterwards.
      */
     private void handleSuccessfulConnection() {
-        for (BindCallback callback : mCallbacks) {
+        // Make a copy so that we don't have a deadlock inside one of the callbacks.
+        Set<BindCallback> callbacksCopy = new ArraySet<>();
+        synchronized (mCallbacks) {
+            callbacksCopy.addAll(mCallbacks);
+            mCallbacks.clear();
+        }
+
+        for (BindCallback callback : callbacksCopy) {
             callback.onSuccess();
         }
-        mCallbacks.clear();
     }
 
     /**
@@ -362,10 +370,16 @@
      * outstanding callbacks is cleared afterwards.
      */
     private void handleFailedConnection() {
-        for (BindCallback callback : mCallbacks) {
+        // Make a copy so that we don't have a deadlock inside one of the callbacks.
+        Set<BindCallback> callbacksCopy = new ArraySet<>();
+        synchronized (mCallbacks) {
+            callbacksCopy.addAll(mCallbacks);
+            mCallbacks.clear();
+        }
+
+        for (BindCallback callback : callbacksCopy) {
             callback.onFailure();
         }
-        mCallbacks.clear();
     }
 
     /**
diff --git a/src/com/android/server/telecom/SystemSettingsUtil.java b/src/com/android/server/telecom/SystemSettingsUtil.java
index 3c75e4d..97659a8 100644
--- a/src/com/android/server/telecom/SystemSettingsUtil.java
+++ b/src/com/android/server/telecom/SystemSettingsUtil.java
@@ -36,4 +36,14 @@
         return Settings.System.getInt(context.getContentResolver(),
                 Settings.System.VIBRATE_WHEN_RINGING, 0) != 0;
     }
+
+    public boolean isEnhancedCallBlockingEnabled(Context context) {
+        return Settings.System.getInt(context.getContentResolver(),
+                Settings.System.DEBUG_ENABLE_ENHANCED_CALL_BLOCKING, 0) != 0;
+    }
+
+    public boolean setEnhancedCallBlockingEnabled(Context context, boolean enabled) {
+        return Settings.System.putInt(context.getContentResolver(),
+                Settings.System.DEBUG_ENABLE_ENHANCED_CALL_BLOCKING, enabled ? 1 : 0);
+    }
 }
diff --git a/src/com/android/server/telecom/SystemStateHelper.java b/src/com/android/server/telecom/SystemStateHelper.java
new file mode 100644
index 0000000..69a46c6
--- /dev/null
+++ b/src/com/android/server/telecom/SystemStateHelper.java
@@ -0,0 +1,210 @@
+/*
+ * 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 android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.telecom.Log;
+
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Provides various system states to the rest of the telecom codebase.
+ */
+public class SystemStateHelper {
+    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 SystemStateHelper(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;
+    }
+
+    public boolean isDeviceAtEar() {
+        return isDeviceAtEar(mContext);
+    }
+
+    /**
+     * Returns a guess whether the phone is up to the user's ear. Use the proximity sensor and
+     * the gravity sensor to make a guess
+     * @return true if the proximity sensor is activated, the magnitude of gravity in directions
+     *         parallel to the screen is greater than some configurable threshold, and the
+     *         y-component of gravity isn't less than some other configurable threshold.
+     */
+    public static boolean isDeviceAtEar(Context context) {
+        SensorManager sm = context.getSystemService(SensorManager.class);
+        if (sm == null) {
+            return false;
+        }
+        Sensor grav = sm.getDefaultSensor(Sensor.TYPE_GRAVITY);
+        Sensor proximity = sm.getDefaultSensor(Sensor.TYPE_PROXIMITY);
+        if (grav == null || proximity == null) {
+            return false;
+        }
+
+        AtomicBoolean result = new AtomicBoolean(true);
+        CountDownLatch gravLatch = new CountDownLatch(1);
+        CountDownLatch proxLatch = new CountDownLatch(1);
+
+        final double xyGravityThreshold = context.getResources().getFloat(
+                R.dimen.device_on_ear_xy_gravity_threshold);
+        final double yGravityNegativeThreshold = context.getResources().getFloat(
+                R.dimen.device_on_ear_y_gravity_negative_threshold);
+
+        SensorEventListener listener = new SensorEventListener() {
+            @Override
+            public void onSensorChanged(SensorEvent event) {
+                if (event.sensor.getType() == Sensor.TYPE_GRAVITY) {
+                    if (gravLatch.getCount() == 0) {
+                        return;
+                    }
+                    double xyMag = Math.sqrt(event.values[0] * event.values[0]
+                            + event.values[1] * event.values[1]);
+                    if (xyMag < xyGravityThreshold
+                            || event.values[1] < yGravityNegativeThreshold) {
+                        result.set(false);
+                    }
+                    gravLatch.countDown();
+                } else if (event.sensor.getType() == Sensor.TYPE_PROXIMITY) {
+                    if (proxLatch.getCount() == 0) {
+                        return;
+                    }
+                    if (event.values[0] >= proximity.getMaximumRange()) {
+                        result.set(false);
+                    }
+                    proxLatch.countDown();
+                }
+            }
+
+            @Override
+            public void onAccuracyChanged(Sensor sensor, int accuracy) {
+            }
+        };
+
+        try {
+            sm.registerListener(listener, grav, SensorManager.SENSOR_DELAY_FASTEST);
+            sm.registerListener(listener, proximity, SensorManager.SENSOR_DELAY_FASTEST);
+            boolean accelValid = gravLatch.await(100, TimeUnit.MILLISECONDS);
+            boolean proxValid = proxLatch.await(100, TimeUnit.MILLISECONDS);
+            if (accelValid && proxValid) {
+                return result.get();
+            } else {
+                Log.w(SystemStateHelper.class.getSimpleName(),
+                        "Timed out waiting for sensors: %b %b", accelValid, proxValid);
+                return false;
+            }
+        } catch (InterruptedException e) {
+            return false;
+        } finally {
+            sm.unregisterListener(listener);
+        }
+    }
+
+    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/SystemStateProvider.java b/src/com/android/server/telecom/SystemStateProvider.java
deleted file mode 100644
index e1938b1..0000000
--- a/src/com/android/server/telecom/SystemStateProvider.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * 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 android.telecom.Log;
-
-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/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index ea55e63..291f9d0 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -29,16 +29,22 @@
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
 import android.content.ComponentName;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
 import android.content.res.Resources;
 import android.net.Uri;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Process;
 import android.os.UserHandle;
+import android.provider.Settings;
+import android.telecom.CallScreeningService;
 import android.telecom.Log;
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
@@ -78,8 +84,29 @@
         }
     }
 
+    public interface SettingsSecureAdapter {
+        void putStringForUser(ContentResolver resolver, String name, String value, int userHandle);
+
+        String getStringForUser(ContentResolver resolver, String name, int userHandle);
+    }
+
+    static class SettingsSecureAdapterImpl implements SettingsSecureAdapter {
+        @Override
+        public void putStringForUser(ContentResolver resolver, String name, String value,
+            int userHandle) {
+            Settings.Secure.putStringForUser(resolver, name, value, userHandle);
+        }
+
+        @Override
+        public String getStringForUser(ContentResolver resolver, String name, int userHandle) {
+            return Settings.Secure.getStringForUser(resolver, name, userHandle);
+        }
+    }
+
     private static final String TIME_LINE_ARG = "timeline";
     private static final int DEFAULT_VIDEO_STATE = -1;
+    private static final String PERMISSION_HANDLE_CALL_INTENT =
+            "android.permission.HANDLE_CALL_INTENT";
 
     private final ITelecomService.Stub mBinderImpl = new ITelecomService.Stub() {
         @Override
@@ -110,10 +137,14 @@
         }
 
         @Override
-        public PhoneAccountHandle getUserSelectedOutgoingPhoneAccount() {
+        public PhoneAccountHandle getUserSelectedOutgoingPhoneAccount(String callingPackage) {
             synchronized (mLock) {
                 try {
                     Log.startSession("TSI.gUSOPA");
+                    if (!isDialerOrPrivileged(callingPackage, "getDefaultOutgoingPhoneAccount")) {
+                        throw new SecurityException("Only the default dialer, or caller with "
+                                + "READ_PRIVILEGED_PHONE_STATE can call this method.");
+                    }
                     final UserHandle callingUserHandle = Binder.getCallingUserHandle();
                     return mPhoneAccountRegistrar.getUserSelectedOutgoingPhoneAccount(
                             callingUserHandle);
@@ -440,6 +471,11 @@
                         if (account.hasCapabilities(PhoneAccount.CAPABILITY_MULTI_USER)) {
                             enforceRegisterMultiUser();
                         }
+                        Bundle extras = account.getExtras();
+                        if (extras != null
+                                && extras.getBoolean(PhoneAccount.EXTRA_SKIP_CALL_FILTERING)) {
+                            enforceRegisterSkipCallFiltering();
+                        }
                         enforceUserHandleMatchesCaller(account.getAccountHandle());
                         final long token = Binder.clearCallingIdentity();
                         try {
@@ -1445,6 +1481,188 @@
                 Log.endSession();
             }
         }
+
+        /**
+         * See {@link TelecomManager#isInEmergencyCall()}
+         */
+        @Override
+        public boolean isInEmergencyCall() {
+            try {
+                Log.startSession("TSI.iIEC");
+                enforceModifyPermission();
+                synchronized (mLock) {
+                    long token = Binder.clearCallingIdentity();
+                    try {
+                        boolean isInEmergencyCall = mCallsManager.isInEmergencyCall();
+                        Log.i(this, "isInEmergencyCall: %b", isInEmergencyCall);
+                        return isInEmergencyCall;
+                    } finally {
+                        Binder.restoreCallingIdentity(token);
+                    }
+                }
+            } finally {
+                Log.endSession();
+            }
+        }
+
+        /**
+         * See {@link TelecomManager#reportNuisanceCallStatus(Uri, boolean)}
+         */
+        @Override
+        public void reportNuisanceCallStatus(Uri handle, boolean isNuisance,
+                String callingPackage) {
+            try {
+                Log.startSession("TSI.rNCS");
+                if (!isPrivilegedDialerCalling(callingPackage)) {
+                    throw new SecurityException(
+                            "Only the default dialer can report nuisance call status");
+                }
+
+                long token = Binder.clearCallingIdentity();
+                try {
+                    String callScreeningPackageName =
+                            mCallsManager.getRoleManagerAdapter().getDefaultCallScreeningApp();
+
+                    if (!TextUtils.isEmpty(callScreeningPackageName)) {
+                        mNuisanceCallReporter.reportNuisanceCallStatus(callScreeningPackageName,
+                                handle, isNuisance);
+                    }
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+            } finally {
+                Log.endSession();
+            }
+        }
+
+        /**
+         * See {@link TelecomManager#handleCallIntent(Intent)} ()}
+         */
+        @Override
+        public void handleCallIntent(Intent intent) {
+            try {
+                Log.startSession("TSI.hCI");
+                synchronized (mLock) {
+                    mContext.enforceCallingOrSelfPermission(PERMISSION_HANDLE_CALL_INTENT,
+                            "handleCallIntent is for internal use only.");
+
+                    long token = Binder.clearCallingIdentity();
+                    try {
+                        Log.i(this, "handleCallIntent: handling call intent");
+                        mCallIntentProcessorAdapter.processOutgoingCallIntent(mContext,
+                                mCallsManager, intent, null /* callingPackage */);
+                    } finally {
+                        Binder.restoreCallingIdentity(token);
+                    }
+                }
+            } finally {
+                Log.endSession();
+            }
+        }
+
+        @Override
+        public void setTestDefaultCallRedirectionApp(String packageName) {
+            try {
+                Log.startSession("TSI.sTDCRA");
+                enforceModifyPermission();
+                if (!Build.IS_USERDEBUG) {
+                    throw new SecurityException("Test-only API.");
+                }
+                synchronized (mLock) {
+                    long token = Binder.clearCallingIdentity();
+                    try {
+                        mCallsManager.getRoleManagerAdapter().setTestDefaultCallRedirectionApp(
+                                packageName);
+                    } finally {
+                        Binder.restoreCallingIdentity(token);
+                    }
+                }
+            } finally {
+                Log.endSession();
+            }
+        }
+
+        @Override
+        public void setTestDefaultCallScreeningApp(String packageName) {
+            try {
+                Log.startSession("TSI.sTDCSA");
+                enforceModifyPermission();
+                if (!Build.IS_USERDEBUG) {
+                    throw new SecurityException("Test-only API.");
+                }
+                synchronized (mLock) {
+                    long token = Binder.clearCallingIdentity();
+                    try {
+                        mCallsManager.getRoleManagerAdapter().setTestDefaultCallScreeningApp(
+                                packageName);
+                    } finally {
+                        Binder.restoreCallingIdentity(token);
+                    }
+                }
+            } finally {
+                Log.endSession();
+            }
+        }
+
+        @Override
+        public void addOrRemoveTestCallCompanionApp(String packageName, boolean isAdded) {
+            try {
+                Log.startSession("TSI.aORTCCA");
+                enforceModifyPermission();
+                if (!Build.IS_USERDEBUG) {
+                    throw new SecurityException("Test-only API.");
+                }
+                synchronized (mLock) {
+                    long token = Binder.clearCallingIdentity();
+                    try {
+                        mCallsManager.getRoleManagerAdapter().addOrRemoveTestCallCompanionApp(
+                                packageName, isAdded);
+                    } finally {
+                        Binder.restoreCallingIdentity(token);
+                    }
+                }
+            } finally {
+                Log.endSession();
+            }
+        }
+
+        @Override
+        public void setTestAutoModeApp(String packageName) {
+            try {
+                Log.startSession("TSI.sTAMA");
+                enforceModifyPermission();
+                if (!Build.IS_USERDEBUG) {
+                    throw new SecurityException("Test-only API.");
+                }
+                synchronized (mLock) {
+                    long token = Binder.clearCallingIdentity();
+                    try {
+                        mCallsManager.getRoleManagerAdapter().setTestAutoModeApp(packageName);
+                    } finally {
+                        Binder.restoreCallingIdentity(token);
+                    }
+                }
+            } finally {
+                Log.endSession();
+            }
+        }
+
+        @Override
+        public void setTestPhoneAcctSuggestionComponent(String flattenedComponentName) {
+            try {
+                Log.startSession("TSI.sPASA");
+                enforceModifyPermission();
+                if (Binder.getCallingUid() != Process.SHELL_UID
+                        && Binder.getCallingUid() != Process.ROOT_UID) {
+                    throw new SecurityException("Shell-only API.");
+                }
+                synchronized (mLock) {
+                    PhoneAccountSuggestionHelper.setOverrideServiceName(flattenedComponentName);
+                }
+            } finally {
+                Log.endSession();
+            }
+        }
     };
 
     /**
@@ -1490,11 +1708,13 @@
     private AppOpsManager mAppOpsManager;
     private PackageManager mPackageManager;
     private CallsManager mCallsManager;
+    private final NuisanceCallReporter mNuisanceCallReporter;
     private final PhoneAccountRegistrar mPhoneAccountRegistrar;
     private final CallIntentProcessor.Adapter mCallIntentProcessorAdapter;
     private final UserCallIntentProcessorFactory mUserCallIntentProcessorFactory;
     private final DefaultDialerCache mDefaultDialerCache;
     private final SubscriptionManagerAdapter mSubscriptionManagerAdapter;
+    private final SettingsSecureAdapter mSettingsSecureAdapter;
     private final TelecomSystem.SyncRoot mLock;
 
     public TelecomServiceImpl(
@@ -1505,6 +1725,8 @@
             UserCallIntentProcessorFactory userCallIntentProcessorFactory,
             DefaultDialerCache defaultDialerCache,
             SubscriptionManagerAdapter subscriptionManagerAdapter,
+            SettingsSecureAdapter settingsSecureAdapter,
+            NuisanceCallReporter nuisanceCallReporter,
             TelecomSystem.SyncRoot lock) {
         mContext = context;
         mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
@@ -1518,6 +1740,8 @@
         mDefaultDialerCache = defaultDialerCache;
         mCallIntentProcessorAdapter = callIntentProcessorAdapter;
         mSubscriptionManagerAdapter = subscriptionManagerAdapter;
+        mSettingsSecureAdapter = settingsSecureAdapter;
+        mNuisanceCallReporter = nuisanceCallReporter;
     }
 
     public ITelecomService.Stub getBinder() {
@@ -1671,6 +1895,13 @@
         }
     }
 
+    private void enforceRegisterSkipCallFiltering() {
+        if (!isCallerSystemApp()) {
+            throw new SecurityException(
+                "EXTRA_SKIP_CALL_FILTERING is only available to system apps.");
+        }
+    }
+
     private void enforceUserHandleMatchesCaller(PhoneAccountHandle accountHandle) {
         if (!Binder.getCallingUserHandle().equals(accountHandle.getUserHandle())) {
             throw new SecurityException("Calling UserHandle does not match PhoneAccountHandle's");
@@ -1715,6 +1946,19 @@
         }
     }
 
+    private boolean isDialerOrPrivileged(String callingPackage, String message) {
+        // The system/default dialer can always read phone state - so that emergency calls will
+        // still work.
+        if (isPrivilegedDialerCalling(callingPackage)) {
+            return true;
+        }
+
+        mContext.enforceCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE, message);
+        // SKIP checking run-time OP_READ_PHONE_STATE since caller or self has PRIVILEGED
+        // permission
+        return true;
+    }
+
     private boolean isSelfManagedConnectionService(PhoneAccountHandle phoneAccountHandle) {
         if (phoneAccountHandle != null) {
                 PhoneAccount phoneAccount = mPhoneAccountRegistrar.getPhoneAccountUnchecked(
@@ -1787,4 +2031,24 @@
         // If only TX or RX were set (or neither), the video state is valid.
         return remainingState == 0;
     }
+
+    private void broadcastCallScreeningAppChangedIntent(String componentName,
+        boolean isDefault) {
+        if (TextUtils.isEmpty(componentName)) {
+            return;
+        }
+
+        ComponentName broadcastComponentName = ComponentName.unflattenFromString(componentName);
+
+        if (broadcastComponentName != null) {
+            Intent intent = new Intent(TelecomManager
+                .ACTION_DEFAULT_CALL_SCREENING_APP_CHANGED);
+            intent.putExtra(TelecomManager
+                .EXTRA_IS_DEFAULT_CALL_SCREENING_APP, isDefault);
+            intent.putExtra(TelecomManager
+                .EXTRA_DEFAULT_CALL_SCREENING_APP_COMPONENT_NAME, componentName);
+            intent.setPackage(broadcastComponentName.getPackageName());
+            mContext.sendBroadcast(intent);
+        }
+    }
 }
diff --git a/src/com/android/server/telecom/TelecomSystem.java b/src/com/android/server/telecom/TelecomSystem.java
index 3fd0e21..0e3234e 100644
--- a/src/com/android/server/telecom/TelecomSystem.java
+++ b/src/com/android/server/telecom/TelecomSystem.java
@@ -35,10 +35,15 @@
 import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.location.Country;
+import android.location.CountryDetector;
 import android.net.Uri;
+import android.os.Process;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.telecom.Log;
 import android.telecom.PhoneAccountHandle;
+import android.telephony.PhoneNumberUtils;
 
 import java.io.FileNotFoundException;
 import java.io.InputStream;
@@ -96,6 +101,8 @@
                 .addDataAuthority(DialerCodeReceiver.TELECOM_SECRET_CODE_DEBUG_OFF, null);
         DIALER_SECRET_CODE_FILTER
                 .addDataAuthority(DialerCodeReceiver.TELECOM_SECRET_CODE_MARK, null);
+        DIALER_SECRET_CODE_FILTER
+                .addDataAuthority(DialerCodeReceiver.TELECOM_SECRET_CODE_MENU, null);
     }
 
     private static TelecomSystem INSTANCE = null;
@@ -113,6 +120,7 @@
     private final TelecomServiceImpl mTelecomServiceImpl;
     private final ContactsAsyncHelper mContactsAsyncHelper;
     private final DialerCodeReceiver mDialerCodeReceiver;
+    private final NuisanceCallReporter mNuisanceCallReporter;
 
     private boolean mIsBootComplete = false;
 
@@ -126,6 +134,7 @@
                     UserHandle currentUserHandle = new UserHandle(userHandleId);
                     mPhoneAccountRegistrar.setCurrentUserHandle(currentUserHandle);
                     mCallsManager.onUserSwitch(currentUserHandle);
+                    mNuisanceCallReporter.setCurrentUserHandle(currentUserHandle);
                 }
             } finally {
                 Log.endSession();
@@ -193,7 +202,9 @@
             PhoneNumberUtilsAdapter phoneNumberUtilsAdapter,
             IncomingCallNotifier incomingCallNotifier,
             InCallTonePlayer.ToneGeneratorFactory toneGeneratorFactory,
-            ClockProxy clockProxy) {
+            CallAudioRouteStateMachine.Factory callAudioRouteStateMachineFactory,
+            ClockProxy clockProxy,
+            RoleManagerAdapter roleManagerAdapter) {
         mContext = context.getApplicationContext();
         LogUtils.initLogging(mContext);
         DefaultDialerManagerAdapter defaultDialerAdapter =
@@ -227,7 +238,7 @@
                     }
                 });
         BluetoothDeviceManager bluetoothDeviceManager = new BluetoothDeviceManager(mContext,
-                new BluetoothAdapterProxy(), mLock);
+                new BluetoothAdapterProxy());
         BluetoothRouteManager bluetoothRouteManager = new BluetoothRouteManager(mContext, mLock,
                 bluetoothDeviceManager, new Timeouts.Adapter());
         BluetoothStateReceiver bluetoothStateReceiver = new BluetoothStateReceiver(
@@ -235,18 +246,22 @@
         mContext.registerReceiver(bluetoothStateReceiver, BluetoothStateReceiver.INTENT_FILTER);
 
         WiredHeadsetManager wiredHeadsetManager = new WiredHeadsetManager(mContext);
-        SystemStateProvider systemStateProvider = new SystemStateProvider(mContext);
+        SystemStateHelper systemStateHelper = new SystemStateHelper(mContext);
 
         mMissedCallNotifier = missedCallNotifierImplFactory
                 .makeMissedCallNotifierImpl(mContext, mPhoneAccountRegistrar, defaultDialerCache);
 
+        CallerInfoLookupHelper callerInfoLookupHelper =
+                new CallerInfoLookupHelper(context, callerInfoAsyncQueryFactory,
+                        mContactsAsyncHelper, mLock);
+
         EmergencyCallHelper emergencyCallHelper = new EmergencyCallHelper(mContext,
                 mContext.getResources().getString(R.string.ui_default_package), timeoutsAdapter);
 
         InCallControllerFactory inCallControllerFactory = new InCallControllerFactory() {
             @Override
             public InCallController create(Context context, SyncRoot lock,
-                    CallsManager callsManager, SystemStateProvider systemStateProvider,
+                    CallsManager callsManager, SystemStateHelper systemStateProvider,
                     DefaultDialerCache defaultDialerCache, Timeouts.Adapter timeoutsAdapter,
                     EmergencyCallHelper emergencyCallHelper) {
                 return new InCallController(context, lock, callsManager, systemStateProvider,
@@ -257,8 +272,7 @@
         mCallsManager = new CallsManager(
                 mContext,
                 mLock,
-                mContactsAsyncHelper,
-                callerInfoAsyncQueryFactory,
+                callerInfoLookupHelper,
                 mMissedCallNotifier,
                 mPhoneAccountRegistrar,
                 headsetMediaButtonFactory,
@@ -268,7 +282,7 @@
                 audioServiceFactory,
                 bluetoothRouteManager,
                 wiredHeadsetManager,
-                systemStateProvider,
+                systemStateHelper,
                 defaultDialerCache,
                 timeoutsAdapter,
                 asyncRingtonePlayer,
@@ -277,7 +291,10 @@
                 toneGeneratorFactory,
                 clockProxy,
                 bluetoothStateReceiver,
-                inCallControllerFactory);
+                callAudioRouteStateMachineFactory,
+                new CallAudioModeStateMachine.Factory(),
+                inCallControllerFactory,
+                roleManagerAdapter);
 
         mIncomingCallNotifier = incomingCallNotifier;
         incomingCallNotifier.setCallsManagerProxy(new IncomingCallNotifier.CallsManagerProxy() {
@@ -316,6 +333,41 @@
         mContext.registerReceiver(mDialerCodeReceiver, DIALER_SECRET_CODE_FILTER,
                 Manifest.permission.CONTROL_INCALL_EXPERIENCE, null);
 
+        mNuisanceCallReporter = new NuisanceCallReporter(mContext,
+                new NuisanceCallReporter.PhoneNumberUtilsProxy() {
+                    @Override
+                    public String formatNumberToE164(String number) {
+                        final CountryDetector detector =
+                                (CountryDetector) context.getSystemService(
+                                        Context.COUNTRY_DETECTOR);
+                        if (detector != null) {
+                            final Country country = detector.detectCountry();
+                            if (country != null) {
+                                String countryIso = country.getCountryIso();
+                                return PhoneNumberUtils.formatNumberToE164(number,
+                                        countryIso);
+                            }
+                        }
+                        return number;
+                    }
+                },
+                new NuisanceCallReporter.PhoneAccountRegistrarProxy() {
+                    @Override
+                    public boolean isSelfManagedConnectionService(
+                            PhoneAccountHandle handle) {
+                        // Sync access to the PhoneAccountRegistrar on Telecom lock.
+                        synchronized (mLock) {
+                            return mPhoneAccountRegistrar.isSelfManagedPhoneAccount(handle);
+                        }
+                    }
+                });
+
+        // There is no USER_SWITCHED broadcast for user 0, handle it here explicitly.
+        final UserManager userManager = UserManager.get(mContext);
+        if (userManager.isPrimaryUser()) {
+            mNuisanceCallReporter.setCurrentUserHandle(Process.myUserHandle());
+        }
+
         mTelecomServiceImpl = new TelecomServiceImpl(
                 mContext, mCallsManager, mPhoneAccountRegistrar,
                 new CallIntentProcessor.AdapterImpl(),
@@ -327,6 +379,8 @@
                 },
                 defaultDialerCache,
                 new TelecomServiceImpl.SubscriptionManagerAdapterImpl(),
+                new TelecomServiceImpl.SettingsSecureAdapterImpl(),
+                mNuisanceCallReporter,
                 mLock);
         Log.endSession();
     }
diff --git a/src/com/android/server/telecom/Timeouts.java b/src/com/android/server/telecom/Timeouts.java
index 5187641..37f9363 100644
--- a/src/com/android/server/telecom/Timeouts.java
+++ b/src/com/android/server/telecom/Timeouts.java
@@ -18,6 +18,7 @@
 
 import android.content.ContentResolver;
 import android.provider.Settings;
+import android.telecom.CallRedirectionService;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -49,6 +50,18 @@
         public long getEmergencyCallbackWindowMillis(ContentResolver cr) {
             return Timeouts.getEmergencyCallbackWindowMillis(cr);
         }
+
+        public long getUserDefinedCallRedirectionTimeoutMillis(ContentResolver cr) {
+            return Timeouts.getUserDefinedCallRedirectionTimeoutMillis(cr);
+        }
+
+        public long getCarrierCallRedirectionTimeoutMillis(ContentResolver cr) {
+            return Timeouts.getCarrierCallRedirectionTimeoutMillis(cr);
+        }
+
+        public long getPhoneAccountSuggestionServiceTimeout(ContentResolver cr) {
+            return Timeouts.getPhoneAccountSuggestionServiceTimeout(cr);
+        }
     }
 
     /** A prefix to use for all keys so to not clobber the global namespace. */
@@ -143,6 +156,14 @@
     }
 
     /**
+     * Returns the amount of time to wait for the phone account suggestion service to reply.
+     */
+    public static long getPhoneAccountSuggestionServiceTimeout(ContentResolver contentResolver) {
+        return get(contentResolver, "phone_account_suggestion_service_timeout",
+                5000L /* 5 seconds */);
+    }
+
+    /**
      * Returns the amount of time to wait for the call screening service to allow or disallow a
      * call.
      */
@@ -158,4 +179,23 @@
       return get(contentResolver, "emergency_callback_window_millis",
           TimeUnit.MILLISECONDS.convert(5, TimeUnit.MINUTES));
     }
+
+    /**
+     * Returns the amount of time for an user-defined {@link CallRedirectionService}.
+     *
+     * @param contentResolver The content resolved.
+     */
+    public static long getUserDefinedCallRedirectionTimeoutMillis(ContentResolver contentResolver) {
+        return get(contentResolver, "user_defined_call_redirection_timeout",
+            5000L /* 5 seconds */);
+    }
+
+    /**
+     * Returns the amount of time for a carrier {@link CallRedirectionService}.
+     *
+     * @param contentResolver The content resolved.
+     */
+    public static long getCarrierCallRedirectionTimeoutMillis(ContentResolver contentResolver) {
+        return get(contentResolver, "carrier_call_redirection_timeout", 5000L /* 5 seconds */);
+    }
 }
diff --git a/src/com/android/server/telecom/VideoProviderProxy.java b/src/com/android/server/telecom/VideoProviderProxy.java
index 7d90a6d..364e0f4 100644
--- a/src/com/android/server/telecom/VideoProviderProxy.java
+++ b/src/com/android/server/telecom/VideoProviderProxy.java
@@ -20,7 +20,6 @@
 import android.app.AppOpsManager;
 import android.content.Context;
 import android.net.Uri;
-import android.os.Binder;
 import android.os.Build;
 import android.os.IBinder;
 import android.os.Looper;
@@ -33,6 +32,7 @@
 import android.text.TextUtils;
 import android.view.Surface;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telecom.IVideoCallback;
 import com.android.internal.telecom.IVideoProvider;
 
@@ -40,8 +40,6 @@
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
-import static android.Manifest.permission.CALL_PHONE;
-
 /**
  * Proxies video provider messages from {@link InCallService.VideoCall}
  * implementations to the underlying {@link Connection.VideoProvider} implementation.  Also proxies
@@ -55,7 +53,7 @@
     /**
      * Listener for Telecom components interested in callbacks from the video provider.
      */
-    interface Listener {
+    public interface Listener {
         void onSessionModifyRequestReceived(Call call, VideoProfile videoProfile);
     }
 
@@ -112,7 +110,7 @@
      * @param call The current call.
      * @throws RemoteException Remote exception.
      */
-    VideoProviderProxy(TelecomSystem.SyncRoot lock,
+    public VideoProviderProxy(TelecomSystem.SyncRoot lock,
             IVideoProvider videoProvider, Call call, CurrentUserProxy currentUserProxy)
             throws RemoteException {
 
@@ -136,11 +134,16 @@
         }
     }
 
+    @VisibleForTesting
+    public VideoCallListenerBinder getVideoCallListenerBinder() {
+        return mVideoCallListenerBinder;
+    }
+
     /**
      * IVideoCallback stub implementation.  An instance of this class receives callbacks from the
      * {@code ConnectionService}'s video provider.
      */
-    private final class VideoCallListenerBinder extends IVideoCallback.Stub {
+    public final class VideoCallListenerBinder extends IVideoCallback.Stub {
         /**
          * Proxies a request from the {@link #mConectionServiceVideoProvider} to the
          * {@link InCallService} when a session modification request is received.
@@ -160,13 +163,14 @@
                             Analytics.RECEIVE_REMOTE_SESSION_MODIFY_REQUEST,
                             videoProfile.getVideoState());
 
-                    if (!mCall.isVideoCallingSupported() &&
-                            VideoProfile.isVideo(videoProfile.getVideoState())) {
-                        // If video calling is not supported by the phone account, and we receive
-                        // a request to upgrade to video, automatically reject it without informing
-                        // the InCallService.
-
-                        Log.addEvent(mCall, LogUtils.Events.SEND_VIDEO_RESPONSE, "video not supported");
+                    if ((!mCall.isVideoCallingSupportedByPhoneAccount()
+                            || !mCall.isLocallyVideoCapable())
+                            && VideoProfile.isVideo(videoProfile.getVideoState())) {
+                        // If video calling is not supported by the phone account, or is not
+                        // locally video capable and we receive a request to upgrade to video,
+                        // automatically reject it without informing the InCallService.
+                        Log.addEvent(mCall, LogUtils.Events.SEND_VIDEO_RESPONSE,
+                                "video not supported");
                         VideoProfile responseProfile = new VideoProfile(
                                 VideoProfile.STATE_AUDIO_ONLY);
                         try {
@@ -434,6 +438,11 @@
             logFromInCall("sendSessionModifyRequest: from=" + fromProfile + " to=" + toProfile);
             Log.addEvent(mCall, LogUtils.Events.SEND_VIDEO_REQUEST,
                     VideoProfile.videoStateToString(toProfile.getVideoState()));
+            if (!VideoProfile.isVideo(fromProfile.getVideoState())
+                    && VideoProfile.isVideo(toProfile.getVideoState())) {
+                // Upgrading to video; change to speaker potentially.
+                mCall.maybeEnableSpeakerForVideoUpgrade(toProfile.getVideoState());
+            }
             mCall.getAnalytics().addVideoEvent(
                     Analytics.SEND_LOCAL_SESSION_MODIFY_REQUEST,
                     toProfile.getVideoState());
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java b/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
index ce35587..4800832 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
@@ -18,25 +18,22 @@
 
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadset;
-import android.bluetooth.BluetoothManager;
+import android.bluetooth.BluetoothHearingAid;
 import android.bluetooth.BluetoothProfile;
-import android.content.BroadcastReceiver;
 import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
 import android.telecom.Log;
 
 import com.android.server.telecom.BluetoothAdapterProxy;
 import com.android.server.telecom.BluetoothHeadsetProxy;
-import com.android.server.telecom.TelecomSystem;
 
+import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
 import java.util.LinkedList;
 import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.stream.Collectors;
+import java.util.Set;
 
 public class BluetoothDeviceManager {
     private final BluetoothProfile.ServiceListener mBluetoothProfileServiceListener =
@@ -50,8 +47,12 @@
                                 mBluetoothHeadsetService =
                                         new BluetoothHeadsetProxy((BluetoothHeadset) proxy);
                                 Log.i(this, "- Got BluetoothHeadset: " + mBluetoothHeadsetService);
+                            } else if (profile == BluetoothProfile.HEARING_AID) {
+                                mBluetoothHearingAidService = (BluetoothHearingAid) proxy;
+                                Log.i(this, "- Got BluetoothHearingAid: "
+                                        + mBluetoothHearingAidService);
                             } else {
-                                Log.w(this, "Connected to non-headset bluetooth service." +
+                                Log.w(this, "Connected to non-requested bluetooth service." +
                                         " Not changing bluetooth headset.");
                             }
                         }
@@ -65,12 +66,25 @@
                     Log.startSession("BMSL.oSD");
                     try {
                         synchronized (mLock) {
-                            mBluetoothHeadsetService = null;
-                            Log.i(BluetoothDeviceManager.this, "Lost BluetoothHeadset service. " +
-                                    "Removing all tracked devices.");
+                            LinkedHashMap<String, BluetoothDevice> lostServiceDevices;
+                            if (profile == BluetoothProfile.HEADSET) {
+                                mBluetoothHeadsetService = null;
+                                Log.i(BluetoothDeviceManager.this,
+                                        "Lost BluetoothHeadset service. " +
+                                                "Removing all tracked devices.");
+                                lostServiceDevices = mHfpDevicesByAddress;
+                            } else if (profile == BluetoothProfile.HEARING_AID) {
+                                mBluetoothHearingAidService = null;
+                                Log.i(BluetoothDeviceManager.this,
+                                        "Lost BluetoothHearingAid service. " +
+                                                "Removing all tracked devices.");
+                                lostServiceDevices = mHearingAidDevicesByAddress;
+                            } else {
+                                return;
+                            }
                             List<BluetoothDevice> devicesToRemove = new LinkedList<>(
-                                    mConnectedDevicesByAddress.values());
-                            mConnectedDevicesByAddress.clear();
+                                    lostServiceDevices.values());
+                            lostServiceDevices.clear();
                             for (BluetoothDevice device : devicesToRemove) {
                                 mBluetoothRouteManager.onDeviceLost(device.getAddress());
                             }
@@ -81,20 +95,27 @@
                 }
            };
 
-    private final LinkedHashMap<String, BluetoothDevice> mConnectedDevicesByAddress =
+    private final LinkedHashMap<String, BluetoothDevice> mHfpDevicesByAddress =
             new LinkedHashMap<>();
-    private final TelecomSystem.SyncRoot mLock;
+    private final LinkedHashMap<String, BluetoothDevice> mHearingAidDevicesByAddress =
+            new LinkedHashMap<>();
+    private final LinkedHashMap<BluetoothDevice, Long> mHearingAidDeviceSyncIds =
+            new LinkedHashMap<>();
+
+    // This lock only protects internal state -- it doesn't lock on anything going into Telecom.
+    private final Object mLock = new Object();
 
     private BluetoothRouteManager mBluetoothRouteManager;
     private BluetoothHeadsetProxy mBluetoothHeadsetService;
+    private BluetoothHearingAid mBluetoothHearingAidService;
+    private BluetoothDevice mBluetoothHearingAidActiveDeviceCache;
 
-    public BluetoothDeviceManager(Context context, BluetoothAdapterProxy bluetoothAdapter,
-            TelecomSystem.SyncRoot lock) {
-        mLock = lock;
-
+    public BluetoothDeviceManager(Context context, BluetoothAdapterProxy bluetoothAdapter) {
         if (bluetoothAdapter != null) {
             bluetoothAdapter.getProfileProxy(context, mBluetoothProfileServiceListener,
                     BluetoothProfile.HEADSET);
+            bluetoothAdapter.getProfileProxy(context, mBluetoothProfileServiceListener,
+                    BluetoothProfile.HEARING_AID);
         }
     }
 
@@ -103,52 +124,172 @@
     }
 
     public int getNumConnectedDevices() {
-        return mConnectedDevicesByAddress.size();
+        synchronized (mLock) {
+            return mHfpDevicesByAddress.size() + mHearingAidDevicesByAddress.size();
+        }
     }
 
     public Collection<BluetoothDevice> getConnectedDevices() {
-        return mConnectedDevicesByAddress.values();
+        synchronized (mLock) {
+            ArrayList<BluetoothDevice> result = new ArrayList<>(mHfpDevicesByAddress.values());
+            result.addAll(mHearingAidDevicesByAddress.values());
+            return Collections.unmodifiableCollection(result);
+        }
     }
 
-    public String getMostRecentlyConnectedDevice(String excludeAddress) {
-        String result = null;
-        synchronized (mLock) {
-            for (String addr : mConnectedDevicesByAddress.keySet()) {
-                if (!Objects.equals(addr, excludeAddress)) {
-                    result = addr;
+    // Same as getConnectedDevices except it filters out the hearing aid devices that are linked
+    // together by their hiSyncId.
+    public Collection<BluetoothDevice> getUniqueConnectedDevices() {
+        ArrayList<BluetoothDevice> result = new ArrayList<>(mHfpDevicesByAddress.values());
+        Set<Long> seenHiSyncIds = new LinkedHashSet<>();
+        // Add the left-most active device to the seen list so that we match up with the list
+        // generated in BluetoothRouteManager.
+        if (mBluetoothHearingAidService != null) {
+            for (BluetoothDevice device : mBluetoothHearingAidService.getActiveDevices()) {
+                if (device != null) {
+                    result.add(device);
+                    seenHiSyncIds.add(mHearingAidDeviceSyncIds.getOrDefault(device, -1L));
+                    break;
                 }
             }
         }
-        return result;
+        synchronized (mLock) {
+            for (BluetoothDevice d : mHearingAidDevicesByAddress.values()) {
+                long hiSyncId = mHearingAidDeviceSyncIds.getOrDefault(d, -1L);
+                if (seenHiSyncIds.contains(hiSyncId)) {
+                    continue;
+                }
+                result.add(d);
+                seenHiSyncIds.add(hiSyncId);
+            }
+        }
+        return Collections.unmodifiableCollection(result);
     }
 
     public BluetoothHeadsetProxy getHeadsetService() {
         return mBluetoothHeadsetService;
     }
 
+    public BluetoothHearingAid getHearingAidService() {
+        return mBluetoothHearingAidService;
+    }
+
     public void setHeadsetServiceForTesting(BluetoothHeadsetProxy bluetoothHeadset) {
         mBluetoothHeadsetService = bluetoothHeadset;
     }
 
-    public BluetoothDevice getDeviceFromAddress(String address) {
-        return mConnectedDevicesByAddress.get(address);
+    public void setHearingAidServiceForTesting(BluetoothHearingAid bluetoothHearingAid) {
+        mBluetoothHearingAidService = bluetoothHearingAid;
     }
 
-    void onDeviceConnected(BluetoothDevice device) {
+    void onDeviceConnected(BluetoothDevice device, boolean isHearingAid) {
         synchronized (mLock) {
-            if (!mConnectedDevicesByAddress.containsKey(device.getAddress())) {
-                mConnectedDevicesByAddress.put(device.getAddress(), device);
+            LinkedHashMap<String, BluetoothDevice> targetDeviceMap;
+            if (isHearingAid) {
+                if (mBluetoothHearingAidService == null) {
+                    Log.w(this, "Hearing aid service null when receiving device added broadcast");
+                    return;
+                }
+                long hiSyncId = mBluetoothHearingAidService.getHiSyncId(device);
+                mHearingAidDeviceSyncIds.put(device, hiSyncId);
+                targetDeviceMap = mHearingAidDevicesByAddress;
+            } else {
+                if (mBluetoothHeadsetService == null) {
+                    Log.w(this, "Headset service null when receiving device added broadcast");
+                    return;
+                }
+                targetDeviceMap = mHfpDevicesByAddress;
+            }
+            if (!targetDeviceMap.containsKey(device.getAddress())) {
+                targetDeviceMap.put(device.getAddress(), device);
                 mBluetoothRouteManager.onDeviceAdded(device.getAddress());
             }
         }
     }
 
-    void onDeviceDisconnected(BluetoothDevice device) {
+    void onDeviceDisconnected(BluetoothDevice device, boolean isHearingAid) {
         synchronized (mLock) {
-            if (mConnectedDevicesByAddress.containsKey(device.getAddress())) {
-                mConnectedDevicesByAddress.remove(device.getAddress());
+            LinkedHashMap<String, BluetoothDevice> targetDeviceMap;
+            if (isHearingAid) {
+                mHearingAidDeviceSyncIds.remove(device);
+                targetDeviceMap = mHearingAidDevicesByAddress;
+            } else {
+                targetDeviceMap = mHfpDevicesByAddress;
+            }
+            if (targetDeviceMap.containsKey(device.getAddress())) {
+                targetDeviceMap.remove(device.getAddress());
                 mBluetoothRouteManager.onDeviceLost(device.getAddress());
             }
         }
     }
+
+    public void disconnectAudio() {
+        if (mBluetoothHearingAidService == null) {
+            Log.w(this, "Trying to disconnect audio but no hearing aid service exists");
+        } else {
+            for (BluetoothDevice device : mBluetoothHearingAidService.getActiveDevices()) {
+                if (device != null) {
+                    mBluetoothHearingAidService.setActiveDevice(null);
+                }
+            }
+        }
+        disconnectSco();
+    }
+
+    public void disconnectSco() {
+        if (mBluetoothHeadsetService == null) {
+            Log.w(this, "Trying to disconnect audio but no headset service exists.");
+        } else {
+            mBluetoothHeadsetService.disconnectAudio();
+        }
+    }
+
+    // Connect audio to the bluetooth device at address, checking to see whether it's a hearing aid
+    // or a HFP device, and using the proper BT API.
+    public boolean connectAudio(String address) {
+        if (mHearingAidDevicesByAddress.containsKey(address)) {
+            if (mBluetoothHearingAidService == null) {
+                Log.w(this, "Attempting to turn on audio when the hearing aid service is null");
+                return false;
+            }
+            return mBluetoothHearingAidService.setActiveDevice(
+                    mHearingAidDevicesByAddress.get(address));
+        } else if (mHfpDevicesByAddress.containsKey(address)) {
+            BluetoothDevice device = mHfpDevicesByAddress.get(address);
+            if (mBluetoothHeadsetService == null) {
+                Log.w(this, "Attempting to turn on audio when the headset service is null");
+                return false;
+            }
+            boolean success = mBluetoothHeadsetService.setActiveDevice(device);
+            if (!success) {
+                Log.w(this, "Couldn't set active device to %s", address);
+                return false;
+            }
+            if (!mBluetoothHeadsetService.isAudioOn()) {
+                return mBluetoothHeadsetService.connectAudio();
+            }
+            return true;
+        } else {
+            Log.w(this, "Attempting to turn on audio for a disconnected device");
+            return false;
+        }
+    }
+
+    public void cacheHearingAidDevice() {
+        if (mBluetoothHearingAidService != null) {
+             for (BluetoothDevice device : mBluetoothHearingAidService.getActiveDevices()) {
+                 if (device != null) {
+                     mBluetoothHearingAidActiveDeviceCache = device;
+                 }
+             }
+        }
+    }
+
+    public void restoreHearingAidDevice() {
+        if (mBluetoothHearingAidActiveDeviceCache != null && mBluetoothHearingAidService != null) {
+            mBluetoothHearingAidService.setActiveDevice(mBluetoothHearingAidActiveDeviceCache);
+            mBluetoothHearingAidActiveDeviceCache = null;
+        }
+    }
+
 }
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java b/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
index f72c342..e53488c 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
@@ -18,6 +18,7 @@
 
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothHearingAid;
 import android.content.Context;
 import android.os.Message;
 import android.telecom.Log;
@@ -33,13 +34,10 @@
 import com.android.server.telecom.TelecomSystem;
 import com.android.server.telecom.Timeouts;
 
-import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedHashSet;
-import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
@@ -57,8 +55,8 @@
          put(CONNECT_HFP, "CONNECT_HFP");
          put(DISCONNECT_HFP, "DISCONNECT_HFP");
          put(RETRY_HFP_CONNECTION, "RETRY_HFP_CONNECTION");
-         put(HFP_IS_ON, "HFP_IS_ON");
-         put(HFP_LOST, "HFP_LOST");
+         put(BT_AUDIO_IS_ON, "BT_AUDIO_IS_ON");
+         put(BT_AUDIO_LOST, "BT_AUDIO_LOST");
          put(CONNECTION_TIMEOUT, "CONNECTION_TIMEOUT");
          put(GET_CURRENT_STATE, "GET_CURRENT_STATE");
          put(RUN_RUNNABLE, "RUN_RUNNABLE");
@@ -77,6 +75,12 @@
         void onBluetoothActiveDeviceGone();
         void onBluetoothAudioConnected();
         void onBluetoothAudioDisconnected();
+        /**
+         * This gets called when we get an unexpected state change from Bluetooth. Their stack does
+         * weird things sometimes, so this is really a signal for the listener to refresh their
+         * internal state and make sure it matches up with what the BT stack is doing.
+         */
+        void onUnexpectedBluetoothStateChange();
     }
 
     /**
@@ -97,9 +101,9 @@
     public static final int RETRY_HFP_CONNECTION = 102;
 
     // arg2: the address of the device that is on
-    public static final int HFP_IS_ON = 200;
-    // arg2: the address of the device that lost HFP
-    public static final int HFP_LOST = 201;
+    public static final int BT_AUDIO_IS_ON = 200;
+    // arg2: the address of the device that lost BT audio
+    public static final int BT_AUDIO_LOST = 201;
 
     // No args; only used internally
     public static final int CONNECTION_TIMEOUT = 300;
@@ -125,8 +129,9 @@
             BluetoothDevice erroneouslyConnectedDevice = getBluetoothAudioConnectedDevice();
             if (erroneouslyConnectedDevice != null) {
                 Log.w(LOG_TAG, "Entering AudioOff state but device %s appears to be connected. " +
-                        "Disconnecting.", erroneouslyConnectedDevice);
-                disconnectAudio();
+                        "Switching to audio-on state for that device.", erroneouslyConnectedDevice);
+                // change this to just transition to the new audio on state
+                transitionToActualState();
             }
             cleanupStatesForDisconnectedDevices();
             if (mListener != null) {
@@ -151,7 +156,7 @@
                         removeDevice((String) args.arg2);
                         break;
                     case CONNECT_HFP:
-                        String actualAddress = connectHfpAudio((String) args.arg2);
+                        String actualAddress = connectBtAudio((String) args.arg2);
 
                         if (actualAddress != null) {
                             transitionTo(getConnectingStateForAddress(actualAddress,
@@ -166,7 +171,7 @@
                         break;
                     case RETRY_HFP_CONNECTION:
                         Log.i(LOG_TAG, "Retrying HFP connection to %s", (String) args.arg2);
-                        String retryAddress = connectHfpAudio((String) args.arg2, args.argi1);
+                        String retryAddress = connectBtAudio((String) args.arg2, args.argi1);
 
                         if (retryAddress != null) {
                             transitionTo(getConnectingStateForAddress(retryAddress,
@@ -178,14 +183,16 @@
                     case CONNECTION_TIMEOUT:
                         // Ignore.
                         break;
-                    case HFP_IS_ON:
+                    case BT_AUDIO_IS_ON:
                         String address = (String) args.arg2;
                         Log.w(LOG_TAG, "HFP audio unexpectedly turned on from device %s", address);
-                        transitionTo(getConnectedStateForAddress(address, "AudioOff/HFP_IS_ON"));
+                        transitionTo(getConnectedStateForAddress(address,
+                                "AudioOff/BT_AUDIO_IS_ON"));
                         break;
-                    case HFP_LOST:
+                    case BT_AUDIO_LOST:
                         Log.i(LOG_TAG, "Received HFP off for device %s while HFP off.",
                                 (String) args.arg2);
+                        mListener.onUnexpectedBluetoothStateChange();
                         break;
                     case GET_CURRENT_STATE:
                         BlockingQueue<IState> sink = (BlockingQueue<IState>) args.arg3;
@@ -253,7 +260,7 @@
                             // Ignore repeated connection attempts to the same device
                             break;
                         }
-                        String actualAddress = connectHfpAudio(address);
+                        String actualAddress = connectBtAudio(address);
 
                         if (actualAddress != null) {
                             transitionTo(getConnectingStateForAddress(actualAddress,
@@ -264,14 +271,13 @@
                         }
                         break;
                     case DISCONNECT_HFP:
-                        disconnectAudio();
-                        transitionTo(mAudioOffState);
+                        mDeviceManager.disconnectAudio();
                         break;
                     case RETRY_HFP_CONNECTION:
                         if (Objects.equals(address, mDeviceAddress)) {
                             Log.d(LOG_TAG, "Retry message came through while connecting.");
                         } else {
-                            String retryAddress = connectHfpAudio(address, args.argi1);
+                            String retryAddress = connectBtAudio(address, args.argi1);
                             if (retryAddress != null) {
                                 transitionTo(getConnectingStateForAddress(retryAddress,
                                         "AudioConnecting/RETRY_HFP_CONNECTION"));
@@ -285,7 +291,7 @@
                                 mDeviceAddress);
                         transitionToActualState();
                         break;
-                    case HFP_IS_ON:
+                    case BT_AUDIO_IS_ON:
                         if (Objects.equals(mDeviceAddress, address)) {
                             Log.i(LOG_TAG, "HFP connection success for device %s.", mDeviceAddress);
                             transitionTo(mAudioConnectedStates.get(mDeviceAddress));
@@ -293,17 +299,18 @@
                             Log.w(LOG_TAG, "In connecting state for device %s but %s" +
                                     " is now connected", mDeviceAddress, address);
                             transitionTo(getConnectedStateForAddress(address,
-                                    "AudioConnecting/HFP_IS_ON"));
+                                    "AudioConnecting/BT_AUDIO_IS_ON"));
                         }
                         break;
-                    case HFP_LOST:
-                        if (Objects.equals(mDeviceAddress, address)) {
+                    case BT_AUDIO_LOST:
+                        if (Objects.equals(mDeviceAddress, address) || address == null) {
                             Log.i(LOG_TAG, "Connection with device %s failed.",
                                     mDeviceAddress);
                             transitionToActualState();
                         } else {
                             Log.w(LOG_TAG, "Got HFP lost message for device %s while" +
                                     " connecting to %s.", address, mDeviceAddress);
+                            mListener.onUnexpectedBluetoothStateChange();
                         }
                         break;
                     case GET_CURRENT_STATE:
@@ -366,7 +373,7 @@
                             // Ignore connection to already connected device.
                             break;
                         }
-                        String actualAddress = connectHfpAudio(address);
+                        String actualAddress = connectBtAudio(address);
 
                         if (actualAddress != null) {
                             transitionTo(getConnectingStateForAddress(address,
@@ -377,14 +384,13 @@
                         }
                         break;
                     case DISCONNECT_HFP:
-                        disconnectAudio();
-                        transitionTo(mAudioOffState);
+                        mDeviceManager.disconnectAudio();
                         break;
                     case RETRY_HFP_CONNECTION:
                         if (Objects.equals(address, mDeviceAddress)) {
                             Log.d(LOG_TAG, "Retry message came through while connected.");
                         } else {
-                            String retryAddress = connectHfpAudio(address, args.argi1);
+                            String retryAddress = connectBtAudio(address, args.argi1);
                             if (retryAddress != null) {
                                 transitionTo(getConnectingStateForAddress(retryAddress,
                                         "AudioConnected/RETRY_HFP_CONNECTION"));
@@ -396,23 +402,25 @@
                     case CONNECTION_TIMEOUT:
                         Log.w(LOG_TAG, "Received CONNECTION_TIMEOUT while connected.");
                         break;
-                    case HFP_IS_ON:
+                    case BT_AUDIO_IS_ON:
                         if (Objects.equals(mDeviceAddress, address)) {
-                            Log.i(LOG_TAG, "Received redundant HFP_IS_ON for %s", mDeviceAddress);
+                            Log.i(LOG_TAG,
+                                    "Received redundant BT_AUDIO_IS_ON for %s", mDeviceAddress);
                         } else {
                             Log.w(LOG_TAG, "In connected state for device %s but %s" +
                                     " is now connected", mDeviceAddress, address);
                             transitionTo(getConnectedStateForAddress(address,
-                                    "AudioConnected/HFP_IS_ON"));
+                                    "AudioConnected/BT_AUDIO_IS_ON"));
                         }
                         break;
-                    case HFP_LOST:
-                        if (Objects.equals(mDeviceAddress, address)) {
+                    case BT_AUDIO_LOST:
+                        if (Objects.equals(mDeviceAddress, address) || address == null) {
                             Log.i(LOG_TAG, "HFP connection with device %s lost.", mDeviceAddress);
                             transitionToActualState();
                         } else {
                             Log.w(LOG_TAG, "Got HFP lost message for device %s while" +
                                     " connected to %s.", address, mDeviceAddress);
+                            mListener.onUnexpectedBluetoothStateChange();
                         }
                         break;
                     case GET_CURRENT_STATE:
@@ -439,8 +447,9 @@
 
     private BluetoothStateListener mListener;
     private BluetoothDeviceManager mDeviceManager;
-    // Tracks the active device in the BT stack.
-    private BluetoothDevice mActiveDeviceCache = null;
+    // Tracks the active devices in the BT stack (HFP or hearing aid).
+    private BluetoothDevice mHfpActiveDeviceCache = null;
+    private BluetoothDevice mHearingAidActiveDeviceCache = null;
 
     public BluetoothRouteManager(Context context, TelecomSystem.SyncRoot lock,
             BluetoothDeviceManager deviceManager, Timeouts.Adapter timeoutsAdapter) {
@@ -537,6 +546,18 @@
         sendMessage(DISCONNECT_HFP, args);
     }
 
+    public void disconnectSco() {
+        mDeviceManager.disconnectSco();
+    }
+
+    public void cacheHearingAidDevice() {
+        mDeviceManager.cacheHearingAidDevice();
+    }
+
+    public void restoreHearingAidDevice() {
+        mDeviceManager.restoreHearingAidDevice();
+    }
+
     public void setListener(BluetoothStateListener listener) {
         mListener = listener;
     }
@@ -559,29 +580,38 @@
         mListener.onBluetoothDeviceListChanged();
     }
 
-    public void onActiveDeviceChanged(BluetoothDevice device) {
-        BluetoothDevice oldActiveDevice = mActiveDeviceCache;
-        mActiveDeviceCache = device;
-        if ((oldActiveDevice == null) ^ (device == null)) {
-            if (device == null) {
-                mListener.onBluetoothActiveDeviceGone();
-            } else {
-                mListener.onBluetoothActiveDevicePresent();
-            }
+    public void onActiveDeviceChanged(BluetoothDevice device, boolean isHearingAid) {
+        boolean wasActiveDevicePresent = mHearingAidActiveDeviceCache != null
+                || mHfpActiveDeviceCache != null;
+        if (isHearingAid) {
+            mHearingAidActiveDeviceCache = device;
+        } else {
+            mHfpActiveDeviceCache = device;
+        }
+        boolean isActiveDevicePresent = mHearingAidActiveDeviceCache != null
+                || mHfpActiveDeviceCache != null;
+
+        if (wasActiveDevicePresent && !isActiveDevicePresent) {
+            mListener.onBluetoothActiveDeviceGone();
+        } else if (!wasActiveDevicePresent && isActiveDevicePresent) {
+            mListener.onBluetoothActiveDevicePresent();
         }
     }
 
-    public Collection<BluetoothDevice> getConnectedDevices() {
-        return Collections.unmodifiableCollection(
-                new ArrayList<>(mDeviceManager.getConnectedDevices()));
+    public boolean hasBtActiveDevice() {
+        return mHearingAidActiveDeviceCache != null || mHfpActiveDeviceCache != null;
     }
 
-    private String connectHfpAudio(String address) {
-        return connectHfpAudio(address, 0);
+    public Collection<BluetoothDevice> getConnectedDevices() {
+        return mDeviceManager.getUniqueConnectedDevices();
+    }
+
+    private String connectBtAudio(String address) {
+        return connectBtAudio(address, 0);
     }
 
     /**
-     * Initiates a HFP connection to the BT address specified.
+     * Initiates a connection to the BT address specified.
      * Note: This method is not synchronized on the Telecom lock, so don't try and call back into
      * Telecom from within it.
      * @param address The address that should be tried first. May be null.
@@ -589,8 +619,8 @@
      * @return The address of the device that's actually being connected to, or null if no
      * connection was successful.
      */
-    private String connectHfpAudio(String address, int retryCount) {
-        Collection<BluetoothDevice> deviceList = getConnectedDevices();
+    private String connectBtAudio(String address, int retryCount) {
+        Collection<BluetoothDevice> deviceList = mDeviceManager.getConnectedDevices();
         Optional<BluetoothDevice> matchingDevice = deviceList.stream()
                 .filter(d -> Objects.equals(d.getAddress(), address))
                 .findAny();
@@ -611,7 +641,7 @@
             Log.i(this, "No device with address %s available. Using %s instead.",
                     address, actualAddress);
         }
-        if (!connectAudio(actualAddress)) {
+        if (!mDeviceManager.connectAudio(actualAddress)) {
             boolean shouldRetry = retryCount < MAX_CONNECTION_RETRIES;
             Log.w(LOG_TAG, "Could not connect to %s. Will %s", actualAddress,
                     shouldRetry ? "retry" : "not retry");
@@ -631,7 +661,13 @@
     }
 
     private String getActiveDeviceAddress() {
-        return mActiveDeviceCache == null ? null : mActiveDeviceCache.getAddress();
+        if (mHfpActiveDeviceCache != null) {
+            return mHfpActiveDeviceCache.getAddress();
+        }
+        if (mHearingAidActiveDeviceCache != null) {
+            return mHearingAidActiveDeviceCache.getAddress();
+        }
+        return null;
     }
 
     private void transitionToActualState() {
@@ -652,20 +688,29 @@
     @VisibleForTesting
     public BluetoothDevice getBluetoothAudioConnectedDevice() {
         BluetoothHeadsetProxy bluetoothHeadset = mDeviceManager.getHeadsetService();
-        if (bluetoothHeadset == null) {
-            Log.i(this, "getBluetoothAudioConnectedDevice: no headset service available.");
+        BluetoothHearingAid bluetoothHearingAid = mDeviceManager.getHearingAidService();
+
+        if (bluetoothHeadset == null && bluetoothHearingAid == null) {
+            Log.i(this, "getBluetoothAudioConnectedDevice: no service available.");
             return null;
         }
-        List<BluetoothDevice> deviceList = bluetoothHeadset.getConnectedDevices();
 
-        for (int i = 0; i < deviceList.size(); i++) {
-            BluetoothDevice device = deviceList.get(i);
-            boolean isAudioOn = bluetoothHeadset.getAudioState(device)
-                    != BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
-            Log.v(this, "isBluetoothAudioConnected: ==> isAudioOn = " + isAudioOn
-                    + "for headset: " + device);
-            if (isAudioOn) {
-                return device;
+        if (bluetoothHeadset != null) {
+            for (BluetoothDevice device : bluetoothHeadset.getConnectedDevices()) {
+                boolean isAudioOn = bluetoothHeadset.getAudioState(device)
+                        != BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
+                Log.v(this, "isBluetoothAudioConnected: ==> isAudioOn = " + isAudioOn
+                        + "for headset: " + device);
+                if (isAudioOn) {
+                    return device;
+                }
+            }
+        }
+        if (bluetoothHearingAid != null) {
+            for (BluetoothDevice device : bluetoothHearingAid.getActiveDevices()) {
+                if (device != null) {
+                    return device;
+                }
             }
         }
         return null;
@@ -687,37 +732,6 @@
         return bluetoothHeadset.isInbandRingingEnabled();
     }
 
-    private boolean connectAudio(String address) {
-        BluetoothHeadsetProxy bluetoothHeadset = mDeviceManager.getHeadsetService();
-        if (bluetoothHeadset == null) {
-            Log.w(this, "Trying to connect audio but no headset service exists.");
-            return false;
-        }
-        BluetoothDevice device = mDeviceManager.getDeviceFromAddress(address);
-        if (device == null) {
-            Log.w(this, "Attempting to turn on audio for a disconnected device");
-            return false;
-        }
-        boolean success = bluetoothHeadset.setActiveDevice(device);
-        if (!success) {
-            Log.w(LOG_TAG, "Couldn't set active device to %s", address);
-            return false;
-        }
-        if (!bluetoothHeadset.isAudioOn()) {
-            return bluetoothHeadset.connectAudio();
-        }
-        return true;
-    }
-
-    private void disconnectAudio() {
-        BluetoothHeadsetProxy bluetoothHeadset = mDeviceManager.getHeadsetService();
-        if (bluetoothHeadset == null) {
-            Log.w(this, "Trying to disconnect audio but no headset service exists.");
-        } else {
-            bluetoothHeadset.disconnectAudio();
-        }
-    }
-
     private boolean addDevice(String address) {
         if (mAudioConnectingStates.containsKey(address)) {
             Log.i(this, "Attempting to add device %s twice.", address);
@@ -776,23 +790,30 @@
 
     @VisibleForTesting
     public void setInitialStateForTesting(String stateName, BluetoothDevice device) {
-        switch (stateName) {
-            case AUDIO_OFF_STATE_NAME:
-                transitionTo(mAudioOffState);
-                break;
-            case AUDIO_CONNECTING_STATE_NAME_PREFIX:
-                transitionTo(getConnectingStateForAddress(device.getAddress(),
-                        "setInitialStateForTesting"));
-                break;
-            case AUDIO_CONNECTED_STATE_NAME_PREFIX:
-                transitionTo(getConnectedStateForAddress(device.getAddress(),
-                        "setInitialStateForTesting"));
-                break;
-        }
+        sendMessage(RUN_RUNNABLE, (Runnable) () -> {
+            switch (stateName) {
+                case AUDIO_OFF_STATE_NAME:
+                    transitionTo(mAudioOffState);
+                    break;
+                case AUDIO_CONNECTING_STATE_NAME_PREFIX:
+                    transitionTo(getConnectingStateForAddress(device.getAddress(),
+                            "setInitialStateForTesting"));
+                    break;
+                case AUDIO_CONNECTED_STATE_NAME_PREFIX:
+                    transitionTo(getConnectedStateForAddress(device.getAddress(),
+                            "setInitialStateForTesting"));
+                    break;
+            }
+            Log.i(LOG_TAG, "transition for testing done: %s", stateName);
+        });
     }
 
     @VisibleForTesting
-    public void setActiveDeviceCacheForTesting(BluetoothDevice device) {
-        mActiveDeviceCache = device;
+    public void setActiveDeviceCacheForTesting(BluetoothDevice device, boolean isHearingAid) {
+        if (isHearingAid) {
+            mHearingAidActiveDeviceCache = device;
+        } else {
+            mHfpActiveDeviceCache = device;
+        }
     }
 }
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java b/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
index f9c6437..0176a43 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
@@ -18,6 +18,8 @@
 
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothHearingAid;
+import android.bluetooth.BluetoothProfile;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -27,8 +29,8 @@
 
 import com.android.internal.os.SomeArgs;
 
-import static com.android.server.telecom.bluetooth.BluetoothRouteManager.HFP_IS_ON;
-import static com.android.server.telecom.bluetooth.BluetoothRouteManager.HFP_LOST;
+import static com.android.server.telecom.bluetooth.BluetoothRouteManager.BT_AUDIO_IS_ON;
+import static com.android.server.telecom.bluetooth.BluetoothRouteManager.BT_AUDIO_LOST;
 
 
 public class BluetoothStateReceiver extends BroadcastReceiver {
@@ -39,6 +41,8 @@
         INTENT_FILTER.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
         INTENT_FILTER.addAction(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED);
         INTENT_FILTER.addAction(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED);
+        INTENT_FILTER.addAction(BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED);
+        INTENT_FILTER.addAction(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED);
     }
 
     // If not in a call, BSR won't listen to the Bluetooth stack's HFP on/off messages, since
@@ -55,9 +59,11 @@
                 case BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED:
                     handleAudioStateChanged(intent);
                     break;
+                case BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED:
                 case BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED:
                     handleConnectionStateChanged(intent);
                     break;
+                case BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED:
                 case BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED:
                     handleActiveDeviceChanged(intent);
                     break;
@@ -91,10 +97,10 @@
         args.arg2 = device.getAddress();
         switch (bluetoothHeadsetAudioState) {
             case BluetoothHeadset.STATE_AUDIO_CONNECTED:
-                mBluetoothRouteManager.sendMessage(HFP_IS_ON, args);
+                mBluetoothRouteManager.sendMessage(BT_AUDIO_IS_ON, args);
                 break;
             case BluetoothHeadset.STATE_AUDIO_DISCONNECTED:
-                mBluetoothRouteManager.sendMessage(HFP_LOST, args);
+                mBluetoothRouteManager.sendMessage(BT_AUDIO_LOST, args);
                 break;
         }
     }
@@ -114,19 +120,36 @@
         Log.i(LOG_TAG, "Device %s changed state to %d",
                 device.getAddress(), bluetoothHeadsetState);
 
-        if (bluetoothHeadsetState == BluetoothHeadset.STATE_CONNECTED) {
-            mBluetoothDeviceManager.onDeviceConnected(device);
-        } else if (bluetoothHeadsetState == BluetoothHeadset.STATE_DISCONNECTED
-                || bluetoothHeadsetState == BluetoothHeadset.STATE_DISCONNECTING) {
-            mBluetoothDeviceManager.onDeviceDisconnected(device);
+        boolean isHearingAid = BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED
+                .equals(intent.getAction());
+        if (bluetoothHeadsetState == BluetoothProfile.STATE_CONNECTED) {
+            mBluetoothDeviceManager.onDeviceConnected(device, isHearingAid);
+        } else if (bluetoothHeadsetState == BluetoothProfile.STATE_DISCONNECTED
+                || bluetoothHeadsetState == BluetoothProfile.STATE_DISCONNECTING) {
+            mBluetoothDeviceManager.onDeviceDisconnected(device, isHearingAid);
         }
     }
 
     private void handleActiveDeviceChanged(Intent intent) {
         BluetoothDevice device =
                 intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
-        Log.i(LOG_TAG, "Device %s is now the preferred HFP device", device);
-        mBluetoothRouteManager.onActiveDeviceChanged(device);
+        boolean isHearingAid =
+                BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED.equals(intent.getAction());
+        Log.i(LOG_TAG, "Device %s is now the preferred BT device for %s", device,
+                isHearingAid ? "heading aid" : "HFP");
+
+        mBluetoothRouteManager.onActiveDeviceChanged(device, isHearingAid);
+        if (isHearingAid) {
+            Session session = Log.createSubsession();
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = session;
+            if (device == null) {
+                mBluetoothRouteManager.sendMessage(BT_AUDIO_LOST, args);
+            } else {
+                args.arg2 = device.getAddress();
+                mBluetoothRouteManager.sendMessage(BT_AUDIO_IS_ON, args);
+            }
+        }
     }
 
     public BluetoothStateReceiver(BluetoothDeviceManager deviceManager,
diff --git a/src/com/android/server/telecom/callfiltering/AsyncBlockCheckFilter.java b/src/com/android/server/telecom/callfiltering/AsyncBlockCheckFilter.java
index 748d15e..0abd15d 100644
--- a/src/com/android/server/telecom/callfiltering/AsyncBlockCheckFilter.java
+++ b/src/com/android/server/telecom/callfiltering/AsyncBlockCheckFilter.java
@@ -21,6 +21,7 @@
 import android.os.AsyncTask;
 import android.os.Bundle;
 import android.provider.BlockedNumberContract;
+import android.provider.CallLog;
 import android.telecom.Log;
 import android.telecom.Logging.Session;
 import android.telecom.TelecomManager;
@@ -40,17 +41,20 @@
         implements IncomingCallFilter.CallFilter {
     private final Context mContext;
     private final BlockCheckerAdapter mBlockCheckerAdapter;
+    private final CallBlockListener mCallBlockListener;
     private Call mIncomingCall;
     private Session mBackgroundTaskSubsession;
     private Session mPostExecuteSubsession;
     private CallFilterResultCallback mCallback;
     private CallerInfoLookupHelper mCallerInfoLookupHelper;
+    private int mBlockStatus = BlockedNumberContract.STATUS_NOT_BLOCKED;
 
     public AsyncBlockCheckFilter(Context context, BlockCheckerAdapter blockCheckerAdapter,
-            CallerInfoLookupHelper callerInfoLookupHelper) {
+            CallerInfoLookupHelper callerInfoLookupHelper, CallBlockListener callBlockListener) {
         mContext = context;
         mBlockCheckerAdapter = blockCheckerAdapter;
         mCallerInfoLookupHelper = callerInfoLookupHelper;
+        mCallBlockListener = callBlockListener;
     }
 
     @Override
@@ -104,7 +108,8 @@
                 extras.putBoolean(BlockedNumberContract.EXTRA_CONTACT_EXIST,
                         Boolean.valueOf(params[2]));
             }
-            return mBlockCheckerAdapter.isBlocked(mContext, params[0], extras);
+            mBlockStatus = mBlockCheckerAdapter.getBlockStatus(mContext, params[0], extras);
+            return mBlockStatus != BlockedNumberContract.STATUS_NOT_BLOCKED;
         } finally {
             Log.endSession();
         }
@@ -119,9 +124,18 @@
                 result = new CallFilteringResult(
                         false, // shouldAllowCall
                         true, //shouldReject
-                        false, //shouldAddToCallLog
-                        false // shouldShowNotification
+                        true, //shouldAddToCallLog
+                        false, // shouldShowNotification
+                        convertBlockStatusToReason(), //callBlockReason
+                        null, //callScreeningAppName
+                        null //callScreeningComponentName
                 );
+                if (mCallBlockListener != null) {
+                    String number = mIncomingCall.getHandle() == null ? null
+                            : mIncomingCall.getHandle().getSchemeSpecificPart();
+                    mCallBlockListener.onCallBlocked(mBlockStatus, number,
+                            mIncomingCall.getInitiatingUser());
+                }
             } else {
                 result = new CallFilteringResult(
                         true, // shouldAllowCall
@@ -130,10 +144,36 @@
                         true // shouldShowNotification
                 );
             }
-            Log.addEvent(mIncomingCall, LogUtils.Events.BLOCK_CHECK_FINISHED, result);
+            Log.addEvent(mIncomingCall, LogUtils.Events.BLOCK_CHECK_FINISHED,
+                    BlockedNumberContract.SystemContract.blockStatusToString(mBlockStatus) + " "
+                            + result);
             mCallback.onCallFilteringComplete(mIncomingCall, result);
         } finally {
             Log.endSession();
         }
     }
+
+    private int convertBlockStatusToReason() {
+        switch (mBlockStatus) {
+            case BlockedNumberContract.STATUS_BLOCKED_IN_LIST:
+                return CallLog.Calls.BLOCK_REASON_BLOCKED_NUMBER;
+
+            case BlockedNumberContract.STATUS_BLOCKED_UNKNOWN_NUMBER:
+                return CallLog.Calls.BLOCK_REASON_UNKNOWN_NUMBER;
+
+            case BlockedNumberContract.STATUS_BLOCKED_RESTRICTED:
+                return CallLog.Calls.BLOCK_REASON_RESTRICTED_NUMBER;
+
+            case BlockedNumberContract.STATUS_BLOCKED_PAYPHONE:
+                return CallLog.Calls.BLOCK_REASON_PAY_PHONE;
+
+            case BlockedNumberContract.STATUS_BLOCKED_NOT_IN_CONTACTS:
+                return CallLog.Calls.BLOCK_REASON_NOT_IN_CONTACTS;
+
+            default:
+                Log.w(AsyncBlockCheckFilter.class.getSimpleName(),
+                    "There's no call log block reason can be converted");
+                return CallLog.Calls.BLOCK_REASON_BLOCKED_NUMBER;
+        }
+    }
 }
diff --git a/src/com/android/server/telecom/callfiltering/BlockCheckerAdapter.java b/src/com/android/server/telecom/callfiltering/BlockCheckerAdapter.java
index 8c74fa9..4a5ac60 100644
--- a/src/com/android/server/telecom/callfiltering/BlockCheckerAdapter.java
+++ b/src/com/android/server/telecom/callfiltering/BlockCheckerAdapter.java
@@ -30,9 +30,15 @@
      * @param context the context of the caller.
      * @param number the number to check.
      * @param extras the extra attribute of the number.
-     * @return {@code true} if the number is blocked. {@code false} otherwise.
+     * @return result code indicating if the number should be blocked, and if so why.
+     *         Valid values are: {@link android.provider.BlockedNumberContract#STATUS_NOT_BLOCKED},
+     *         {@link android.provider.BlockedNumberContract#STATUS_BLOCKED_IN_LIST},
+     *         {@link android.provider.BlockedNumberContract#STATUS_BLOCKED_NOT_IN_CONTACTS},
+     *         {@link android.provider.BlockedNumberContract#STATUS_BLOCKED_PAYPHONE},
+     *         {@link android.provider.BlockedNumberContract#STATUS_BLOCKED_RESTRICTED},
+     *         {@link android.provider.BlockedNumberContract#STATUS_BLOCKED_UNKNOWN_NUMBER}.
      */
-    public boolean isBlocked(Context context, String number, Bundle extras) {
-        return BlockChecker.isBlocked(context, number, extras);
+    public int getBlockStatus(Context context, String number, Bundle extras) {
+        return BlockChecker.getBlockStatus(context, number, extras);
     }
 }
diff --git a/src/com/android/server/telecom/callfiltering/CallBlockListener.java b/src/com/android/server/telecom/callfiltering/CallBlockListener.java
new file mode 100644
index 0000000..d0a9949
--- /dev/null
+++ b/src/com/android/server/telecom/callfiltering/CallBlockListener.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2018 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.callfiltering;
+
+import android.os.UserHandle;
+
+/**
+ * Listener for components which wish to be notified when a call is blocked.
+ */
+public interface CallBlockListener {
+    void onCallBlocked(int blockStatus, String number, UserHandle userHandle);
+}
diff --git a/src/com/android/server/telecom/callfiltering/CallFilteringResult.java b/src/com/android/server/telecom/callfiltering/CallFilteringResult.java
index 9e35d86..6c37fd4 100644
--- a/src/com/android/server/telecom/callfiltering/CallFilteringResult.java
+++ b/src/com/android/server/telecom/callfiltering/CallFilteringResult.java
@@ -16,11 +16,18 @@
 
 package com.android.server.telecom.callfiltering;
 
+import android.provider.CallLog;
+import android.provider.CallLog.Calls;
+import android.text.TextUtils;
+
 public class CallFilteringResult {
     public boolean shouldAllowCall;
     public boolean shouldReject;
     public boolean shouldAddToCallLog;
     public boolean shouldShowNotification;
+    public int mCallBlockReason = CallLog.Calls.BLOCK_REASON_NOT_BLOCKED;
+    public CharSequence mCallScreeningAppName = null;
+    public String mCallScreeningComponentName = null;
 
     public CallFilteringResult(boolean shouldAllowCall, boolean shouldReject, boolean
             shouldAddToCallLog, boolean shouldShowNotification) {
@@ -30,22 +37,85 @@
         this.shouldShowNotification = shouldShowNotification;
     }
 
+    public CallFilteringResult(boolean shouldAllowCall, boolean shouldReject, boolean
+            shouldAddToCallLog, boolean shouldShowNotification, int callBlockReason,
+            CharSequence callScreeningAppName, String callScreeningComponentName) {
+        this.shouldAllowCall = shouldAllowCall;
+        this.shouldReject = shouldReject;
+        this.shouldAddToCallLog = shouldAddToCallLog;
+        this.shouldShowNotification = shouldShowNotification;
+        this.mCallBlockReason = callBlockReason;
+        this.mCallScreeningAppName = callScreeningAppName;
+        this.mCallScreeningComponentName = callScreeningComponentName;
+    }
+
     /**
-     * Combine this CallFilteringResult with another, returning a CallFilteringResult with
-     * the more restrictive properties of the two.
+     * Combine this CallFilteringResult with another, returning a CallFilteringResult with the more
+     * restrictive properties of the two. Where there are multiple call filtering components which
+     * block a call, the first filter from {@link AsyncBlockCheckFilter},
+     * {@link DirectToVoicemailCallFilter}, {@link CallScreeningServiceFilter} which blocked a call
+     * shall be used to populate the call block reason, component name, etc.
      */
     public CallFilteringResult combine(CallFilteringResult other) {
         if (other == null) {
             return this;
         }
 
+        if (isBlockedByProvider(mCallBlockReason)) {
+            return getCombinedCallFilteringResult(other, mCallBlockReason,
+                null /*callScreeningAppName*/, null /*callScreeningComponentName*/);
+        } else if (isBlockedByProvider(other.mCallBlockReason)) {
+            return getCombinedCallFilteringResult(other, other.mCallBlockReason,
+                null /*callScreeningAppName*/, null /*callScreeningComponentName*/);
+        }
+
+        if (mCallBlockReason == Calls.BLOCK_REASON_DIRECT_TO_VOICEMAIL
+            || other.mCallBlockReason == Calls.BLOCK_REASON_DIRECT_TO_VOICEMAIL) {
+            return getCombinedCallFilteringResult(other, Calls.BLOCK_REASON_DIRECT_TO_VOICEMAIL,
+                null /*callScreeningAppName*/, null /*callScreeningComponentName*/);
+        }
+
+        if (shouldReject && mCallBlockReason == CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE) {
+            return getCombinedCallFilteringResult(other, Calls.BLOCK_REASON_CALL_SCREENING_SERVICE,
+                mCallScreeningAppName, mCallScreeningComponentName);
+        } else if (other.shouldReject && other.mCallBlockReason == CallLog.Calls
+            .BLOCK_REASON_CALL_SCREENING_SERVICE) {
+            return getCombinedCallFilteringResult(other, Calls.BLOCK_REASON_CALL_SCREENING_SERVICE,
+                other.mCallScreeningAppName, other.mCallScreeningComponentName);
+        }
+
         return new CallFilteringResult(
-                shouldAllowCall && other.shouldAllowCall,
-                shouldReject || other.shouldReject,
-                shouldAddToCallLog && other.shouldAddToCallLog,
-                shouldShowNotification && other.shouldShowNotification);
+            shouldAllowCall && other.shouldAllowCall,
+            shouldReject || other.shouldReject,
+            shouldAddToCallLog && other.shouldAddToCallLog,
+            shouldShowNotification && other.shouldShowNotification);
     }
 
+    private boolean isBlockedByProvider(int blockReason) {
+        if (blockReason == Calls.BLOCK_REASON_BLOCKED_NUMBER
+            || blockReason == Calls.BLOCK_REASON_UNKNOWN_NUMBER
+            || blockReason == Calls.BLOCK_REASON_RESTRICTED_NUMBER
+            || blockReason == Calls.BLOCK_REASON_PAY_PHONE
+            || blockReason == Calls.BLOCK_REASON_NOT_IN_CONTACTS) {
+            return true;
+        }
+
+        return false;
+    }
+
+    private CallFilteringResult getCombinedCallFilteringResult(CallFilteringResult other,
+        int callBlockReason, CharSequence callScreeningAppName, String callScreeningComponentName) {
+        return new CallFilteringResult(
+            shouldAllowCall && other.shouldAllowCall,
+            shouldReject || other.shouldReject,
+            shouldAddToCallLog && other.shouldAddToCallLog,
+            shouldShowNotification && other.shouldShowNotification,
+            callBlockReason,
+            callScreeningAppName,
+            callScreeningComponentName);
+    }
+
+
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
@@ -56,7 +126,24 @@
         if (shouldAllowCall != that.shouldAllowCall) return false;
         if (shouldReject != that.shouldReject) return false;
         if (shouldAddToCallLog != that.shouldAddToCallLog) return false;
-        return shouldShowNotification == that.shouldShowNotification;
+        if (shouldShowNotification != that.shouldShowNotification) return false;
+        if (mCallBlockReason != that.mCallBlockReason) return false;
+
+        if ((TextUtils.isEmpty(mCallScreeningAppName) &&
+            TextUtils.isEmpty(that.mCallScreeningAppName)) &&
+            (TextUtils.isEmpty(mCallScreeningComponentName) &&
+            TextUtils.isEmpty(that.mCallScreeningComponentName))) {
+            return true;
+        } else if (!TextUtils.isEmpty(mCallScreeningAppName) &&
+            !TextUtils.isEmpty(that.mCallScreeningAppName) &&
+            mCallScreeningAppName.equals(that.mCallScreeningAppName) &&
+            !TextUtils.isEmpty(mCallScreeningComponentName) &&
+            !TextUtils.isEmpty(that.mCallScreeningComponentName) &&
+            mCallScreeningComponentName.equals(that.mCallScreeningComponentName)) {
+            return true;
+        }
+
+        return false;
     }
 
     @Override
@@ -87,6 +174,21 @@
         if (shouldShowNotification) {
             sb.append(", notified");
         }
+
+        if (mCallBlockReason != 0) {
+            sb.append(", mCallBlockReason = ");
+            sb.append(mCallBlockReason);
+        }
+
+        if (!TextUtils.isEmpty(mCallScreeningAppName)) {
+            sb.append(", mCallScreeningAppName = ");
+            sb.append(mCallScreeningAppName);
+        }
+
+        if (!TextUtils.isEmpty(mCallScreeningComponentName)) {
+            sb.append(", mCallScreeningComponentName = ");
+            sb.append(mCallScreeningComponentName);
+        }
         sb.append("]");
 
         return sb.toString();
diff --git a/src/com/android/server/telecom/callfiltering/CallScreeningServiceController.java b/src/com/android/server/telecom/callfiltering/CallScreeningServiceController.java
new file mode 100644
index 0000000..b72ac0b
--- /dev/null
+++ b/src/com/android/server/telecom/callfiltering/CallScreeningServiceController.java
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) 2018 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.callfiltering;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.PersistableBundle;
+import android.provider.CallLog;
+import android.telecom.Log;
+import android.telecom.Logging.Runnable;
+import android.telecom.TelecomManager;
+import android.telephony.CarrierConfigManager;
+import android.text.TextUtils;
+
+import com.android.internal.telephony.CallerInfo;
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallScreeningServiceHelper;
+import com.android.server.telecom.CallerInfoLookupHelper;
+import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.LogUtils;
+import com.android.server.telecom.ParcelableCallUtils;
+import com.android.server.telecom.PhoneAccountRegistrar;
+import com.android.server.telecom.TelecomServiceImpl;
+import com.android.server.telecom.TelecomSystem;
+
+/**
+ * This class supports binding to the various {@link android.telecom.CallScreeningService}:
+ * carrier, default dialer and user chosen. Carrier's CallScreeningService implementation will be
+ * bound first, and then default dialer's and user chosen's. If Carrier's CallScreeningService
+ * blocks a call, no further CallScreeningService after it will be bound.
+ */
+public class CallScreeningServiceController implements IncomingCallFilter.CallFilter,
+        CallScreeningServiceFilter.CallScreeningFilterResultCallback {
+
+    private final Context mContext;
+    private final CallsManager mCallsManager;
+    private final PhoneAccountRegistrar mPhoneAccountRegistrar;
+    private final ParcelableCallUtils.Converter mParcelableCallUtilsConverter;
+    private final TelecomSystem.SyncRoot mTelecomLock;
+    private final TelecomServiceImpl.SettingsSecureAdapter mSettingsSecureAdapter;
+    private final CallerInfoLookupHelper mCallerInfoLookupHelper;
+    private final CallScreeningServiceHelper.AppLabelProxy mAppLabelProxy;
+
+    private final int CARRIER_CALL_FILTERING_TIMED_OUT = 2000; // 2 seconds
+    private final int CALL_FILTERING_TIMED_OUT = 4500; // 4.5 seconds
+
+    private final Handler mHandler = new Handler(Looper.getMainLooper());
+
+    private Call mCall;
+    private CallFilterResultCallback mCallback;
+
+    private CallFilteringResult mResult = new CallFilteringResult(
+            true, // shouldAllowCall
+            false, // shouldReject
+            true, // shouldAddToCallLog
+            true // shouldShowNotification
+    );
+
+    private boolean mIsFinished;
+    private boolean mIsCarrierFinished;
+    private boolean mIsDefaultDialerFinished;
+    private boolean mIsUserChosenFinished;
+
+    public CallScreeningServiceController(
+            Context context,
+            CallsManager callsManager,
+            PhoneAccountRegistrar phoneAccountRegistrar,
+            ParcelableCallUtils.Converter parcelableCallUtilsConverter,
+            TelecomSystem.SyncRoot lock,
+            TelecomServiceImpl.SettingsSecureAdapter settingsSecureAdapter,
+            CallerInfoLookupHelper callerInfoLookupHelper,
+            CallScreeningServiceHelper.AppLabelProxy appLabelProxy) {
+        mContext = context;
+        mCallsManager = callsManager;
+        mPhoneAccountRegistrar = phoneAccountRegistrar;
+        mParcelableCallUtilsConverter = parcelableCallUtilsConverter;
+        mTelecomLock = lock;
+        mSettingsSecureAdapter = settingsSecureAdapter;
+        mCallerInfoLookupHelper = callerInfoLookupHelper;
+        mAppLabelProxy = appLabelProxy;
+    }
+
+    @Override
+    public void startFilterLookup(Call call, CallFilterResultCallback callBack) {
+        mCall = call;
+        mCallback = callBack;
+        mIsFinished = false;
+        mIsCarrierFinished = false;
+        mIsDefaultDialerFinished = false;
+        mIsUserChosenFinished = false;
+
+        bindCarrierService();
+
+        // Call screening filtering timed out
+        mHandler.postDelayed(new Runnable("ICF.pFTO", mTelecomLock) {
+            @Override
+            public void loggedRun() {
+                if (!mIsFinished) {
+                    Log.i(CallScreeningServiceController.this, "Call screening has timed out.");
+                    finishCallScreening();
+                }
+            }
+        }.prepare(), CALL_FILTERING_TIMED_OUT);
+    }
+
+    @Override
+    public void onCallScreeningFilterComplete(Call call, CallFilteringResult result,
+            String packageName) {
+        synchronized (mTelecomLock) {
+            mResult = result.combine(mResult);
+            if (!TextUtils.isEmpty(packageName) && packageName.equals(getCarrierPackageName())) {
+                mIsCarrierFinished = true;
+                if (result.mCallBlockReason == CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE) {
+                    finishCallScreening();
+                } else {
+                    checkContactExistsAndBindService();
+                }
+            } else if (!TextUtils.isEmpty(packageName) &&
+                    packageName.equals(getDefaultDialerPackageName())) {
+                // Default dialer defined CallScreeningService cannot skip the call log.
+                mResult.shouldAddToCallLog = true;
+                mIsDefaultDialerFinished = true;
+                if (result.mCallBlockReason == CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE ||
+                        mIsUserChosenFinished) {
+                    finishCallScreening();
+                }
+            } else if (!TextUtils.isEmpty(packageName) &&
+                    packageName.equals(getUserChosenPackageName())) {
+                // User defined CallScreeningService cannot skip the call log.
+                mResult.shouldAddToCallLog = true;
+                mIsUserChosenFinished = true;
+                if (mIsDefaultDialerFinished) {
+                    finishCallScreening();
+                }
+            }
+        }
+    }
+
+    private void bindCarrierService() {
+        String carrierPackageName = getCarrierPackageName();
+        if (TextUtils.isEmpty(carrierPackageName)) {
+            mIsCarrierFinished = true;
+            bindDefaultDialerAndUserChosenService();
+        } else {
+            createCallScreeningServiceFilter().startCallScreeningFilter(mCall, this,
+                    carrierPackageName, mAppLabelProxy.getAppLabel(carrierPackageName));
+        }
+
+        // Carrier filtering timed out
+        mHandler.postDelayed(new Runnable("ICF.pFTO", mTelecomLock) {
+            @Override
+            public void loggedRun() {
+                if (!mIsCarrierFinished) {
+                    mIsCarrierFinished = true;
+                    checkContactExistsAndBindService();
+                }
+            }
+        }.prepare(), CARRIER_CALL_FILTERING_TIMED_OUT);
+    }
+
+    private void bindDefaultDialerAndUserChosenService() {
+        if (mIsCarrierFinished) {
+            String dialerPackageName = getDefaultDialerPackageName();
+            if (TextUtils.isEmpty(dialerPackageName)) {
+                mIsDefaultDialerFinished = true;
+            } else {
+                createCallScreeningServiceFilter().startCallScreeningFilter(mCall,
+                        CallScreeningServiceController.this, dialerPackageName,
+                        mAppLabelProxy.getAppLabel(dialerPackageName));
+            }
+
+            String userChosenPackageName = getUserChosenPackageName();
+            if (TextUtils.isEmpty(userChosenPackageName)) {
+                mIsUserChosenFinished = true;
+            } else {
+                createCallScreeningServiceFilter().startCallScreeningFilter(mCall,
+                        CallScreeningServiceController.this, userChosenPackageName,
+                        mAppLabelProxy.getAppLabel(userChosenPackageName));
+            }
+
+            if (mIsDefaultDialerFinished && mIsUserChosenFinished) {
+                finishCallScreening();
+            }
+        }
+    }
+
+    private CallScreeningServiceFilter createCallScreeningServiceFilter() {
+        return new CallScreeningServiceFilter(
+                mContext,
+                mCallsManager,
+                mPhoneAccountRegistrar,
+                mParcelableCallUtilsConverter,
+                mTelecomLock,
+                mSettingsSecureAdapter);
+    }
+
+    private void checkContactExistsAndBindService() {
+        mCallerInfoLookupHelper.startLookup(mCall.getHandle(),
+                new CallerInfoLookupHelper.OnQueryCompleteListener() {
+                    @Override
+                    public void onCallerInfoQueryComplete(Uri handle, CallerInfo info) {
+                        boolean contactExists = info != null && info.contactExists;
+                        Log.i(CallScreeningServiceController.this, "Contact exists: " +
+                                contactExists);
+                        if (!contactExists) {
+                            bindDefaultDialerAndUserChosenService();
+                        } else {
+                            finishCallScreening();
+                        }
+                    }
+
+                    @Override
+                    public void onContactPhotoQueryComplete(Uri handle, CallerInfo
+                            info) {
+                        // ignore
+                    }
+                });
+    }
+
+    private void finishCallScreening() {
+        Log.addEvent(mCall, LogUtils.Events.CONTROLLER_SCREENING_COMPLETED, mResult);
+        mCallback.onCallFilteringComplete(mCall, mResult);
+        mIsFinished = true;
+    }
+
+    private String getCarrierPackageName() {
+        ComponentName componentName = null;
+        CarrierConfigManager configManager = (CarrierConfigManager) mContext.getSystemService
+                (Context.CARRIER_CONFIG_SERVICE);
+        PersistableBundle configBundle = configManager.getConfig();
+        if (configBundle != null) {
+            componentName = ComponentName.unflattenFromString(configBundle.getString
+                    (CarrierConfigManager.KEY_CARRIER_CALL_SCREENING_APP_STRING));
+        }
+
+        return componentName != null ? componentName.getPackageName() : null;
+    }
+
+    private String getDefaultDialerPackageName() {
+        return TelecomManager.from(mContext).getDefaultDialerPackage();
+    }
+
+    private String getUserChosenPackageName() {
+        return mCallsManager.getRoleManagerAdapter().getDefaultCallScreeningApp();
+    }
+}
diff --git a/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java b/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java
index 4830b31..3fcb684 100644
--- a/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java
+++ b/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java
@@ -24,21 +24,28 @@
 import android.content.pm.ResolveInfo;
 import android.os.Binder;
 import android.os.IBinder;
+import android.os.PersistableBundle;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.provider.CallLog;
+import android.provider.Settings;
+import android.telecom.CallIdentification;
 import android.telecom.CallScreeningService;
 import android.telecom.Log;
+import android.telecom.ParcelableCall;
+import android.telecom.TelecomManager;
+import android.telephony.CarrierConfigManager;
 import android.text.TextUtils;
 
 import com.android.internal.telecom.ICallScreeningAdapter;
 import com.android.internal.telecom.ICallScreeningService;
 import com.android.server.telecom.Call;
+import com.android.server.telecom.CallScreeningServiceHelper;
 import com.android.server.telecom.CallsManager;
-import com.android.server.telecom.DefaultDialerCache;
 import com.android.server.telecom.LogUtils;
 import com.android.server.telecom.ParcelableCallUtils;
 import com.android.server.telecom.PhoneAccountRegistrar;
-import com.android.server.telecom.TelecomServiceImpl;
+import com.android.server.telecom.TelecomServiceImpl.SettingsSecureAdapter;
 import com.android.server.telecom.TelecomSystem;
 
 import java.util.List;
@@ -47,7 +54,13 @@
  * Binds to {@link ICallScreeningService} to allow call blocking. A single instance of this class
  * handles a single call.
  */
-public class CallScreeningServiceFilter implements IncomingCallFilter.CallFilter {
+public class CallScreeningServiceFilter {
+
+    public interface CallScreeningFilterResultCallback {
+        void onCallScreeningFilterComplete(Call call, CallFilteringResult result, String
+                packageName);
+    }
+
     private class CallScreeningServiceConnection implements ServiceConnection {
         @Override
         public void onServiceConnected(ComponentName componentName, IBinder service) {
@@ -108,20 +121,26 @@
                 String callId,
                 boolean shouldReject,
                 boolean shouldAddToCallLog,
-                boolean shouldShowNotification) {
+                boolean shouldShowNotification,
+                ComponentName componentName) {
             Log.startSession("CSCR.dC");
             long token = Binder.clearCallingIdentity();
             try {
                 synchronized (mTelecomLock) {
+                    boolean isServiceRequestingLogging = isLoggable(componentName,
+                            shouldAddToCallLog);
                     Log.i(this, "disallowCall(%s), shouldReject: %b, shouldAddToCallLog: %b, "
                                     + "shouldShowNotification: %b", callId, shouldReject,
-                            shouldAddToCallLog, shouldShowNotification);
+                            isServiceRequestingLogging, shouldShowNotification);
                     if (mCall != null && mCall.getId().equals(callId)) {
                         mResult = new CallFilteringResult(
                                 false, // shouldAllowCall
                                 shouldReject, //shouldReject
-                                shouldAddToCallLog, //shouldAddToCallLog
-                                shouldShowNotification // shouldShowNotification
+                                isServiceRequestingLogging, //shouldAddToCallLog
+                                shouldShowNotification, // shouldShowNotification
+                                CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE, //callBlockReason
+                                mAppName, //callScreeningAppName
+                                componentName.flattenToString() //callScreeningComponentName
                         );
                     } else {
                         Log.w(this, "disallowCall, unknown call id: %s", callId);
@@ -133,21 +152,41 @@
                 Log.endSession();
             }
         }
+
+        @Override
+        public void provideCallIdentification(String callId,
+                CallIdentification callIdentification) {
+            Log.startSession("CSCR.pCI");
+            long token = Binder.clearCallingIdentity();
+            try {
+                synchronized (mTelecomLock) {
+                    if (mCall != null && mCall.getId().equals(callId)) {
+                        callIdentification.setCallScreeningAppName(mAppName);
+                        callIdentification.setCallScreeningPackageName(mPackageName);
+                        mCall.setCallIdentification(callIdentification);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(token);
+                Log.endSession();
+            }
+        }
     }
 
     private final Context mContext;
-    private final PhoneAccountRegistrar mPhoneAccountRegistrar;
     private final CallsManager mCallsManager;
-    private final DefaultDialerCache mDefaultDialerCache;
     private final ParcelableCallUtils.Converter mParcelableCallUtilsConverter;
     private final TelecomSystem.SyncRoot mTelecomLock;
+    private final SettingsSecureAdapter mSettingsSecureAdapter;
 
     private Call mCall;
-    private CallFilterResultCallback mCallback;
+    private CallScreeningFilterResultCallback mCallback;
     private ICallScreeningService mService;
     private ServiceConnection mConnection;
-
+    private String mPackageName;
+    private CharSequence mAppName;
     private boolean mHasFinished = false;
+
     private CallFilteringResult mResult = new CallFilteringResult(
             true, // shouldAllowCall
             false, //shouldReject
@@ -159,27 +198,35 @@
             Context context,
             CallsManager callsManager,
             PhoneAccountRegistrar phoneAccountRegistrar,
-            DefaultDialerCache defaultDialerCache,
             ParcelableCallUtils.Converter parcelableCallUtilsConverter,
-            TelecomSystem.SyncRoot lock) {
+            TelecomSystem.SyncRoot lock,
+            SettingsSecureAdapter settingsSecureAdapter) {
         mContext = context;
-        mPhoneAccountRegistrar = phoneAccountRegistrar;
         mCallsManager = callsManager;
-        mDefaultDialerCache = defaultDialerCache;
         mParcelableCallUtilsConverter = parcelableCallUtilsConverter;
         mTelecomLock = lock;
+        mSettingsSecureAdapter = settingsSecureAdapter;
     }
 
-    @Override
-    public void startFilterLookup(Call call, CallFilterResultCallback callback) {
+    public void startCallScreeningFilter(Call call,
+                                         CallScreeningFilterResultCallback callback,
+                                         String packageName,
+                                         CharSequence appName) {
         if (mHasFinished) {
             Log.w(this, "Attempting to reuse CallScreeningServiceFilter. Ignoring.");
             return;
         }
-        Log.addEvent(call, LogUtils.Events.SCREENING_SENT);
+        Log.addEvent(call, LogUtils.Events.SCREENING_SENT, packageName);
         mCall = call;
         mCallback = callback;
-        if (!bindService()) {
+        mPackageName = packageName;
+        mAppName = appName;
+
+        mConnection = new CallScreeningServiceConnection();
+        if (!CallScreeningServiceHelper.bindCallScreeningService(mContext,
+                mCallsManager.getCurrentUserHandle(),
+                mPackageName,
+                mConnection)) {
             Log.i(this, "Could not bind to call screening service");
             finishCallScreening();
         }
@@ -188,11 +235,15 @@
     private void finishCallScreening() {
         if (!mHasFinished) {
             Log.addEvent(mCall, LogUtils.Events.SCREENING_COMPLETED, mResult);
-            mCallback.onCallFilteringComplete(mCall, mResult);
+            mCallback.onCallScreeningFilterComplete(mCall, mResult, mPackageName);
 
             if (mConnection != null) {
                 // We still need to call unbind even if the service disconnected.
-                mContext.unbindService(mConnection);
+                try {
+                    mContext.unbindService(mConnection);
+                } catch (IllegalArgumentException ie) {
+                    Log.e(this, ie, "Unbind error");
+                }
                 mConnection = null;
             }
             mService = null;
@@ -200,65 +251,67 @@
         }
     }
 
-    private boolean bindService() {
-        String dialerPackage = mDefaultDialerCache
-                .getDefaultDialerApplication(UserHandle.USER_CURRENT);
-        if (TextUtils.isEmpty(dialerPackage)) {
-            Log.i(this, "Default dialer is empty. Not performing call screening.");
-            return false;
+    private void onServiceBound(ICallScreeningService service) {
+        mService = service;
+        try {
+            // Important: Only send a minimal subset of the call to the screening service.
+            mService.screenCall(new CallScreeningAdapter(),
+                    mParcelableCallUtilsConverter.toParcelableCallForScreening(mCall));
+        } catch (RemoteException e) {
+            Log.e(this, e, "Failed to set the call screening adapter.");
+            finishCallScreening();
+        }
+    }
+
+    private boolean isLoggable(ComponentName componentName, boolean shouldAddToCallLog) {
+        if (isCarrierCallScreeningApp(componentName)) {
+            return shouldAddToCallLog;
+        } else if (isDefaultDialer(componentName) || isUserChosenCallScreeningApp(componentName)) {
+            return true;
         }
 
-        Intent intent = new Intent(CallScreeningService.SERVICE_INTERFACE)
-            .setPackage(dialerPackage);
-        List<ResolveInfo> entries = mContext.getPackageManager().queryIntentServicesAsUser(
-                intent, 0, mCallsManager.getCurrentUserHandle().getIdentifier());
-        if (entries.isEmpty()) {
-            Log.i(this, "There are no call screening services installed on this device.");
-            return false;
+        return shouldAddToCallLog;
+    }
+
+    private boolean isCarrierCallScreeningApp(ComponentName componentName) {
+        String carrierCallScreeningApp = null;
+        CarrierConfigManager configManager = (CarrierConfigManager) mContext
+                .getSystemService(Context.CARRIER_CONFIG_SERVICE);
+        PersistableBundle configBundle = configManager.getConfig();
+        if (configBundle != null) {
+            carrierCallScreeningApp = configBundle
+                    .getString(CarrierConfigManager.KEY_CARRIER_CALL_SCREENING_APP_STRING);
         }
 
-        ResolveInfo entry = entries.get(0);
-        if (entry.serviceInfo == null) {
-            Log.w(this, "The call screening service has invalid service info");
-            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.addEvent(mCall, LogUtils.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;
+        if (!TextUtils.isEmpty(carrierCallScreeningApp) && carrierCallScreeningApp
+                .equals(componentName.flattenToString())) {
             return true;
         }
 
         return false;
     }
 
-    private void onServiceBound(ICallScreeningService service) {
-        mService = service;
-        try {
-            mService.screenCall(new CallScreeningAdapter(),
-                    mParcelableCallUtilsConverter.toParcelableCall(
-                            mCall,
-                            false, /* includeVideoProvider */
-                            mPhoneAccountRegistrar));
-        } catch (RemoteException e) {
-            Log.e(this, e, "Failed to set the call screening adapter.");
-            finishCallScreening();
+    private boolean isDefaultDialer(ComponentName componentName) {
+        String defaultDialer = TelecomManager.from(mContext).getDefaultDialerPackage();
+
+        if (!TextUtils.isEmpty(defaultDialer) && defaultDialer
+                .equals(componentName.getPackageName())) {
+            return true;
         }
+
+        return false;
+    }
+
+    private boolean isUserChosenCallScreeningApp(ComponentName componentName) {
+        String defaultCallScreeningApplication = mSettingsSecureAdapter
+                .getStringForUser(mContext.getContentResolver(),
+                        Settings.Secure.CALL_SCREENING_DEFAULT_COMPONENT, UserHandle.USER_CURRENT);
+
+        if (!TextUtils.isEmpty(defaultCallScreeningApplication) && defaultCallScreeningApplication
+                .equals(componentName.flattenToString())) {
+            return true;
+        }
+
+        return false;
     }
 }
diff --git a/src/com/android/server/telecom/callfiltering/DirectToVoicemailCallFilter.java b/src/com/android/server/telecom/callfiltering/DirectToVoicemailCallFilter.java
index 2ac82dc..3a8ff7d 100644
--- a/src/com/android/server/telecom/callfiltering/DirectToVoicemailCallFilter.java
+++ b/src/com/android/server/telecom/callfiltering/DirectToVoicemailCallFilter.java
@@ -17,6 +17,7 @@
 package com.android.server.telecom.callfiltering;
 
 import android.net.Uri;
+import android.provider.CallLog;
 import android.telecom.Log;
 
 import com.android.internal.telephony.CallerInfo;
@@ -49,7 +50,11 @@
                                         false, // shouldAllowCall
                                         true, // shouldReject
                                         true, // shouldAddToCallLog
-                                        true // shouldShowNotification
+                                        true, // shouldShowNotification
+                                        CallLog.Calls.BLOCK_REASON_DIRECT_TO_VOICEMAIL,
+                                        //callBlockReason
+                                        null, //callScreeningAppName
+                                        null // callScreeningComponentName
                                 );
                             } else {
                                 result = new CallFilteringResult(
diff --git a/src/com/android/server/telecom/callredirection/CallRedirectionCallback.java b/src/com/android/server/telecom/callredirection/CallRedirectionCallback.java
new file mode 100644
index 0000000..24ac326
--- /dev/null
+++ b/src/com/android/server/telecom/callredirection/CallRedirectionCallback.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2018 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.callredirection;
+
+import com.android.server.telecom.Call;
+
+public interface CallRedirectionCallback {
+    void onCallRedirectionComplete(Call call);
+}
diff --git a/src/com/android/server/telecom/callredirection/CallRedirectionProcessor.java b/src/com/android/server/telecom/callredirection/CallRedirectionProcessor.java
new file mode 100644
index 0000000..28ecbc7
--- /dev/null
+++ b/src/com/android/server/telecom/callredirection/CallRedirectionProcessor.java
@@ -0,0 +1,422 @@
+/*
+ * Copyright (C) 2018 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.callredirection;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.telecom.CallRedirectionService;
+import android.telecom.GatewayInfo;
+import android.telecom.Log;
+import android.telecom.Logging.Runnable;
+import android.telecom.PhoneAccountHandle;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telecom.ICallRedirectionAdapter;
+import com.android.internal.telecom.ICallRedirectionService;
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.LogUtils;
+import com.android.server.telecom.PhoneAccountRegistrar;
+import com.android.server.telecom.TelecomSystem;
+import com.android.server.telecom.Timeouts;
+
+/**
+ * A single instance of call redirection processor that handles the call redirection with
+ * user-defined {@link CallRedirectionService} and carrier {@link CallRedirectionService} for a
+ * single call.
+ *
+ * A user-defined call redirection will be performed firstly and a carrier call redirection will be
+ * performed after that; there will be a total of two call redirection cycles.
+ *
+ * A call redirection cycle is a cycle:
+ * 1) Telecom requests a call redirection of a call with a specific {@link CallRedirectionService},
+ * 2) Telecom receives the response either from a specific {@link CallRedirectionService} or from
+ * the timeout.
+ *
+ * Telecom should return to {@link CallsManager} at the end of current call redirection
+ * cycle, if
+ * 1) {@link CallRedirectionService} sends {@link CallRedirectionService#cancelCall()} response
+ * before timeout;
+ * or 2) Telecom finishes call redirection with carrier {@link CallRedirectionService}.
+ */
+public class CallRedirectionProcessor implements CallRedirectionCallback {
+
+    private class CallRedirectionAttempt {
+        private final ComponentName mComponentName;
+        private final String mServiceType;
+        private ServiceConnection mConnection;
+        private ICallRedirectionService mService;
+
+        private CallRedirectionAttempt(ComponentName componentName, String serviceType) {
+            mComponentName = componentName;
+            mServiceType = serviceType;
+        }
+
+        private void process() {
+            Intent intent = new Intent(CallRedirectionService.SERVICE_INTERFACE)
+                    .setComponent(mComponentName);
+            ServiceConnection connection = new CallRedirectionServiceConnection();
+            if (mContext.bindServiceAsUser(
+                    intent,
+                    connection,
+                    Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
+                    UserHandle.CURRENT)) {
+                Log.d(this, "bindService, found " + mServiceType + " call redirection service,"
+                        + " waiting for it to connect");
+                mConnection = connection;
+            }
+        }
+
+        private void onServiceBound(ICallRedirectionService service) {
+            mService = service;
+            try {
+                mHandle = mCallRedirectionProcessorHelper.formatNumberForRedirection(mHandle);
+                // Telecom does not perform user interactions for carrier call redirection.
+                mService.placeCall(new CallRedirectionAdapter(), mHandle, mPhoneAccountHandle,
+                        mAllowInteractiveResponse
+                                && mServiceType.equals(SERVICE_TYPE_USER_DEFINED));
+                Log.addEvent(mCall, mServiceType.equals(SERVICE_TYPE_USER_DEFINED)
+                        ? LogUtils.Events.REDIRECTION_SENT_USER
+                        : LogUtils.Events.REDIRECTION_SENT_CARRIER, mComponentName);
+                Log.d(this, "Requested placeCall with [handle]" + Log.pii(mHandle)
+                        + " [phoneAccountHandle]" + mPhoneAccountHandle);
+            } catch (RemoteException e) {
+                Log.e(this, e, "Failed to request with the found " + mServiceType + " call"
+                        + " redirection service");
+                finishCallRedirection();
+            }
+        }
+
+        private void finishCallRedirection() {
+            if (((mServiceType.equals(SERVICE_TYPE_CARRIER)) && mIsCarrierRedirectionPending)
+                || ((mServiceType.equals(SERVICE_TYPE_USER_DEFINED))
+                    && mIsUserDefinedRedirectionPending)) {
+                if (mConnection != null) {
+                    // We still need to call unbind even if the service disconnected.
+                    mContext.unbindService(mConnection);
+                    mConnection = null;
+                }
+                mService = null;
+                onCallRedirectionComplete(mCall);
+            }
+        }
+
+        private class CallRedirectionServiceConnection implements ServiceConnection {
+            @Override
+            public void onServiceConnected(ComponentName componentName, IBinder service) {
+                Log.startSession("CRSC.oSC");
+                try {
+                    synchronized (mTelecomLock) {
+                        Log.addEvent(mCall, mServiceType.equals(SERVICE_TYPE_USER_DEFINED)
+                                ? LogUtils.Events.REDIRECTION_BOUND_USER
+                                : LogUtils.Events.REDIRECTION_BOUND_CARRIER, componentName);
+                        onServiceBound(ICallRedirectionService.Stub.asInterface(service));
+                    }
+                } finally {
+                    Log.endSession();
+                }
+            }
+
+            @Override
+            public void onServiceDisconnected(ComponentName componentName) {
+                Log.startSession("CRSC.oSD");
+                try {
+                    synchronized (mTelecomLock) {
+                        finishCallRedirection();
+                    }
+                } finally {
+                    Log.endSession();
+                }
+            }
+        }
+
+        private class CallRedirectionAdapter extends ICallRedirectionAdapter.Stub {
+            @Override
+            public void cancelCall() {
+                Log.startSession("CRA.cC");
+                long token = Binder.clearCallingIdentity();
+                try {
+                    synchronized (mTelecomLock) {
+                        Log.d(this, "Received cancelCall from " +  mServiceType + " call"
+                                + " redirection service");
+                        mShouldCancelCall = true;
+                        finishCallRedirection();
+                    }
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                    Log.endSession();
+                }
+            }
+
+            @Override
+            public void placeCallUnmodified() {
+                Log.startSession("CRA.pCU");
+                long token = Binder.clearCallingIdentity();
+                try {
+                    synchronized (mTelecomLock) {
+                        Log.d(this, "Received placeCallUnmodified from " +  mServiceType + " call"
+                                + " redirection service");
+                        finishCallRedirection();
+                    }
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                    Log.endSession();
+                }
+            }
+
+            @Override
+            public void redirectCall(Uri handle, PhoneAccountHandle targetPhoneAccount,
+                                     boolean confirmFirst) {
+                Log.startSession("CRA.rC");
+                long token = Binder.clearCallingIdentity();
+                try {
+                    synchronized (mTelecomLock) {
+                        mHandle = handle;
+                        mPhoneAccountHandle = targetPhoneAccount;
+                        mUiAction = (confirmFirst && mServiceType.equals(SERVICE_TYPE_USER_DEFINED)
+                                && mAllowInteractiveResponse)
+                                ? UI_TYPE_USER_DEFINED_ASK_FOR_CONFIRM : mUiAction;
+                        Log.d(this, "Received redirectCall with [handle]" + Log.pii(mHandle)
+                                + " [phoneAccountHandle]" + mPhoneAccountHandle + " from "
+                                + mServiceType + " call redirection service");
+                        finishCallRedirection();
+                    }
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                    Log.endSession();
+                }
+            }
+        }
+    }
+
+    private final Context mContext;
+    private final CallsManager mCallsManager;
+    private final Call mCall;
+    private final boolean mAllowInteractiveResponse;
+    private final GatewayInfo mGatewayInfo;
+    private final boolean mSpeakerphoneOn;
+    private final int mVideoState;
+    private final Timeouts.Adapter mTimeoutsAdapter;
+    private final TelecomSystem.SyncRoot mTelecomLock;
+    private final Handler mHandler = new Handler(Looper.getMainLooper());
+
+    private CallRedirectionAttempt mAttempt;
+    private CallRedirectionProcessorHelper mCallRedirectionProcessorHelper;
+
+    public static final String SERVICE_TYPE_CARRIER = "carrier";
+    public static final String SERVICE_TYPE_USER_DEFINED = "user_defined";
+    public static final String UI_TYPE_NO_ACTION = "no_action";
+    public static final String UI_TYPE_USER_DEFINED_TIMEOUT = "user_defined_timeout";
+    public static final String UI_TYPE_USER_DEFINED_ASK_FOR_CONFIRM
+            = "user_defined_ask_for_confirm";
+
+    private PhoneAccountHandle mPhoneAccountHandle;
+    private Uri mHandle;
+
+    /**
+     * Indicates if Telecom should cancel the call when the whole call redirection finishes.
+     */
+    private boolean mShouldCancelCall = false;
+    /**
+     * Indicates Telecom should handle different types of UI if need.
+     */
+    private String mUiAction = UI_TYPE_NO_ACTION;
+    /**
+     * Indicates if Telecom is waiting for a callback from a user-defined
+     * {@link CallRedirectionService}.
+     */
+    private boolean mIsUserDefinedRedirectionPending = false;
+    /**
+     * Indicates if Telecom is waiting for a callback from a carrier
+     * {@link CallRedirectionService}.
+     */
+    private boolean mIsCarrierRedirectionPending = false;
+
+    public CallRedirectionProcessor(
+            Context context,
+            CallsManager callsManager,
+            Call call,
+            Uri handle,
+            PhoneAccountRegistrar phoneAccountRegistrar,
+            GatewayInfo gatewayInfo,
+            boolean speakerphoneOn,
+            int videoState) {
+        mContext = context;
+        mCallsManager = callsManager;
+        mCall = call;
+        mHandle = handle;
+        mPhoneAccountHandle = call.getTargetPhoneAccount();
+        mGatewayInfo = gatewayInfo;
+        mSpeakerphoneOn = speakerphoneOn;
+        mVideoState = videoState;
+        mTimeoutsAdapter = callsManager.getTimeoutsAdapter();
+        mTelecomLock = callsManager.getLock();
+        /**
+         * The current rule to decide whether the implemented {@link CallRedirectionService} should
+         * allow interactive responses with users is only based on whether it is in car mode.
+         */
+        mAllowInteractiveResponse = !callsManager.getSystemStateHelper().isCarMode();
+        mCallRedirectionProcessorHelper = new CallRedirectionProcessorHelper(
+                context, callsManager, phoneAccountRegistrar);
+    }
+
+    @Override
+    public void onCallRedirectionComplete(Call call) {
+        // synchronized on mTelecomLock to enter into Telecom.
+        mHandler.post(new Runnable("CRP.oCRC", mTelecomLock) {
+            @Override
+            public void loggedRun() {
+                mHandle = mCallRedirectionProcessorHelper.processNumberWhenRedirectionComplete(
+                        mHandle);
+                if (mIsUserDefinedRedirectionPending) {
+                    Log.addEvent(mCall, LogUtils.Events.REDIRECTION_COMPLETED_USER);
+                    mIsUserDefinedRedirectionPending = false;
+                    if (mShouldCancelCall) {
+                        mCallsManager.onCallRedirectionComplete(mCall, mHandle,
+                                mPhoneAccountHandle, mGatewayInfo, mSpeakerphoneOn, mVideoState,
+                                mShouldCancelCall, mUiAction);
+                    } else {
+                        performCarrierCallRedirection();
+                    }
+                }
+                if (mIsCarrierRedirectionPending) {
+                    Log.addEvent(mCall, LogUtils.Events.REDIRECTION_COMPLETED_CARRIER);
+                    mIsCarrierRedirectionPending = false;
+                    mCallsManager.onCallRedirectionComplete(mCall, mHandle,
+                            mPhoneAccountHandle, mGatewayInfo, mSpeakerphoneOn, mVideoState,
+                            mShouldCancelCall, mUiAction);
+                }
+            }
+        }.prepare());
+    }
+
+    /**
+     * The entry to perform call redirection of the call from (@link CallsManager)
+     */
+    public void performCallRedirection() {
+        // If the Gateway Info is set with intent, do not perform call redirection.
+        if (mGatewayInfo != null) {
+            mCallsManager.onCallRedirectionComplete(mCall, mHandle, mPhoneAccountHandle,
+                    mGatewayInfo, mSpeakerphoneOn, mVideoState, mShouldCancelCall, mUiAction);
+        } else {
+            mCallRedirectionProcessorHelper.storePostDialDigits(mHandle);
+            performUserDefinedCallRedirection();
+        }
+    }
+
+    private void performUserDefinedCallRedirection() {
+        Log.d(this, "performUserDefinedCallRedirection");
+        ComponentName componentName =
+                mCallRedirectionProcessorHelper.getUserDefinedCallRedirectionService();
+        if (componentName != null) {
+            mAttempt = new CallRedirectionAttempt(componentName, SERVICE_TYPE_USER_DEFINED);
+            mAttempt.process();
+            mIsUserDefinedRedirectionPending = true;
+            processTimeoutForCallRedirection(SERVICE_TYPE_USER_DEFINED);
+        } else {
+            Log.i(this, "There are no user-defined call redirection services installed on this"
+                    + " device.");
+            performCarrierCallRedirection();
+        }
+    }
+
+    private void performCarrierCallRedirection() {
+        Log.d(this, "performCarrierCallRedirection");
+        ComponentName componentName =
+                mCallRedirectionProcessorHelper.getCarrierCallRedirectionService(
+                        mPhoneAccountHandle);
+        if (componentName != null) {
+            mAttempt = new CallRedirectionAttempt(componentName, SERVICE_TYPE_CARRIER);
+            mAttempt.process();
+            mIsCarrierRedirectionPending = true;
+            processTimeoutForCallRedirection(SERVICE_TYPE_CARRIER);
+        } else {
+            Log.i(this, "There are no carrier call redirection services installed on this"
+                    + " device.");
+            mCallsManager.onCallRedirectionComplete(mCall, mHandle,
+                    mPhoneAccountHandle, mGatewayInfo, mSpeakerphoneOn, mVideoState,
+                    mShouldCancelCall, mUiAction);
+        }
+    }
+
+    private void processTimeoutForCallRedirection(String serviceType) {
+        long timeout = serviceType.equals(SERVICE_TYPE_USER_DEFINED) ?
+            mTimeoutsAdapter.getUserDefinedCallRedirectionTimeoutMillis(
+                mContext.getContentResolver()) : mTimeoutsAdapter
+            .getCarrierCallRedirectionTimeoutMillis(mContext.getContentResolver());
+
+        mHandler.postDelayed(new Runnable("CRP.pTFCR", null) {
+            @Override
+            public void loggedRun() {
+                boolean isCurrentRedirectionPending =
+                        serviceType.equals(SERVICE_TYPE_USER_DEFINED) ?
+                                mIsUserDefinedRedirectionPending : mIsCarrierRedirectionPending;
+                if (isCurrentRedirectionPending) {
+                    Log.i(this, serviceType + " call redirection has timed out.");
+                    Log.addEvent(mCall, serviceType.equals(SERVICE_TYPE_USER_DEFINED)
+                            ? LogUtils.Events.REDIRECTION_TIMED_OUT_USER
+                            : LogUtils.Events.REDIRECTION_TIMED_OUT_CARRIER);
+                    if (serviceType.equals(SERVICE_TYPE_USER_DEFINED)) {
+                        mUiAction = UI_TYPE_USER_DEFINED_TIMEOUT;
+                        mShouldCancelCall = true;
+                    }
+                    onCallRedirectionComplete(mCall);
+                }
+            }
+        }.prepare(), timeout);
+    }
+
+    /**
+     * Checks if Telecom can make call redirection with any available call redirection service.
+     *
+     * @return {@code true} if it can; {@code false} otherwise.
+     */
+    public boolean canMakeCallRedirectionWithService() {
+        boolean canMakeCallRedirectionWithService =
+                mCallRedirectionProcessorHelper.getUserDefinedCallRedirectionService() != null
+                        || mCallRedirectionProcessorHelper.getCarrierCallRedirectionService(
+                                mPhoneAccountHandle) != null;
+        Log.w(this, "Can make call redirection with any available service: "
+                + canMakeCallRedirectionWithService);
+        return canMakeCallRedirectionWithService;
+    }
+
+    /**
+     * Returns the handler, for testing purposes.
+     */
+    @VisibleForTesting
+    public Handler getHandler() {
+        return mHandler;
+    }
+
+    /**
+     * Set CallRedirectionProcessorHelper for testing purposes.
+     */
+    @VisibleForTesting
+    public void setCallRedirectionServiceHelper(
+            CallRedirectionProcessorHelper callRedirectionProcessorHelper) {
+        mCallRedirectionProcessorHelper = callRedirectionProcessorHelper;
+    }
+}
diff --git a/src/com/android/server/telecom/callredirection/CallRedirectionProcessorHelper.java b/src/com/android/server/telecom/callredirection/CallRedirectionProcessorHelper.java
new file mode 100644
index 0000000..5e3f0da
--- /dev/null
+++ b/src/com/android/server/telecom/callredirection/CallRedirectionProcessorHelper.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2018 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.callredirection;
+
+import android.Manifest;
+import android.app.AppOpsManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.PersistableBundle;
+import android.provider.Settings;
+import android.telecom.CallRedirectionService;
+import android.telecom.Log;
+import android.telecom.PhoneAccountHandle;
+import android.telephony.CarrierConfigManager;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.PhoneAccountRegistrar;
+
+import java.util.List;
+
+public class CallRedirectionProcessorHelper {
+
+    private final Context mContext;
+    private final CallsManager mCallsManager;
+    private final PhoneAccountRegistrar mPhoneAccountRegistrar;
+    private String mOriginalPostDialDigits = null;
+
+    public CallRedirectionProcessorHelper(
+            Context context,
+            CallsManager callsManager,
+            PhoneAccountRegistrar phoneAccountRegistrar) {
+        mContext = context;
+        mCallsManager = callsManager;
+        mPhoneAccountRegistrar = phoneAccountRegistrar;
+    }
+
+    @VisibleForTesting
+    // TODO integarte with RoleManager functions
+    public ComponentName getUserDefinedCallRedirectionService() {
+        String componentNameString = Settings.Secure.getStringForUser(
+                mContext.getContentResolver(),
+                Settings.Secure.CALL_REDIRECTION_DEFAULT_APPLICATION,
+                mCallsManager.getCurrentUserHandle().getIdentifier());
+        if (TextUtils.isEmpty(componentNameString)) {
+            Log.i(this, "Default user-defined call redirection is empty. Not performing call"
+                    + " redirection.");
+            return null;
+        }
+        return getComponentName(componentNameString,
+                CallRedirectionProcessor.SERVICE_TYPE_USER_DEFINED);
+    }
+
+    @VisibleForTesting
+    public ComponentName getCarrierCallRedirectionService(
+            PhoneAccountHandle targetPhoneAccountHandle) {
+        CarrierConfigManager configManager = (CarrierConfigManager)
+                mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
+        if (configManager == null) {
+            Log.i(this, "Cannot get CarrierConfigManager.");
+            return null;
+        }
+        PersistableBundle pb = configManager.getConfigForSubId(mPhoneAccountRegistrar
+                .getSubscriptionIdForPhoneAccount(targetPhoneAccountHandle));
+        if (pb == null) {
+            Log.i(this, "Cannot get PersistableBundle.");
+            return null;
+        }
+        String componentNameString = pb.getString(
+                CarrierConfigManager.KEY_CALL_REDIRECTION_SERVICE_COMPONENT_NAME_STRING);
+        if (componentNameString == null) {
+            Log.i(this, "Cannot get carrier componentNameString.");
+            return null;
+        }
+        return getComponentName(componentNameString,
+                CallRedirectionProcessor.SERVICE_TYPE_CARRIER);
+    }
+
+    protected ComponentName getComponentName(String componentNameString, String serviceType) {
+        ComponentName componentName = ComponentName.unflattenFromString(componentNameString);
+        if (componentName == null) {
+            Log.w(this, "ComponentName is null from string: " + componentNameString);
+            return null;
+        }
+        Intent intent = new Intent(CallRedirectionService.SERVICE_INTERFACE);
+        intent.setComponent(componentName);
+        List<ResolveInfo> entries = mContext.getPackageManager().queryIntentServicesAsUser(
+                intent, 0, mCallsManager.getCurrentUserHandle().getIdentifier());
+        if (entries.isEmpty()) {
+            Log.i(this, "There are no " + serviceType + " call redirection services installed" +
+                    " on this device.");
+            return null;
+        } else if (entries.size() != 1) {
+            Log.i(this, "There are multiple " + serviceType + " call redirection services" +
+                    " installed on this device.");
+            return null;
+        }
+        ResolveInfo entry = entries.get(0);
+        if (entry.serviceInfo == null) {
+            Log.w(this, "The " + serviceType + " call redirection service has invalid" +
+                    " service info");
+            return null;
+        }
+        if (entry.serviceInfo.permission == null || !entry.serviceInfo.permission.equals(
+                Manifest.permission.BIND_CALL_REDIRECTION_SERVICE)) {
+            Log.w(this, "CallRedirectionService must require BIND_CALL_REDIRECTION_SERVICE"
+                    + " permission: " + entry.serviceInfo.packageName);
+            return null;
+        }
+        AppOpsManager appOps = (AppOpsManager) mContext.getSystemService(
+                Context.APP_OPS_SERVICE);
+        if (appOps.noteOpNoThrow(AppOpsManager.OP_PROCESS_OUTGOING_CALLS, Binder.getCallingUid(),
+                entry.serviceInfo.packageName) != AppOpsManager.MODE_ALLOWED) {
+            Log.w(this, "App Ops does not allow " + entry.serviceInfo.packageName);
+            return null;
+        }
+        return componentName;
+    }
+
+    /**
+     * Format Number to E164, and remove post dial digits.
+     */
+    protected Uri formatNumberForRedirection(Uri handle) {
+        return removePostDialDigits(formatNumberToE164(handle));
+    }
+
+    protected Uri processNumberWhenRedirectionComplete(Uri handle) {
+        return appendStoredPostDialDigits(formatNumberForRedirection(handle));
+    }
+
+    protected void storePostDialDigits(Uri handle) {
+        String number = handle.getSchemeSpecificPart();
+        mOriginalPostDialDigits += PhoneNumberUtils.extractPostDialPortion(number);
+        Log.i(this, "storePostDialDigits, stored post dial digits: "
+                + Log.pii(mOriginalPostDialDigits));
+    }
+
+    protected Uri appendStoredPostDialDigits(Uri handle) {
+        String number = handle.getSchemeSpecificPart();
+        number += mOriginalPostDialDigits;
+        Log.i(this, "appendStoredPostDialDigits, appended number: " + Log.pii(number));
+        return Uri.fromParts(handle.getScheme(), number, null);
+    }
+
+    protected Uri formatNumberToE164(Uri handle) {
+        String number = handle.getSchemeSpecificPart();
+
+        // Format number to E164
+        TelephonyManager tm = (TelephonyManager) mContext.getSystemService(
+                Context.TELEPHONY_SERVICE);
+        Log.i(this, "formatNumberToE164, original number: " + Log.pii(number));
+        number = PhoneNumberUtils.formatNumberToE164(number, tm.getNetworkCountryIso());
+        Log.i(this, "formatNumberToE164, formatted E164 number: " + Log.pii(number));
+        // if there is a problem with parsing the phone number, formatNumberToE164 will return null;
+        // and should just use the original number in that case.
+        if (number == null) {
+            return handle;
+        } else {
+            return Uri.fromParts(handle.getScheme(), number, null);
+        }
+    }
+
+    protected Uri removePostDialDigits(Uri handle) {
+        String number = handle.getSchemeSpecificPart();
+
+        // Extract the post dial portion
+        number = PhoneNumberUtils.extractNetworkPortionAlt(number);
+        Log.i(this, "removePostDialDigits, number after being extracted post dial digits: "
+                + Log.pii(number));
+        // if there is a problem with parsing the phone number, removePostDialDigits will return
+        // null; and should just use the original number in that case.
+        if (number == null) {
+            return handle;
+        } else {
+            return Uri.fromParts(handle.getScheme(), number, null);
+        }
+    }
+
+}
diff --git a/src/com/android/server/telecom/components/PhoneAccountBroadcastReceiver.java b/src/com/android/server/telecom/components/AppUninstallBroadcastReceiver.java
similarity index 68%
rename from src/com/android/server/telecom/components/PhoneAccountBroadcastReceiver.java
rename to src/com/android/server/telecom/components/AppUninstallBroadcastReceiver.java
index 7737cd8..3a0d517 100644
--- a/src/com/android/server/telecom/components/PhoneAccountBroadcastReceiver.java
+++ b/src/com/android/server/telecom/components/AppUninstallBroadcastReceiver.java
@@ -19,9 +19,12 @@
 import com.android.server.telecom.PhoneAccountRegistrar;
 
 import android.content.BroadcastReceiver;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.net.Uri;
+import android.os.UserHandle;
+import android.provider.Settings;
 import android.telecom.TelecomManager;
 
 import java.lang.String;
@@ -30,14 +33,20 @@
  * Captures {@code android.intent.action.ACTION_PACKAGE_FULLY_REMOVED} intents and triggers the
  * removal of associated {@link android.telecom.PhoneAccount}s via the
  * {@link com.android.telecom.PhoneAccountRegistrar}.
+ *
  * Note: This class listens for the {@code PACKAGE_FULLY_REMOVED} intent rather than
  * {@code PACKAGE_REMOVED} as {@code PACKAGE_REMOVED} is triggered on re-installation of the same
  * package, where {@code PACKAGE_FULLY_REMOVED} is triggered only when an application is completely
- * uninstalled.  This is desirable as we do not wish to un-register all
+ * uninstalled.
+ *
+ * This is desirable as we do not wish to un-register all
  * {@link android.telecom.PhoneAccount}s associated with a package being re-installed to ensure
  * the enabled state of the accounts is retained.
+ *
+ * When default call screening application is removed, set
+ * {@link Settings.Secure.CALL_SCREENING_DEFAULT_APPLICATION} as null into provider.
  */
-public class PhoneAccountBroadcastReceiver extends BroadcastReceiver {
+public class AppUninstallBroadcastReceiver extends BroadcastReceiver {
     /**
      * Receives the intents the class is configured to received.
      *
@@ -54,6 +63,7 @@
 
             String packageName = uri.getSchemeSpecificPart();
             handlePackageRemoved(context, packageName);
+            handleUninstallOfCallScreeningService(context, packageName);
         }
     }
 
@@ -69,4 +79,20 @@
             telecomManager.clearAccountsForPackage(packageName);
         }
     }
+
+    private void handleUninstallOfCallScreeningService(Context context, String packageName) {
+        ComponentName componentName = null;
+        String defaultCallScreeningApp = Settings.Secure
+            .getStringForUser(context.getContentResolver(),
+                Settings.Secure.CALL_SCREENING_DEFAULT_COMPONENT, UserHandle.USER_CURRENT);
+
+        if (defaultCallScreeningApp != null) {
+            componentName = ComponentName.unflattenFromString(defaultCallScreeningApp);
+        }
+
+        if (componentName != null && componentName.getPackageName().equals(packageName)) {
+            Settings.Secure.putStringForUser(context.getContentResolver(),
+                Settings.Secure.CALL_SCREENING_DEFAULT_COMPONENT, null, UserHandle.USER_CURRENT);
+        }
+    }
 }
diff --git a/src/com/android/server/telecom/components/PrimaryCallReceiver.java b/src/com/android/server/telecom/components/PrimaryCallReceiver.java
deleted file mode 100644
index f19a243..0000000
--- a/src/com/android/server/telecom/components/PrimaryCallReceiver.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package com.android.server.telecom.components;
-
-import com.android.server.telecom.TelecomSystem;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.telecom.Log;
-
-/**
- * Single point of entry for all outgoing and incoming calls. {@link UserCallIntentProcessor} serves
- * as a trampoline that captures call intents for individual users and forwards it to
- * the {@link PrimaryCallReceiver} which interacts with the rest of Telecom, both of which run only as
- * the primary user.
- */
-public class PrimaryCallReceiver extends BroadcastReceiver implements TelecomSystem.Component {
-
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        Log.startSession("PCR.oR");
-        synchronized (getTelecomSystem().getLock()) {
-            getTelecomSystem().getCallIntentProcessor().processIntent(intent);
-        }
-        Log.endSession();
-    }
-
-    @Override
-    public TelecomSystem getTelecomSystem() {
-        return TelecomSystem.getInstance();
-    }
-}
diff --git a/src/com/android/server/telecom/components/TelecomService.java b/src/com/android/server/telecom/components/TelecomService.java
index 9a09636..1beae2b 100644
--- a/src/com/android/server/telecom/components/TelecomService.java
+++ b/src/com/android/server/telecom/components/TelecomService.java
@@ -33,6 +33,7 @@
 import com.android.server.telecom.AsyncRingtonePlayer;
 import com.android.server.telecom.BluetoothAdapterProxy;
 import com.android.server.telecom.BluetoothPhoneServiceImpl;
+import com.android.server.telecom.CallAudioRouteStateMachine;
 import com.android.server.telecom.CallerInfoAsyncQueryFactory;
 import com.android.server.telecom.CallsManager;
 import com.android.server.telecom.ClockProxy;
@@ -49,6 +50,8 @@
 import com.android.server.telecom.InCallWakeLockController;
 import com.android.server.telecom.ProximitySensorManager;
 import com.android.server.telecom.R;
+import com.android.server.telecom.RoleManagerAdapter;
+import com.android.server.telecom.RoleManagerAdapterImpl;
 import com.android.server.telecom.TelecomSystem;
 import com.android.server.telecom.TelecomWakeLock;
 import com.android.server.telecom.Timeouts;
@@ -167,20 +170,13 @@
                                             phoneAccountRegistrar);
                                 }
                             },
-                            new ConnectionServiceFocusManager
-                                    .ConnectionServiceFocusManagerFactory() {
-                                @Override
-                                public ConnectionServiceFocusManager create(
-                                        ConnectionServiceFocusManager.CallsManagerRequester requester,
-                                        Looper looper) {
-                                    return new ConnectionServiceFocusManager(requester, looper);
-                                }
-                            },
+                            ConnectionServiceFocusManager::new,
                             new Timeouts.Adapter(),
                             new AsyncRingtonePlayer(shouldPauseBetweenRingtoneRepeat),
                             new PhoneNumberUtilsAdapterImpl(),
                             new IncomingCallNotifier(context),
                             ToneGenerator::new,
+                            new CallAudioRouteStateMachine.Factory(),
                             new ClockProxy() {
                                 @Override
                                 public long currentTimeMillis() {
@@ -191,7 +187,8 @@
                                 public long elapsedRealtime() {
                                     return SystemClock.elapsedRealtime();
                                 }
-                            }));
+                            },
+                            new RoleManagerAdapterImpl()));
         }
         if (BluetoothAdapter.getDefaultAdapter() != null) {
             context.startService(new Intent(context, BluetoothPhoneService.class));
diff --git a/src/com/android/server/telecom/components/UserCallIntentProcessor.java b/src/com/android/server/telecom/components/UserCallIntentProcessor.java
index 0c8525f..ae4a7d8 100644
--- a/src/com/android/server/telecom/components/UserCallIntentProcessor.java
+++ b/src/com/android/server/telecom/components/UserCallIntentProcessor.java
@@ -159,7 +159,7 @@
         // Save the user handle of current user before forwarding the intent to primary user.
         intent.putExtra(CallIntentProcessor.KEY_INITIATING_USER, mUserHandle);
 
-        sendIntentToDestination(intent, isLocalInvocation);
+        sendIntentToDestination(intent, isLocalInvocation, callingPackageName);
     }
 
     private boolean isDefaultOrSystemDialer(String callingPackageName) {
@@ -189,27 +189,29 @@
     }
 
     /**
-     * Potentially trampolines the intent to the broadcast receiver that runs only as the primary
-     * user.  If the caller is local to the Telecom service, we send the intent to Telecom without
-     * rebroadcasting it.
+     * Potentially trampolines the intent to Telecom via TelecomServiceImpl.
+     * If the caller is local to the Telecom service, we send the intent to Telecom without
+     * sending it through TelecomServiceImpl.
      */
-    private boolean sendIntentToDestination(Intent intent, boolean isLocalInvocation) {
+    private boolean sendIntentToDestination(Intent intent, boolean isLocalInvocation,
+            String callingPackage) {
         intent.putExtra(CallIntentProcessor.KEY_IS_INCOMING_CALL, false);
         intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-        intent.setClass(mContext, PrimaryCallReceiver.class);
         if (isLocalInvocation) {
             // We are invoking this from TelecomServiceImpl, so TelecomSystem is available.  Don't
             // bother trampolining the intent, just sent it directly to the call intent processor.
             // TODO: We should not be using an intent here; this whole flows needs cleanup.
             Log.i(this, "sendIntentToDestination: send intent to Telecom directly.");
             synchronized (TelecomSystem.getInstance().getLock()) {
-                TelecomSystem.getInstance().getCallIntentProcessor().processIntent(intent);
+                TelecomSystem.getInstance().getCallIntentProcessor().processIntent(intent,
+                        callingPackage);
             }
         } else {
             // We're calling from the UserCallActivity, so the TelecomSystem is not in the same
             // process; we need to trampoline to TelecomSystem in the system server process.
             Log.i(this, "sendIntentToDestination: trampoline to Telecom.");
-            mContext.sendBroadcastAsUser(intent, UserHandle.SYSTEM);
+            TelecomManager tm = (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
+            tm.handleCallIntent(intent);
         }
         return true;
     }
diff --git a/src/com/android/server/telecom/settings/BlockedNumbersActivity.java b/src/com/android/server/telecom/settings/BlockedNumbersActivity.java
index ae7e661..1593e23 100644
--- a/src/com/android/server/telecom/settings/BlockedNumbersActivity.java
+++ b/src/com/android/server/telecom/settings/BlockedNumbersActivity.java
@@ -121,6 +121,7 @@
 
         mAddButton = (TextView) findViewById(R.id.add_blocked);
         mAddButton.setOnClickListener(this);
+        mAddButton.setContentDescription(getText(R.string.block_number));
 
         mProgressBar = (ProgressBar) findViewById(R.id.progress_bar);
         String[] fromColumns = {BlockedNumberContract.BlockedNumbers.COLUMN_ORIGINAL_NUMBER};
diff --git a/src/com/android/server/telecom/settings/BlockedNumbersUtil.java b/src/com/android/server/telecom/settings/BlockedNumbersUtil.java
index 4f45720..5acfe64 100644
--- a/src/com/android/server/telecom/settings/BlockedNumbersUtil.java
+++ b/src/com/android/server/telecom/settings/BlockedNumbersUtil.java
@@ -33,6 +33,7 @@
 import android.widget.Toast;
 
 import com.android.server.telecom.R;
+import com.android.server.telecom.SystemSettingsUtil;
 import com.android.server.telecom.ui.NotificationChannelManager;
 
 import java.util.Locale;
@@ -134,7 +135,8 @@
             carrierConfig = configManager.getDefaultConfig();
         }
         return carrierConfig.getBoolean(
-                CarrierConfigManager.KEY_SUPPORT_ENHANCED_CALL_BLOCKING_BOOL);
+                CarrierConfigManager.KEY_SUPPORT_ENHANCED_CALL_BLOCKING_BOOL)
+                || new SystemSettingsUtil().isEnhancedCallBlockingEnabled(context);
     }
 
     /**
diff --git a/src/com/android/server/telecom/ui/TelecomDeveloperMenu.java b/src/com/android/server/telecom/ui/TelecomDeveloperMenu.java
new file mode 100644
index 0000000..ae2e853
--- /dev/null
+++ b/src/com/android/server/telecom/ui/TelecomDeveloperMenu.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2018 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.ui;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.Switch;
+
+import com.android.server.telecom.R;
+import com.android.server.telecom.SystemSettingsUtil;
+
+/**
+ * Telecom Developer Settings Menu.
+ */
+public class TelecomDeveloperMenu extends Activity {
+
+    private Switch mEnhancedCallingSwitch;
+    private SystemSettingsUtil mSystemSettingsUtil;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mSystemSettingsUtil = new SystemSettingsUtil();
+        setContentView(R.layout.telecom_developer_menu);
+
+        mEnhancedCallingSwitch = findViewById(R.id.switchEnhancedCallBlocking);
+        mEnhancedCallingSwitch.setOnClickListener(l -> {
+            handleEnhancedCallingToggle();
+        });
+        loadPreferences();
+    }
+
+    private void handleEnhancedCallingToggle() {
+        mSystemSettingsUtil.setEnhancedCallBlockingEnabled(this,
+                mEnhancedCallingSwitch.isChecked());
+    }
+
+    private void loadPreferences() {
+        mEnhancedCallingSwitch.setChecked(mSystemSettingsUtil.isEnhancedCallBlockingEnabled(this));
+    }
+}
\ No newline at end of file
diff --git a/testapps/AndroidManifest.xml b/testapps/AndroidManifest.xml
index 48451d1..1373906 100644
--- a/testapps/AndroidManifest.xml
+++ b/testapps/AndroidManifest.xml
@@ -19,9 +19,10 @@
           package="com.android.server.telecom.testapps">
 
     <uses-sdk
-        android:minSdkVersion="23"
-        android:targetSdkVersion="23" />
+        android:minSdkVersion="28"
+        android:targetSdkVersion="28" />
 
+    <uses-permission android:name="android.permission.ACCEPT_HANDOVER" />
     <uses-permission android:name="android.permission.BLUETOOTH" />
     <uses-permission android:name="android.permission.CAMERA" />
     <uses-permission android:name="android.permission.CALL_PHONE" />
@@ -237,5 +238,26 @@
         <receiver android:exported="false"
             android:process="com.android.server.telecom.testapps.SelfMangingCallingApp"
             android:name="com.android.server.telecom.testapps.SelfManagedCallNotificationReceiver" />
+
+        <receiver android:exported="true"
+                  android:name="com.android.server.telecom.testapps.NuisanceReportReceiver">
+            <intent-filter>
+                <action android:name="android.telecom.action.NUISANCE_CALL_STATUS_CHANGED" />
+            </intent-filter>
+        </receiver>
+
+        <service
+            android:name=".TestCallScreeningService"
+            android:permission="android.permission.BIND_SCREENING_SERVICE">
+            <intent-filter>
+                <action android:name="android.telecom.CallScreeningService"/>
+            </intent-filter>
+        </service>
+
+        <activity android:name=".CallScreeningActivity"
+                  android:configChanges="orientation|screenSize|keyboardHidden"
+                  android:excludeFromRecents="true"
+                  android:launchMode="singleInstance">
+        </activity>
     </application>
 </manifest>
diff --git a/testapps/res/layout/call_list_item.xml b/testapps/res/layout/call_list_item.xml
index c9f2ff7..ecaa237 100644
--- a/testapps/res/layout/call_list_item.xml
+++ b/testapps/res/layout/call_list_item.xml
@@ -39,4 +39,37 @@
             android:layout_height="wrap_content"
             android:textSize="25dp"
             android:text="TextView" />
+    <ImageView
+        android:id="@+id/callIdPhoto"
+        android:layout_gravity="left"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+    <TextView
+        android:id="@+id/callIdName"
+        android:layout_gravity="left"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textSize="25dp"
+        android:text="TextView" />
+    <TextView
+        android:id="@+id/callIdDescription"
+        android:layout_gravity="left"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textSize="25dp"
+        android:text="TextView" />
+    <TextView
+        android:id="@+id/callIdDetails"
+        android:layout_gravity="left"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textSize="25dp"
+        android:text="TextView" />
+    <TextView
+        android:id="@+id/callIdType"
+        android:layout_gravity="left"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textSize="25dp"
+        android:text="TextView" />
 </LinearLayout>
diff --git a/testapps/res/layout/self_managed_call_list_item.xml b/testapps/res/layout/self_managed_call_list_item.xml
index 66b5b21..6a31a50 100644
--- a/testapps/res/layout/self_managed_call_list_item.xml
+++ b/testapps/res/layout/self_managed_call_list_item.xml
@@ -65,13 +65,18 @@
         <Button
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:text="Speaker"
+            android:text="🔊"
             android:id="@+id/speakerButton" />
         <Button
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:text="Earpiece"
+            android:text="👂"
             android:id="@+id/earpieceButton" />
+        <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="🎧"
+            android:id="@+id/bluetoothButton" />
         <CheckBox
             android:id="@+id/holdable"
             android:layout_width="wrap_content"
diff --git a/testapps/res/layout/self_managed_sample_main.xml b/testapps/res/layout/self_managed_sample_main.xml
index 68ae65c..28f4473 100644
--- a/testapps/res/layout/self_managed_sample_main.xml
+++ b/testapps/res/layout/self_managed_sample_main.xml
@@ -106,6 +106,11 @@
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:text="Accept Handover"/>
+        <Button
+            android:id="@+id/requestCallScreeningRole"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Req CallScreen Role"/>
     </LinearLayout>
 
     <ListView
diff --git a/testapps/res/layout/testdialer_main.xml b/testapps/res/layout/testdialer_main.xml
index e6c56b7..9da3789 100644
--- a/testapps/res/layout/testdialer_main.xml
+++ b/testapps/res/layout/testdialer_main.xml
@@ -30,6 +30,16 @@
         android:layout_height="wrap_content"
         android:text="@string/placeCallButton" />
     <Button
+        android:id="@+id/report_nuisance_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Report Nuisance" />
+    <Button
+        android:id="@+id/report_not_nuisance_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Report Not Nuisance" />
+    <Button
         android:id="@+id/set_default_button"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
diff --git a/testapps/src/com/android/server/telecom/testapps/CallListAdapter.java b/testapps/src/com/android/server/telecom/testapps/CallListAdapter.java
index 85785d5..e5b8780 100644
--- a/testapps/src/com/android/server/telecom/testapps/CallListAdapter.java
+++ b/testapps/src/com/android/server/telecom/testapps/CallListAdapter.java
@@ -18,13 +18,16 @@
 
 import android.content.Context;
 import android.net.Uri;
+import android.os.Bundle;
 import android.os.Handler;
 import android.telecom.Call;
+import android.telecom.CallIdentification;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.BaseAdapter;
+import android.widget.ImageView;
 import android.widget.TextView;
 
 public class CallListAdapter extends BaseAdapter {
@@ -90,9 +93,14 @@
             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);
+        TextView phoneNumber = convertView.findViewById(R.id.phoneNumber);
+        TextView duration = convertView.findViewById(R.id.duration);
+        TextView state = convertView.findViewById(R.id.callState);
+        TextView callIdName = convertView.findViewById(R.id.callIdName);
+        TextView callIdDescription = convertView.findViewById(R.id.callIdDescription);
+        TextView callIdDetails = convertView.findViewById(R.id.callIdDetails);
+        TextView callIdType = convertView.findViewById(R.id.callIdType);
+        ImageView callIdPhoto = convertView.findViewById(R.id.callIdPhoto);
 
         Call call = mCallList.getCall(position);
         Uri handle = call.getDetails().getHandle();
@@ -103,12 +111,36 @@
 
         state.setText(getStateString(call));
 
+        CallIdentification callIdentification = call.getDetails().getCallIdentification();
+        if (callIdentification != null) {
+            callIdName.setText(callIdentification.getName());
+            callIdDescription.setText(callIdentification.getDescription());
+            callIdDetails.setText(callIdentification.getDetails());
+            callIdType.setText("Call Type: " + callIdentification.getNuisanceConfidence());
+            callIdPhoto.setImageIcon(callIdentification.getPhoto());
+        }
+
         Log.i(TAG, "Call found: " + ((handle == null) ? "null" : handle.getSchemeSpecificPart())
                 + ", " + durationMs);
+        Log.i(TAG, "Call extras: " + extrasToString(call.getDetails().getExtras()));
+        Log.i(TAG, "Call intent extras: " + extrasToString(call.getDetails().getIntentExtras()));
+
 
         return convertView;
     }
 
+    private String extrasToString(Bundle bundle) {
+        StringBuilder sb = new StringBuilder("[");
+        for (String key : bundle.keySet()) {
+            sb.append(key);
+            sb.append(": ");
+            sb.append(bundle.get(key));
+            sb.append("\n");
+        }
+        sb.append("]");
+        return sb.toString();
+    }
+
     private static String getStateString(Call call) {
         switch (call.getState()) {
             case Call.STATE_ACTIVE:
diff --git a/testapps/src/com/android/server/telecom/testapps/CallScreeningActivity.java b/testapps/src/com/android/server/telecom/testapps/CallScreeningActivity.java
new file mode 100644
index 0000000..05ba500
--- /dev/null
+++ b/testapps/src/com/android/server/telecom/testapps/CallScreeningActivity.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2018 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.testapps;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.telecom.CallScreeningService;
+import android.view.WindowManager;
+
+public class CallScreeningActivity extends Activity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        AlertDialog alertDialog = new AlertDialog.Builder(this)
+                .setTitle("Test Call Screening")
+                .setMessage("Allow the call?")
+                .setNegativeButton("Block", new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int which) {
+                        if (TestCallScreeningService.getInstance() != null) {
+                            TestCallScreeningService.getInstance().blockCall();
+                        }
+                        finish();
+                    }
+                })
+                .setPositiveButton("Allow", new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int which) {
+                        if (TestCallScreeningService.getInstance() != null) {
+                            TestCallScreeningService.getInstance().allowCall();
+                        }
+                        finish();
+                    }
+                }).create();
+        alertDialog.show();
+    }
+}
diff --git a/testapps/src/com/android/server/telecom/testapps/CallServiceNotifier.java b/testapps/src/com/android/server/telecom/testapps/CallServiceNotifier.java
index 758ae4f..1c84d29 100644
--- a/testapps/src/com/android/server/telecom/testapps/CallServiceNotifier.java
+++ b/testapps/src/com/android/server/telecom/testapps/CallServiceNotifier.java
@@ -19,6 +19,7 @@
 import com.android.server.telecom.testapps.R;
 
 import android.app.Notification;
+import android.app.NotificationChannel;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.content.ComponentName;
@@ -26,6 +27,7 @@
 import android.content.Intent;
 import android.graphics.Color;
 import android.graphics.drawable.Icon;
+import android.media.AudioAttributes;
 import android.net.Uri;
 import android.os.Bundle;
 import android.telecom.PhoneAccount;
@@ -45,6 +47,7 @@
  */
 public class CallServiceNotifier {
     private static final CallServiceNotifier INSTANCE = new CallServiceNotifier();
+    private static final String CHANNEL_ID = "channel1";
 
     public static final String CALL_PROVIDER_ID = "testapps_TestConnectionService_CALL_PROVIDER_ID";
     public static final String SIM_SUBSCRIPTION_ID =
@@ -86,6 +89,9 @@
      */
     public void updateNotification(Context context) {
         log("adding the notification ------------");
+        NotificationChannel channel = new NotificationChannel(CHANNEL_ID, "Test Channel",
+                NotificationManager.IMPORTANCE_DEFAULT);
+        getNotificationManager(context).createNotificationChannel(channel);
         getNotificationManager(context).notify(CALL_NOTIFICATION_ID, getMainNotification(context));
         getNotificationManager(context).notify(
                 PHONE_ACCOUNT_NOTIFICATION_ID, getPhoneAccountNotification(context));
@@ -219,7 +225,7 @@
      * Creates a notification object for using the telecom APIs.
      */
     private Notification getPhoneAccountNotification(Context context) {
-        final Notification.Builder builder = new Notification.Builder(context);
+        final Notification.Builder builder = new Notification.Builder(context, CHANNEL_ID);
         // Both notifications have buttons and only the first one with buttons will show its
         // buttons.  Since the phone accounts notification is always first, setting false ensures
         // it can be dismissed to use the other notification.
@@ -244,7 +250,7 @@
      * Creates a notification object out of the current calls state.
      */
     private Notification getMainNotification(Context context) {
-        final Notification.Builder builder = new Notification.Builder(context);
+        final Notification.Builder builder = new Notification.Builder(context, CHANNEL_ID);
         builder.setOngoing(true);
         builder.setPriority(Notification.PRIORITY_HIGH);
         builder.setSmallIcon(android.R.drawable.stat_sys_phone_call);
diff --git a/testapps/src/com/android/server/telecom/testapps/NuisanceReportReceiver.java b/testapps/src/com/android/server/telecom/testapps/NuisanceReportReceiver.java
new file mode 100644
index 0000000..76fff66
--- /dev/null
+++ b/testapps/src/com/android/server/telecom/testapps/NuisanceReportReceiver.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2019 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.testapps;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.telecom.CallScreeningService;
+import android.widget.Toast;
+
+/**
+ * Example receiver for nuisance call reports from Telecom.
+ */
+public class NuisanceReportReceiver extends BroadcastReceiver {
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        if (intent.getAction().equals(CallScreeningService.ACTION_NUISANCE_CALL_STATUS_CHANGED)) {
+            Uri handle = intent.getParcelableExtra(CallScreeningService.EXTRA_CALL_HANDLE);
+            boolean isNuisance = intent.getBooleanExtra(CallScreeningService.EXTRA_IS_NUISANCE,
+                    false);
+            int durationBucket = intent.getIntExtra(CallScreeningService.EXTRA_CALL_DURATION, 0);
+            int callType = intent.getIntExtra(CallScreeningService.EXTRA_CALL_TYPE, 0);
+            handleNuisanceReport(context, handle, isNuisance, durationBucket, callType);
+        }
+    }
+
+    private void handleNuisanceReport(Context context, Uri handle, boolean isNuisance,
+            int durationBucket, int callType) {
+
+        String message = "Nuisance report for: " + handle + " isNuisance=" + isNuisance
+                + " duration=" + durationBucket + " callType=" + callType;
+        Toast.makeText(context,
+                (CharSequence) message,
+                Toast.LENGTH_LONG)
+                .show();
+    }
+}
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallListAdapter.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallListAdapter.java
index 8eaa282..75ceb62 100644
--- a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallListAdapter.java
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallListAdapter.java
@@ -104,6 +104,16 @@
         }
     };
 
+    private View.OnClickListener mBluetoothListener = new View.OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            View parent = (View) v.getParent().getParent();
+            SelfManagedConnection connection = (SelfManagedConnection) parent.getTag();
+            connection.setAudioRoute(CallAudioState.ROUTE_BLUETOOTH);
+            notifyDataSetChanged();
+        }
+    };
+
     private View.OnClickListener mHoldableListener = new View.OnClickListener() {
         @Override
         public void onClick (View v) {
@@ -221,6 +231,8 @@
         speakerButton.setOnClickListener(mSpeakerListener);
         View earpieceButton = view.findViewById(R.id.earpieceButton);
         earpieceButton.setOnClickListener(mEarpieceListener);
+        View bluetoothButton = view.findViewById(R.id.bluetoothButton);
+        bluetoothButton.setOnClickListener(mBluetoothListener);
         View missedButton = view.findViewById(R.id.missedButton);
         missedButton.setOnClickListener(mMissedListener);
         missedButton.setVisibility(isRinging ? View.VISIBLE : View.GONE);
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java
index a7b1350..959b855 100644
--- a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java
@@ -17,6 +17,10 @@
 package com.android.server.telecom.testapps;
 
 import android.app.Activity;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.media.AudioAttributes;
+import android.media.RingtoneManager;
 import android.net.Uri;
 import android.os.Bundle;
 import android.telecom.ConnectionRequest;
@@ -100,6 +104,7 @@
                         | WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
 
         getWindow().addFlags(flags);
+        configureNotificationChannel();
         setContentView(R.layout.self_managed_sample_main);
         mCheckIfPermittedBeforeCalling = (CheckBox) findViewById(
                 R.id.checkIfPermittedBeforeCalling);
@@ -114,12 +119,12 @@
         mPlaceIncomingCallButton.setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View v) {
-                placeIncomingCall(false /* isHandoverFrom */);
+                placeIncomingCall();
             }
         });
         mHandoverFrom = (Button) findViewById(R.id.handoverFrom);
         mHandoverFrom.setOnClickListener((v -> {
-            placeIncomingCall(true /* isHandoverFrom */);
+            initiateHandover();
         }));
 
         mUseAcct1Button = findViewById(R.id.useAcct1Button);
@@ -171,7 +176,14 @@
         tm.placeCall(Uri.parse(mNumber.getText().toString()), extras);
     }
 
-    private void placeIncomingCall(boolean isHandoverFrom) {
+    private void initiateHandover() {
+        TelecomManager tm = TelecomManager.from(this);
+        PhoneAccountHandle phoneAccountHandle = getSelectedPhoneAccountHandle();
+        Uri address = Uri.parse(mNumber.getText().toString());
+        tm.acceptHandover(address, VideoProfile.STATE_BIDIRECTIONAL, phoneAccountHandle);
+    }
+
+    private void placeIncomingCall() {
         TelecomManager tm = TelecomManager.from(this);
         PhoneAccountHandle phoneAccountHandle = getSelectedPhoneAccountHandle();
 
@@ -191,9 +203,22 @@
             extras.putInt(TelecomManager.EXTRA_INCOMING_VIDEO_STATE,
                     VideoProfile.STATE_BIDIRECTIONAL);
         }
-        if (isHandoverFrom) {
-            extras.putBoolean(TelecomManager.EXTRA_IS_HANDOVER, true);
-        }
         tm.addNewIncomingCall(getSelectedPhoneAccountHandle(), extras);
     }
+
+    private void configureNotificationChannel() {
+        NotificationChannel channel = new NotificationChannel(
+                SelfManagedConnection.INCOMING_CALL_CHANNEL_ID, "Incoming Calls",
+                NotificationManager.IMPORTANCE_MAX);
+        channel.setShowBadge(false);
+        Uri ringtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE);
+        channel.setSound(ringtoneUri, new AudioAttributes.Builder()
+                .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
+                .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+                .build());
+        channel.enableLights(true);
+
+        NotificationManager mgr = getSystemService(NotificationManager.class);
+        mgr.createNotificationChannel(channel);
+    }
 }
\ No newline at end of file
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedConnection.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedConnection.java
index 8d0af04..ebd7423 100644
--- a/testapps/src/com/android/server/telecom/testapps/SelfManagedConnection.java
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedConnection.java
@@ -22,7 +22,10 @@
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.drawable.Icon;
+import android.media.AudioAttributes;
 import android.media.MediaPlayer;
+import android.media.RingtoneManager;
+import android.net.Uri;
 import android.os.Bundle;
 import android.telecom.Call;
 import android.telecom.CallAudioState;
@@ -43,7 +46,7 @@
         public void onConnectionStateChanged(SelfManagedConnection connection) {}
         public void onConnectionRemoved(SelfManagedConnection connection) {}
     }
-
+    public static final String INCOMING_CALL_CHANNEL_ID = "INCOMING_CALL_CHANNEL_ID";
     public static final String EXTRA_PHONE_ACCOUNT_HANDLE =
             "com.android.server.telecom.testapps.extra.PHONE_ACCOUNT_HANDLE";
     public static final String CALL_NOTIFICATION = "com.android.server.telecom.testapps.CALL";
@@ -58,6 +61,7 @@
     private boolean mIsIncomingCallUiShowing;
     private Listener mListener;
     private boolean mIsHandover;
+    private Notification.Builder mNotificationBuilder;
 
     SelfManagedConnection(SelfManagedCallList callList, Context context, boolean isIncoming) {
         mCallList = callList;
@@ -93,7 +97,8 @@
         PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 1, intent, 0);
 
         // Build the notification as an ongoing high priority item.
-        final Notification.Builder builder = new Notification.Builder(mContext);
+        final Notification.Builder builder = new Notification.Builder(mContext,
+                INCOMING_CALL_CHANNEL_ID);
         builder.setOngoing(true);
         builder.setPriority(Notification.PRIORITY_HIGH);
 
@@ -131,9 +136,12 @@
                                 PendingIntent.FLAG_UPDATE_CURRENT))
                         .build());
 
+        Notification notification = builder.build();
+        notification.flags |= Notification.FLAG_INSISTENT;
         NotificationManager notificationManager = mContext.getSystemService(
                 NotificationManager.class);
-        notificationManager.notify(CALL_NOTIFICATION, mCallId, builder.build());
+        mNotificationBuilder = builder;
+        notificationManager.notify(CALL_NOTIFICATION, mCallId, notification);
     }
 
     @Override
@@ -172,6 +180,15 @@
         setConnectionDisconnected(DisconnectCause.LOCAL);
     }
 
+    @Override
+    public void onSilence() {
+        // Re-post our notification without a ringtone.
+        mNotificationBuilder.setOnlyAlertOnce(true);
+        NotificationManager notificationManager = mContext.getSystemService(
+                NotificationManager.class);
+        notificationManager.notify(CALL_NOTIFICATION, mCallId, mNotificationBuilder.build());
+    }
+
     public void setConnectionActive() {
         mMediaPlayer.start();
         setActive();
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedConnectionService.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedConnectionService.java
index d5d79af..f2b6496 100644
--- a/testapps/src/com/android/server/telecom/testapps/SelfManagedConnectionService.java
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedConnectionService.java
@@ -24,9 +24,7 @@
 import android.telecom.Log;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
-import android.telecom.VideoProfile;
 
-import java.util.Objects;
 import java.util.Random;
 
 /**
@@ -45,13 +43,25 @@
             PhoneAccountHandle connectionManagerAccount,
             final ConnectionRequest request) {
 
-        return createSelfManagedConnection(request, false);
+        return createSelfManagedConnection(request, false, false /* isHandover */);
     }
 
     @Override
     public Connection onCreateIncomingConnection(PhoneAccountHandle connectionManagerPhoneAccount,
             ConnectionRequest request) {
-        return createSelfManagedConnection(request, true);
+        return createSelfManagedConnection(request, true, false /* isHandover */);
+    }
+
+    @Override
+    public Connection onCreateOutgoingHandoverConnection(PhoneAccountHandle fromPhoneAccountHandle,
+            ConnectionRequest request) {
+        return createSelfManagedConnection(request, false, true /* isHandover */);
+    }
+
+    @Override
+    public Connection onCreateIncomingHandoverConnection(PhoneAccountHandle fromPhoneAccountHandle,
+            ConnectionRequest request) {
+        return createSelfManagedConnection(request, true, true /* isHandover */);
     }
 
     @Override
@@ -77,13 +87,15 @@
         mCallList.notifyConnectionServiceFocusGained();
     }
 
-    private Connection createSelfManagedConnection(ConnectionRequest request, boolean isIncoming) {
+    private Connection createSelfManagedConnection(ConnectionRequest request, boolean isIncoming,
+            boolean isHandover) {
         SelfManagedConnection connection = new SelfManagedConnection(mCallList,
                 getApplicationContext(), isIncoming);
         connection.setListener(mCallList.getConnectionListener());
         connection.setConnectionProperties(Connection.PROPERTY_SELF_MANAGED);
         connection.setAddress(request.getAddress(), TelecomManager.PRESENTATION_ALLOWED);
-        connection.setAudioModeIsVoip(true);
+        // Purposely do not set the audio mode to voip since we expect this to be the default:
+        // connection.setAudioModeIsVoip(true);
         connection.setVideoState(request.getVideoState());
         Random random = new Random();
         connection.setCallerDisplayName(TEST_NAMES[random.nextInt(TEST_NAMES.length)],
@@ -97,11 +109,10 @@
         if (requestExtras != null) {
             boolean isHoldable = requestExtras.getBoolean(EXTRA_HOLDABLE, false);
             Log.i(this, "createConnection: isHandover=%b, handoverFrom=%s, holdable=%b",
-                    requestExtras.getBoolean(TelecomManager.EXTRA_IS_HANDOVER),
+                    isHandover,
                     requestExtras.getString(TelecomManager.EXTRA_HANDOVER_FROM_PHONE_ACCOUNT),
                     isHoldable);
-            connection.setIsHandover(requestExtras.getBoolean(TelecomManager.EXTRA_IS_HANDOVER,
-                    false));
+            connection.setIsHandover(isHandover);
             if (isHoldable) {
                 connection.setConnectionCapabilities(connection.getConnectionCapabilities() |
                         Connection.CAPABILITY_HOLD | Connection.CAPABILITY_SUPPORT_HOLD);
diff --git a/testapps/src/com/android/server/telecom/testapps/TestCallScreeningService.java b/testapps/src/com/android/server/telecom/testapps/TestCallScreeningService.java
new file mode 100644
index 0000000..0c0b303
--- /dev/null
+++ b/testapps/src/com/android/server/telecom/testapps/TestCallScreeningService.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2018 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.testapps;
+
+import android.content.Intent;
+import android.graphics.drawable.Icon;
+import android.telecom.Call;
+import android.telecom.CallIdentification;
+import android.telecom.CallScreeningService;
+import android.telecom.Log;
+
+public class TestCallScreeningService extends CallScreeningService {
+    private Call.Details mDetails;
+    private static TestCallScreeningService sTestCallScreeningService;
+
+    public static TestCallScreeningService getInstance() {
+        return sTestCallScreeningService;
+    }
+
+    /**
+     * Handles request from the system to screen an incoming call.
+     * @param callDetails Information about a new incoming call, see {@link Call.Details}.
+     */
+    @Override
+    public void onScreenCall(Call.Details callDetails) {
+        Log.i(this, "onScreenCall: received call %s", callDetails);
+        sTestCallScreeningService = this;
+
+        mDetails = callDetails;
+        if (callDetails.getCallDirection() == Call.Details.DIRECTION_INCOMING) {
+            Intent errorIntent = new Intent(this, CallScreeningActivity.class);
+            errorIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            startActivity(errorIntent);
+        } else {
+            CallIdentification callIdentification = new CallIdentification.Builder()
+                    .setNuisanceConfidence(CallIdentification.CONFIDENCE_NOT_NUISANCE)
+                    .setName("Janes's Laundry")
+                    .setDescription("1255 DirtySocks Lane")
+                    .setDetails("Open 16 hrs")
+                    .setPhoto(Icon.createWithResource(this, R.drawable.ic_android_black_24dp))
+                    .build();
+            provideCallIdentification(mDetails, callIdentification);
+        }
+    }
+
+    public void blockCall() {
+        CallScreeningService.CallResponse
+                response = new CallScreeningService.CallResponse.Builder()
+                .setDisallowCall(true)
+                .setRejectCall(true)
+                .setSkipCallLog(false)
+                .setSkipNotification(true)
+                .build();
+        respondToCall(mDetails, response);
+    }
+
+    public void allowCall() {
+        // Provide call identification
+        CallIdentification callIdentification = new CallIdentification.Builder()
+                .setNuisanceConfidence(CallIdentification.CONFIDENCE_NOT_NUISANCE)
+                .setName("Joe's Laundry")
+                .setDescription("1234 DirtySocks Lane")
+                .setDetails("Open 24 hrs")
+                .setPhoto(Icon.createWithResource(this, R.drawable.ic_android_black_24dp))
+                .build();
+        provideCallIdentification(mDetails, callIdentification);
+        CallScreeningService.CallResponse
+                response = new CallScreeningService.CallResponse.Builder()
+                .setDisallowCall(false)
+                .setRejectCall(false)
+                .setSkipCallLog(false)
+                .setSkipNotification(false)
+                .build();
+        respondToCall(mDetails, response);
+    }
+}
diff --git a/testapps/src/com/android/server/telecom/testapps/TestConnectionService.java b/testapps/src/com/android/server/telecom/testapps/TestConnectionService.java
index 52dcadb..1a2aac6 100644
--- a/testapps/src/com/android/server/telecom/testapps/TestConnectionService.java
+++ b/testapps/src/com/android/server/telecom/testapps/TestConnectionService.java
@@ -16,12 +16,18 @@
 
 package com.android.server.telecom.testapps;
 
+import static android.media.AudioAttributes.CONTENT_TYPE_SPEECH;
+import static android.media.AudioAttributes.USAGE_VOICE_COMMUNICATION;
+
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.media.AudioAttributes;
+import android.media.AudioManager;
 import android.media.MediaPlayer;
+import android.media.ToneGenerator;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
@@ -57,6 +63,11 @@
 
     public static final String EXTRA_HANDLE = "extra_handle";
 
+    /**
+     * If an outgoing call ends with 2879 (BUSY), the test CS will indicate the call is busy.
+     */
+    public static final String BUSY_SUFFIX = "2879";
+
     private static final String LOG_TAG = TestConnectionService.class.getSimpleName();
 
     private static TestConnectionService INSTANCE;
@@ -204,8 +215,15 @@
         void startOutgoing() {
             setDialing();
             mHandler.postDelayed(() -> {
-                setActive();
-                activateCall(TestConnection.this);
+                if (getAddress().getSchemeSpecificPart().endsWith(BUSY_SUFFIX)) {
+                    setDisconnected(new DisconnectCause(DisconnectCause.REMOTE, "Line busy",
+                            "Line busy", "Line busy", ToneGenerator.TONE_SUP_BUSY));
+                    destroyCall(this);
+                    destroy();
+                } else {
+                    setActive();
+                    activateCall(TestConnection.this);
+                }
             }, 4000);
             if (mOriginalRequest.isRequestingRtt()) {
                 Log.i(LOG_TAG, "Is RTT call. Starting chatbot service.");
@@ -582,8 +600,16 @@
     }
 
     private MediaPlayer createMediaPlayer() {
+        AudioAttributes attributes = new AudioAttributes.Builder()
+                .setUsage(USAGE_VOICE_COMMUNICATION)
+                .setContentType(CONTENT_TYPE_SPEECH)
+                .build();
+
+        final int audioSessionId = ((AudioManager) getSystemService(
+                Context.AUDIO_SERVICE)).generateAudioSessionId();
         // Prepare the media player to play a tone when there is a call.
-        MediaPlayer mediaPlayer = MediaPlayer.create(getApplicationContext(), R.raw.beep_boop);
+        MediaPlayer mediaPlayer = MediaPlayer.create(getApplicationContext(), R.raw.beep_boop, attributes,
+                audioSessionId);
         mediaPlayer.setLooping(true);
         return mediaPlayer;
     }
diff --git a/testapps/src/com/android/server/telecom/testapps/TestDialerActivity.java b/testapps/src/com/android/server/telecom/testapps/TestDialerActivity.java
index c7eccf7..66a6944 100644
--- a/testapps/src/com/android/server/telecom/testapps/TestDialerActivity.java
+++ b/testapps/src/com/android/server/telecom/testapps/TestDialerActivity.java
@@ -54,6 +54,20 @@
             }
         });
 
+        findViewById(R.id.report_nuisance_button).setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                reportNuisance(true);
+            }
+        });
+
+        findViewById(R.id.report_not_nuisance_button).setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                reportNuisance(false);
+            }
+        });
+
         mNumberView = (EditText) findViewById(R.id.number);
         mRttCheckbox = (CheckBox) findViewById(R.id.call_with_rtt_checkbox);
         updateMutableUi();
@@ -140,4 +154,11 @@
         Log.i("Santos xtr", intentExtras.toString());
         return intentExtras;
     }
+
+    private void reportNuisance(boolean isNuisance) {
+        final TelecomManager telecomManager =
+                (TelecomManager) getSystemService(Context.TELECOM_SERVICE);
+        telecomManager.reportNuisanceCallStatus(Uri.fromParts(PhoneAccount.SCHEME_TEL,
+                mNumberView.getText().toString(), null), isNuisance);
+    }
 }
diff --git a/testapps/src/com/android/server/telecom/testapps/TestInCallUI.java b/testapps/src/com/android/server/telecom/testapps/TestInCallUI.java
index 9851253..2a5b33a 100644
--- a/testapps/src/com/android/server/telecom/testapps/TestInCallUI.java
+++ b/testapps/src/com/android/server/telecom/testapps/TestInCallUI.java
@@ -18,6 +18,7 @@
 
 import android.app.Activity;
 import android.bluetooth.BluetoothDevice;
+import android.content.ComponentName;
 import android.content.Intent;
 import android.os.Bundle;
 import android.telecom.Call;
@@ -209,11 +210,8 @@
 
         handoverButton.setOnClickListener((v) -> {
             Call call = mCallList.getCall(0);
-            Bundle extras = new Bundle();
-            extras.putParcelable(Call.EXTRA_HANDOVER_PHONE_ACCOUNT_HANDLE,
-                    getHandoverToPhoneAccountHandle());
-            extras.putInt(Call.EXTRA_HANDOVER_VIDEO_STATE, VideoProfile.STATE_BIDIRECTIONAL);
-            call.sendCallEvent(Call.EVENT_REQUEST_HANDOVER, extras);
+            call.handoverTo(getHandoverToPhoneAccountHandle(), VideoProfile.STATE_BIDIRECTIONAL,
+                    null);
         });
     }
 
@@ -263,17 +261,8 @@
     }
 
     private PhoneAccountHandle getHandoverToPhoneAccountHandle() {
-        TelecomManager tm = TelecomManager.from(this);
-
-        List<PhoneAccountHandle> handles = tm.getAllPhoneAccountHandles();
-        Optional<PhoneAccountHandle> found = handles.stream().filter(h -> {
-            PhoneAccount account = tm.getPhoneAccount(h);
-            Bundle extras = account.getExtras();
-            return extras != null && extras.getBoolean(PhoneAccount.EXTRA_SUPPORTS_HANDOVER_TO);
-        }).findFirst();
-        PhoneAccountHandle foundHandle = found.orElse(null);
-        Log.i(TestInCallUI.class.getSimpleName(), "getHandoverToPhoneAccountHandle() = " +
-            foundHandle);
-        return foundHandle;
+        return new PhoneAccountHandle(new ComponentName(
+                SelfManagedCallList.class.getPackage().getName(),
+                SelfManagedConnectionService.class.getName()), "1");
     }
 }
diff --git a/tests/Android.mk b/tests/Android.mk
index 5f083de..5abf999 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -20,15 +20,15 @@
 LOCAL_STATIC_JAVA_LIBRARIES := \
     android-ex-camera2 \
     guava \
-    mockito-target \
+    mockito-target-inline \
     android-support-test \
     platform-test-annotations
 
 LOCAL_STATIC_ANDROID_LIBRARIES := \
-    android-support-core-ui \
-    android-support-core-utils \
-    android-support-compat \
-    android-support-fragment
+    androidx.legacy_legacy-support-core-ui \
+    androidx.legacy_legacy-support-core-utils \
+    androidx.core_core \
+    androidx.fragment_fragment
 
 LOCAL_SRC_FILES := \
     $(call all-java-files-under, src) \
@@ -51,12 +51,13 @@
 
 LOCAL_USE_AAPT2 := true
 
+LOCAL_JNI_SHARED_LIBRARIES := \
+    libdexmakerjvmtiagent \
+
 LOCAL_AAPT_FLAGS := \
     --auto-add-overlay \
     --extra-packages com.android.server.telecom
 
-LOCAL_JACK_FLAGS := --multi-dex native
-
 LOCAL_PROGUARD_ENABLED := disabled
 
 LOCAL_PACKAGE_NAME := TelecomUnitTests
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index 0e79fce..8132157 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -34,6 +34,7 @@
 
     <!-- Used to access TelephonyManager APIs -->
     <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
 
     <application android:label="@string/app_name"
                  android:debuggable="true">
diff --git a/tests/src/com/android/server/telecom/tests/AsyncBlockCheckFilterTest.java b/tests/src/com/android/server/telecom/tests/AsyncBlockCheckFilterTest.java
index 9b2b3fb..91ddf5a 100644
--- a/tests/src/com/android/server/telecom/tests/AsyncBlockCheckFilterTest.java
+++ b/tests/src/com/android/server/telecom/tests/AsyncBlockCheckFilterTest.java
@@ -16,10 +16,14 @@
 
 package com.android.server.telecom.tests;
 
+import static android.provider.BlockedNumberContract.STATUS_BLOCKED_IN_LIST;
+import static android.provider.BlockedNumberContract.STATUS_NOT_BLOCKED;
+
 import android.content.Context;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.PersistableBundle;
+import android.provider.CallLog;
 import android.telecom.TelecomManager;
 import android.telephony.CarrierConfigManager;
 import android.test.suitebuilder.annotation.SmallTest;
@@ -49,7 +53,6 @@
 
 @RunWith(JUnit4.class)
 public class AsyncBlockCheckFilterTest extends TelecomTestCase {
-    @Mock private Context mContext;
     @Mock private BlockCheckerAdapter mBlockCheckerAdapter;
     @Mock private Call mCall;
     @Mock private CallFilterResultCallback mCallback;
@@ -60,8 +63,12 @@
     private static final CallFilteringResult BLOCK_RESULT = new CallFilteringResult(
             false, // shouldAllowCall
             true, //shouldReject
-            false, //shouldAddToCallLog
-            false // shouldShowNotification
+            true, //shouldAddToCallLog
+            false, // shouldShowNotification
+            CallLog.Calls.BLOCK_REASON_BLOCKED_NUMBER, //blockReason
+            null, // callScreeningAppName
+            null //callScreeningComponentName
+
     );
 
     private static final CallFilteringResult PASS_RESULT = new CallFilteringResult(
@@ -72,7 +79,7 @@
     );
 
     private static final Uri TEST_HANDLE = Uri.parse("tel:1235551234");
-    private static final int TEST_TIMEOUT = 100;
+    private static final int TEST_TIMEOUT = 1000;
 
     @Override
     @Before
@@ -80,7 +87,7 @@
         super.setUp();
         when(mCall.getHandle()).thenReturn(TEST_HANDLE);
         mFilter = new AsyncBlockCheckFilter(mContext, mBlockCheckerAdapter,
-                mCallerInfoLookupHelper);
+                mCallerInfoLookupHelper, null);
     }
 
     @SmallTest
@@ -89,9 +96,9 @@
         final CountDownLatch latch = new CountDownLatch(1);
         doAnswer(invocation -> {
             latch.countDown();
-            return true;
+            return STATUS_BLOCKED_IN_LIST;
         }).when(mBlockCheckerAdapter)
-                .isBlocked(any(Context.class), eq(TEST_HANDLE.getSchemeSpecificPart()),
+                .getBlockStatus(any(Context.class), eq(TEST_HANDLE.getSchemeSpecificPart()),
                         any(Bundle.class));
 
         setEnhancedBlockingEnabled(false);
@@ -108,9 +115,9 @@
         final CountDownLatch latch = new CountDownLatch(1);
         doAnswer(invocation -> {
             latch.countDown();
-            return true;
+            return STATUS_BLOCKED_IN_LIST;
         }).when(mBlockCheckerAdapter)
-                .isBlocked(any(Context.class), eq(TEST_HANDLE.getSchemeSpecificPart()),
+                .getBlockStatus(any(Context.class), eq(TEST_HANDLE.getSchemeSpecificPart()),
                         any(Bundle.class));
 
         setEnhancedBlockingEnabled(true);
@@ -128,9 +135,9 @@
         final CountDownLatch latch = new CountDownLatch(1);
         doAnswer(invocation -> {
             latch.countDown();
-            return false;
+            return STATUS_NOT_BLOCKED;
         }).when(mBlockCheckerAdapter)
-                .isBlocked(any(Context.class), eq(TEST_HANDLE.getSchemeSpecificPart()),
+                .getBlockStatus(any(Context.class), eq(TEST_HANDLE.getSchemeSpecificPart()),
                         any(Bundle.class));
 
         setEnhancedBlockingEnabled(false);
@@ -147,9 +154,9 @@
         final CountDownLatch latch = new CountDownLatch(1);
         doAnswer(invocation -> {
             latch.countDown();
-            return false;
+            return STATUS_NOT_BLOCKED;
         }).when(mBlockCheckerAdapter)
-                .isBlocked(any(Context.class), eq(TEST_HANDLE.getSchemeSpecificPart()),
+                .getBlockStatus(any(Context.class), eq(TEST_HANDLE.getSchemeSpecificPart()),
                         any(Bundle.class));
 
         setEnhancedBlockingEnabled(true);
diff --git a/tests/src/com/android/server/telecom/tests/BasicCallTests.java b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
index e304d34..36eea89 100644
--- a/tests/src/com/android/server/telecom/tests/BasicCallTests.java
+++ b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
@@ -20,6 +20,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyString;
@@ -331,12 +332,17 @@
         int startingNumCalls = mInCallServiceFixtureX.mCallById.size();
         String callId = startOutgoingPhoneCallWithNoPhoneAccount("650-555-1212",
                 mConnectionServiceFixtureA);
+        mTelecomSystem.getCallsManager().getLatestPreAccountSelectionFuture().join();
+        waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
         assertEquals(Call.STATE_SELECT_PHONE_ACCOUNT,
                 mInCallServiceFixtureX.getCall(callId).getState());
         assertEquals(Call.STATE_SELECT_PHONE_ACCOUNT,
                 mInCallServiceFixtureY.getCall(callId).getState());
         mInCallServiceFixtureX.mInCallAdapter.phoneAccountSelected(callId,
                 mPhoneAccountA0.getAccountHandle(), false);
+        waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
+        waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
+        verifyAndProcessOutgoingCallBroadcast(mPhoneAccountA0.getAccountHandle());
 
         IdPair ids = outgoingCallPhoneAccountSelected(mPhoneAccountA0.getAccountHandle(),
                 startingNumConnections, startingNumCalls, mConnectionServiceFixtureA);
@@ -548,8 +554,10 @@
         // TODO: We have to use the same PhoneAccount for both; see http://b/18461539
         IdPair outgoing = startAndMakeActiveOutgoingCall("650-555-1212",
                 mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
+        waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
         IdPair incoming = startAndMakeActiveIncomingCall("650-555-2323",
                 mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
+        waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
         verify(mConnectionServiceFixtureA.getTestDouble())
                 .hold(eq(outgoing.mConnectionId), any());
         mConnectionServiceFixtureA.mConnectionById.get(outgoing.mConnectionId).state =
@@ -585,10 +593,15 @@
                 .setMicrophoneMute(eq(false), any(String.class), any(Integer.class));
 
         mInCallServiceFixtureX.mInCallAdapter.setAudioRoute(CallAudioState.ROUTE_SPEAKER, null);
+        waitForHandlerAction(mTelecomSystem.getCallsManager().getCallAudioManager()
+                .getCallAudioRouteStateMachine().getHandler(), TEST_TIMEOUT);
         verify(audioManager, timeout(TEST_TIMEOUT))
                 .setSpeakerphoneOn(true);
         mInCallServiceFixtureX.mInCallAdapter.setAudioRoute(CallAudioState.ROUTE_EARPIECE, null);
-        verify(audioManager, timeout(TEST_TIMEOUT))
+        waitForHandlerAction(mTelecomSystem.getCallsManager().getCallAudioManager()
+                .getCallAudioRouteStateMachine().getHandler(), TEST_TIMEOUT);
+        // setSpeakerPhoneOn(false) gets called once during the call initiation phase
+        verify(audioManager, timeout(TEST_TIMEOUT).atLeast(2))
                 .setSpeakerphoneOn(false);
 
         mConnectionServiceFixtureA.
@@ -784,7 +797,7 @@
                 anyString(),
                 eq(BlockedNumberContract.SystemContract.METHOD_SHOULD_SYSTEM_BLOCK_NUMBER),
                 eq(phoneNumber),
-                isNull(Bundle.class))).thenAnswer(answer);
+                nullable(Bundle.class))).thenAnswer(answer);
     }
 
     private void verifyNoBlockChecks() {
@@ -912,12 +925,12 @@
                 Process.myUserHandle(), VideoProfile.STATE_BIDIRECTIONAL);
         com.android.server.telecom.Call call = mTelecomSystem.getCallsManager().getCalls()
                 .iterator().next();
-        assert(call.isVideoCallingSupported());
+        assert(call.isVideoCallingSupportedByPhoneAccount());
         assertEquals(VideoProfile.STATE_BIDIRECTIONAL, call.getVideoState());
 
         // Change the phone account to one which supports video calling.
         call.setTargetPhoneAccount(mPhoneAccountA1.getAccountHandle());
-        assert(call.isVideoCallingSupported());
+        assert(call.isVideoCallingSupportedByPhoneAccount());
         assertEquals(VideoProfile.STATE_BIDIRECTIONAL, call.getVideoState());
     }
 
@@ -935,12 +948,12 @@
                 Process.myUserHandle(), VideoProfile.STATE_BIDIRECTIONAL);
         com.android.server.telecom.Call call = mTelecomSystem.getCallsManager().getCalls()
                 .iterator().next();
-        assert(call.isVideoCallingSupported());
+        assert(call.isVideoCallingSupportedByPhoneAccount());
         assertEquals(VideoProfile.STATE_BIDIRECTIONAL, call.getVideoState());
 
         // Change the phone account to one which does not support video calling.
         call.setTargetPhoneAccount(mPhoneAccountA2.getAccountHandle());
-        assert(!call.isVideoCallingSupported());
+        assert(!call.isVideoCallingSupportedByPhoneAccount());
         assertEquals(VideoProfile.STATE_AUDIO_ONLY, call.getVideoState());
     }
 
@@ -1045,8 +1058,13 @@
                 mConnectionServiceFixtureA);
 
         // Should have reverted back to earpiece.
-        assertEquals(CallAudioState.ROUTE_EARPIECE,
-                mInCallServiceFixtureX.mCallAudioState.getRoute());
+        assertTrueWithTimeout(new Predicate<Void>() {
+            @Override
+            public boolean apply(Void aVoid) {
+                return mInCallServiceFixtureX.mCallAudioState.getRoute()
+                        == CallAudioState.ROUTE_EARPIECE;
+            }
+        });
     }
 
     /**
@@ -1098,6 +1116,7 @@
      */
     @LargeTest
     @Test
+    @FlakyTest
     public void testUnmuteDuringEmergencyCall() throws Exception {
         // Make an outgoing call and turn ON mute.
         IdPair outgoingCall = startAndMakeActiveOutgoingCall("650-555-1212",
diff --git a/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java b/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java
index 4a48f1b..9fd97f8 100644
--- a/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java
@@ -18,16 +18,15 @@
 
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothHearingAid;
 import android.bluetooth.BluetoothProfile;
 import android.content.BroadcastReceiver;
 import android.content.Intent;
-import android.content.IntentFilter;
 import android.os.Parcel;
 import android.test.suitebuilder.annotation.SmallTest;
 
 import com.android.server.telecom.BluetoothAdapterProxy;
 import com.android.server.telecom.BluetoothHeadsetProxy;
-import com.android.server.telecom.TelecomSystem;
 import com.android.server.telecom.bluetooth.BluetoothDeviceManager;
 import com.android.server.telecom.bluetooth.BluetoothRouteManager;
 import com.android.server.telecom.bluetooth.BluetoothStateReceiver;
@@ -41,15 +40,20 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.Arrays;
 
 @RunWith(JUnit4.class)
 public class BluetoothDeviceManagerTest extends TelecomTestCase {
     @Mock BluetoothRouteManager mRouteManager;
     @Mock BluetoothHeadsetProxy mHeadsetProxy;
     @Mock BluetoothAdapterProxy mAdapterProxy;
+    @Mock BluetoothHearingAid mBluetoothHearingAid;
 
     BluetoothDeviceManager mBluetoothDeviceManager;
     BluetoothProfile.ServiceListener serviceListenerUnderTest;
@@ -58,18 +62,24 @@
     private BluetoothDevice device1;
     private BluetoothDevice device2;
     private BluetoothDevice device3;
+    private BluetoothDevice device4;
 
     @Override
     @Before
     public void setUp() throws Exception {
         super.setUp();
         device1 = makeBluetoothDevice("00:00:00:00:00:01");
+        // hearing aid
         device2 = makeBluetoothDevice("00:00:00:00:00:02");
         device3 = makeBluetoothDevice("00:00:00:00:00:03");
+        // hearing aid
+        device4 = makeBluetoothDevice("00:00:00:00:00:04");
+
+        when(mBluetoothHearingAid.getHiSyncId(device2)).thenReturn(100L);
+        when(mBluetoothHearingAid.getHiSyncId(device4)).thenReturn(100L);
 
         mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
-        mBluetoothDeviceManager = new BluetoothDeviceManager(mContext, mAdapterProxy,
-                new TelecomSystem.SyncRoot() { });
+        mBluetoothDeviceManager = new BluetoothDeviceManager(mContext, mAdapterProxy);
         mBluetoothDeviceManager.setBluetoothRouteManager(mRouteManager);
 
         ArgumentCaptor<BluetoothProfile.ServiceListener> serviceCaptor =
@@ -82,80 +92,119 @@
                 null /* route mgr not needed here */);
 
         mBluetoothDeviceManager.setHeadsetServiceForTesting(mHeadsetProxy);
+        mBluetoothDeviceManager.setHearingAidServiceForTesting(mBluetoothHearingAid);
     }
 
     @SmallTest
     @Test
     public void testSingleDeviceConnectAndDisconnect() {
         receiverUnderTest.onReceive(mContext,
-                buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device1));
+                buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device1, false));
         assertEquals(1, mBluetoothDeviceManager.getNumConnectedDevices());
-        assertEquals(device1.getAddress(),
-                mBluetoothDeviceManager.getMostRecentlyConnectedDevice(null));
         receiverUnderTest.onReceive(mContext,
-                buildConnectionActionIntent(BluetoothHeadset.STATE_DISCONNECTED, device1));
+                buildConnectionActionIntent(BluetoothHeadset.STATE_DISCONNECTED, device1, false));
         assertEquals(0, mBluetoothDeviceManager.getNumConnectedDevices());
-        assertNull(mBluetoothDeviceManager.getMostRecentlyConnectedDevice(null));
     }
 
     @SmallTest
     @Test
+    public void testAddDeviceFailsWhenServicesAreNull() {
+        mBluetoothDeviceManager.setHeadsetServiceForTesting(null);
+        mBluetoothDeviceManager.setHearingAidServiceForTesting(null);
+
+        receiverUnderTest.onReceive(mContext,
+                buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device1, false));
+        receiverUnderTest.onReceive(mContext,
+                buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device2, true));
+
+        assertEquals(0, mBluetoothDeviceManager.getNumConnectedDevices());
+    }
+    
+    @SmallTest
+    @Test
     public void testMultiDeviceConnectAndDisconnect() {
         receiverUnderTest.onReceive(mContext,
-                buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device1));
+                buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device1, false));
         receiverUnderTest.onReceive(mContext,
-                buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device2));
+                buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device2, true));
         receiverUnderTest.onReceive(mContext,
-                buildConnectionActionIntent(BluetoothHeadset.STATE_DISCONNECTED, device1));
+                buildConnectionActionIntent(BluetoothHeadset.STATE_DISCONNECTED, device1, false));
         receiverUnderTest.onReceive(mContext,
-                buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device3));
-        receiverUnderTest.onReceive(mContext,
-                buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device2));
+                buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device3, false));
         assertEquals(2, mBluetoothDeviceManager.getNumConnectedDevices());
-        assertEquals(device3.getAddress(),
-                mBluetoothDeviceManager.getMostRecentlyConnectedDevice(null));
         receiverUnderTest.onReceive(mContext,
-                buildConnectionActionIntent(BluetoothHeadset.STATE_DISCONNECTED, device3));
+                buildConnectionActionIntent(BluetoothHeadset.STATE_DISCONNECTED, device3, false));
         assertEquals(1, mBluetoothDeviceManager.getNumConnectedDevices());
-        assertEquals(device2.getAddress(),
-                mBluetoothDeviceManager.getMostRecentlyConnectedDevice(null));
     }
 
     @SmallTest
     @Test
-    public void testExclusionaryGetRecentDevices() {
+    public void testHearingAidDedup() {
         receiverUnderTest.onReceive(mContext,
-                buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device1));
+                buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device1, false));
         receiverUnderTest.onReceive(mContext,
-                buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device2));
+                buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device2, true));
         receiverUnderTest.onReceive(mContext,
-                buildConnectionActionIntent(BluetoothHeadset.STATE_DISCONNECTED, device1));
-        receiverUnderTest.onReceive(mContext,
-                buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device3));
-        receiverUnderTest.onReceive(mContext,
-                buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device2));
-        assertEquals(2, mBluetoothDeviceManager.getNumConnectedDevices());
-        assertEquals(device2.getAddress(),
-                mBluetoothDeviceManager.getMostRecentlyConnectedDevice(device3.getAddress()));
+                buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device4, true));
+        assertEquals(3, mBluetoothDeviceManager.getNumConnectedDevices());
+        assertEquals(2, mBluetoothDeviceManager.getUniqueConnectedDevices().size());
     }
 
     @SmallTest
     @Test
     public void testHeadsetServiceDisconnect() {
         receiverUnderTest.onReceive(mContext,
-                buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device1));
+                buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device1, false));
         receiverUnderTest.onReceive(mContext,
-                buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device2));
-        serviceListenerUnderTest.onServiceDisconnected(0);
+                buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device3, false));
+        receiverUnderTest.onReceive(mContext,
+                buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device2, true));
+        serviceListenerUnderTest.onServiceDisconnected(BluetoothProfile.HEADSET);
 
         verify(mRouteManager).onDeviceLost(device1.getAddress());
-        verify(mRouteManager).onDeviceLost(device2.getAddress());
+        verify(mRouteManager).onDeviceLost(device3.getAddress());
+        verify(mRouteManager, never()).onDeviceLost(device2.getAddress());
         assertNull(mBluetoothDeviceManager.getHeadsetService());
-        assertEquals(0, mBluetoothDeviceManager.getNumConnectedDevices());
+        assertEquals(1, mBluetoothDeviceManager.getNumConnectedDevices());
     }
 
-    private Intent buildConnectionActionIntent(int state, BluetoothDevice device) {
-        Intent i = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
+    @SmallTest
+    @Test
+    public void testConnectDisconnectAudioHeadset() {
+        receiverUnderTest.onReceive(mContext,
+                buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device1, false));
+        when(mHeadsetProxy.setActiveDevice(nullable(BluetoothDevice.class))).thenReturn(true);
+        mBluetoothDeviceManager.connectAudio(device1.getAddress());
+        verify(mHeadsetProxy).setActiveDevice(device1);
+        verify(mHeadsetProxy).connectAudio();
+        verify(mBluetoothHearingAid, never()).setActiveDevice(nullable(BluetoothDevice.class));
+
+        mBluetoothDeviceManager.disconnectAudio();
+        verify(mHeadsetProxy).disconnectAudio();
+    }
+
+    @SmallTest
+    @Test
+    public void testConnectDisconnectAudioHearingAid() {
+        receiverUnderTest.onReceive(mContext,
+                buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device2, true));
+        mBluetoothDeviceManager.connectAudio(device2.getAddress());
+        verify(mBluetoothHearingAid).setActiveDevice(device2);
+        verify(mHeadsetProxy, never()).connectAudio();
+        verify(mHeadsetProxy, never()).setActiveDevice(nullable(BluetoothDevice.class));
+
+        when(mBluetoothHearingAid.getActiveDevices()).thenReturn(Arrays.asList(device2, null));
+
+        mBluetoothDeviceManager.disconnectAudio();
+        verify(mBluetoothHearingAid).setActiveDevice(null);
+        verify(mHeadsetProxy).disconnectAudio();
+    }
+
+    private Intent buildConnectionActionIntent(int state, BluetoothDevice device,
+            boolean isHearingAid) {
+        Intent i = new Intent(isHearingAid
+                ? BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED
+                : BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
         i.putExtra(BluetoothHeadset.EXTRA_STATE, state);
         i.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
         return i;
diff --git a/tests/src/com/android/server/telecom/tests/BluetoothPhoneServiceTest.java b/tests/src/com/android/server/telecom/tests/BluetoothPhoneServiceTest.java
index a5feab7..a960d91 100644
--- a/tests/src/com/android/server/telecom/tests/BluetoothPhoneServiceTest.java
+++ b/tests/src/com/android/server/telecom/tests/BluetoothPhoneServiceTest.java
@@ -309,17 +309,18 @@
 
         mBluetoothPhoneService.mBinder.queryPhoneState();
         verify(mMockBluetoothHeadset).phoneStateChanged(eq(1), eq(1), eq(CALL_STATE_IDLE),
-                eq(""), eq(128));
+                eq(""), eq(128), nullable(String.class));
         when(parentCall.wasConferencePreviouslyMerged()).thenReturn(true);
         mBluetoothPhoneService.mCallsManagerListener.onIsConferencedChanged(parentCall);
         verify(mMockBluetoothHeadset).phoneStateChanged(eq(1), eq(0), eq(CALL_STATE_IDLE),
-                eq(""), eq(128));
+                eq(""), eq(128), nullable(String.class));
         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), nullable(String.class), any(int.class));
+                any(int.class), any(int.class), nullable(String.class), any(int.class),
+                nullable(String.class));
     }
 
     @MediumTest
@@ -611,7 +612,7 @@
         mBluetoothPhoneService.mBinder.queryPhoneState();
 
         verify(mMockBluetoothHeadset).phoneStateChanged(eq(0), eq(0), eq(CALL_STATE_INCOMING),
-                eq("5550000"), eq(PhoneNumberUtils.TOA_Unknown));
+                eq("5550000"), eq(PhoneNumberUtils.TOA_Unknown), nullable(String.class));
     }
 
     @MediumTest
@@ -632,7 +633,7 @@
 
         mBluetoothPhoneService.mBinder.queryPhoneState();
         verify(mMockBluetoothHeadset).phoneStateChanged(eq(1), eq(0), eq(CALL_STATE_IDLE),
-                eq(""), eq(128));
+                eq(""), eq(128), nullable(String.class));
     }
 
     @MediumTest
@@ -681,7 +682,8 @@
                 CHLD_TYPE_RELEASEACTIVE_ACCEPTHELD);
 
         verify(mMockCallsManager).disconnectCall(eq(activeCall));
-        verify(mMockCallsManager).unholdCall(eq(heldCall));
+        // Call unhold will occur as part of CallsManager auto-unholding the background call on its
+        // own.
         assertEquals(didProcess, true);
     }
 
@@ -771,7 +773,7 @@
 
         verify(parentCall).swapConference();
         verify(mMockBluetoothHeadset).phoneStateChanged(eq(1), eq(1), eq(CALL_STATE_IDLE), eq(""),
-                eq(128));
+                eq(128), nullable(String.class));
         assertEquals(didProcess, true);
     }
 
@@ -785,7 +787,7 @@
         mBluetoothPhoneService.mCallsManagerListener.onCallAdded(ringingCall);
 
         verify(mMockBluetoothHeadset).phoneStateChanged(eq(0), eq(0), eq(CALL_STATE_INCOMING),
-                eq("555000"), eq(PhoneNumberUtils.TOA_Unknown));
+                eq("555000"), eq(PhoneNumberUtils.TOA_Unknown), nullable(String.class));
 
     }
 
@@ -807,7 +809,7 @@
         mBluetoothPhoneService.mCallsManagerListener.onCallAdded(parentCall);
 
         verify(mMockBluetoothHeadset).phoneStateChanged(eq(1), eq(1), eq(CALL_STATE_IDLE),
-                eq(""), eq(128));
+                eq(""), eq(128), nullable(String.class));
 
     }
 
@@ -820,7 +822,7 @@
         mBluetoothPhoneService.mCallsManagerListener.onCallRemoved(activeCall);
 
         verify(mMockBluetoothHeadset).phoneStateChanged(eq(0), eq(0), eq(CALL_STATE_IDLE),
-                eq(""), eq(128));
+                eq(""), eq(128), nullable(String.class));
     }
 
     @MediumTest
@@ -838,7 +840,7 @@
                 CallState.ACTIVE, CallState.ON_HOLD);
 
         verify(mMockBluetoothHeadset, never()).phoneStateChanged(anyInt(), anyInt(), anyInt(),
-                anyString(), anyInt());
+                anyString(), anyInt(), nullable(String.class));
     }
 
     @MediumTest
@@ -850,7 +852,7 @@
                 CallState.CONNECTING, CallState.DIALING);
 
         verify(mMockBluetoothHeadset, never()).phoneStateChanged(anyInt(), anyInt(), anyInt(),
-                anyString(), anyInt());
+                anyString(), anyInt(), nullable(String.class));
     }
 
     @MediumTest
@@ -861,8 +863,10 @@
         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);
+        verify(mMockBluetoothHeadset).phoneStateChanged(eq(0), eq(0), eq(CALL_STATE_DIALING),
+                eq(""), eq(128), nullable(String.class));
+        verify(mMockBluetoothHeadset).phoneStateChanged(eq(0), eq(0), eq(CALL_STATE_ALERTING),
+                eq(""), eq(128), nullable(String.class));
     }
 
     @MediumTest
@@ -873,16 +877,16 @@
         mBluetoothPhoneService.mCallsManagerListener.onCallStateChanged(disconnectedCall,
                 CallState.DISCONNECTING, CallState.DISCONNECTED);
         verify(mMockBluetoothHeadset).phoneStateChanged(eq(0), eq(0), eq(CALL_STATE_DISCONNECTED),
-                eq(""), eq(128));
+                eq(""), eq(128), nullable(String.class));
 
         doReturn(false).when(mMockCallsManager).hasOnlyDisconnectedCalls();
         mBluetoothPhoneService.mCallsManagerListener.onDisconnectedTonePlaying(true);
         verify(mMockBluetoothHeadset).phoneStateChanged(eq(0), eq(0), eq(CALL_STATE_DISCONNECTED),
-                eq(""), eq(128));
+                eq(""), eq(128), nullable(String.class));
 
         mBluetoothPhoneService.mCallsManagerListener.onDisconnectedTonePlaying(false);
         verify(mMockBluetoothHeadset).phoneStateChanged(eq(0), eq(0), eq(CALL_STATE_IDLE),
-                eq(""), eq(128));
+                eq(""), eq(128), nullable(String.class));
     }
 
     @MediumTest
@@ -893,7 +897,7 @@
         mBluetoothPhoneService.mCallsManagerListener.onCallAdded(ringingCall);
 
         verify(mMockBluetoothHeadset).phoneStateChanged(eq(0), eq(0), eq(CALL_STATE_INCOMING),
-                eq("555-0000"), eq(PhoneNumberUtils.TOA_Unknown));
+                eq("555-0000"), eq(PhoneNumberUtils.TOA_Unknown), nullable(String.class));
 
         //Switch to active
         doReturn(null).when(mMockCallsManager).getRingingCall();
@@ -903,7 +907,7 @@
                 CallState.RINGING, CallState.ACTIVE);
 
         verify(mMockBluetoothHeadset).phoneStateChanged(eq(1), eq(0), eq(CALL_STATE_IDLE),
-                eq(""), eq(128));
+                eq(""), eq(128), nullable(String.class));
     }
 
     @MediumTest
@@ -916,7 +920,7 @@
                 CallState.ACTIVE, CallState.ON_HOLD);
 
         verify(mMockBluetoothHeadset, never()).phoneStateChanged(eq(0), eq(2), eq(CALL_STATE_HELD),
-                eq("5550000"), eq(PhoneNumberUtils.TOA_Unknown));
+                eq("5550000"), eq(PhoneNumberUtils.TOA_Unknown), nullable(String.class));
     }
 
     @MediumTest
@@ -942,18 +946,18 @@
         // CDMA "conference"
         mBluetoothPhoneService.mCallsManagerListener.onIsConferencedChanged(activeCall);
         verify(mMockBluetoothHeadset, never()).phoneStateChanged(anyInt(), anyInt(), anyInt(),
-                anyString(), anyInt());
+                anyString(), anyInt(), nullable(String.class));
         mBluetoothPhoneService.mCallsManagerListener.onIsConferencedChanged(heldCall);
         verify(mMockBluetoothHeadset, never()).phoneStateChanged(anyInt(), anyInt(), anyInt(),
-                anyString(), anyInt());
+                anyString(), anyInt(), nullable(String.class));
         mBluetoothPhoneService.mCallsManagerListener.onIsConferencedChanged(parentCall);
         verify(mMockBluetoothHeadset, never()).phoneStateChanged(anyInt(), anyInt(), anyInt(),
-                anyString(), anyInt());
+                anyString(), anyInt(), nullable(String.class));
 
         calls.add(heldCall);
         mBluetoothPhoneService.mCallsManagerListener.onIsConferencedChanged(parentCall);
         verify(mMockBluetoothHeadset).phoneStateChanged(eq(1), eq(1), eq(CALL_STATE_IDLE),
-                eq(""), eq(128));
+                eq(""), eq(128), nullable(String.class));
     }
 
     @MediumTest
@@ -967,7 +971,7 @@
         mBluetoothPhoneService.mBluetoothAdapterReceiver.onReceive(mContext, intent);
 
         verify(mMockBluetoothHeadset).phoneStateChanged(eq(0), eq(0), eq(CALL_STATE_INCOMING),
-                eq("5550000"), eq(PhoneNumberUtils.TOA_Unknown));
+                eq("5550000"), eq(PhoneNumberUtils.TOA_Unknown), nullable(String.class));
     }
 
     private void addCallCapability(Call call, int capability) {
diff --git a/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java b/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java
index 5b45828..42626d9 100644
--- a/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java
@@ -18,6 +18,7 @@
 
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothHearingAid;
 import android.content.ContentResolver;
 import android.os.Parcel;
 import android.telecom.Log;
@@ -37,13 +38,12 @@
 import org.mockito.Mock;
 
 import java.util.Arrays;
-import java.util.Objects;
+import java.util.Collections;
 
 import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.atLeast;
-import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -58,6 +58,7 @@
 
     @Mock private BluetoothDeviceManager mDeviceManager;
     @Mock private BluetoothHeadsetProxy mHeadsetProxy;
+    @Mock private BluetoothHearingAid mBluetoothHearingAid;
     @Mock private Timeouts.Adapter mTimeoutsAdapter;
     @Mock private BluetoothRouteManager.BluetoothStateListener mListener;
 
@@ -130,18 +131,14 @@
         when(mDeviceManager.getNumConnectedDevices()).thenReturn(devices.length);
         when(mDeviceManager.getConnectedDevices()).thenReturn(Arrays.asList(devices));
         when(mHeadsetProxy.getConnectedDevices()).thenReturn(Arrays.asList(devices));
+        when(mHeadsetProxy.getAudioState(any(BluetoothDevice.class)))
+                .thenReturn(BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
+        when(mBluetoothHearingAid.getConnectedDevices()).thenReturn(Collections.emptyList());
+        when(mBluetoothHearingAid.getActiveDevices()).thenReturn(Arrays.asList(null, null));
         if (activeDevice != null) {
             when(mHeadsetProxy.getAudioState(eq(activeDevice)))
                     .thenReturn(BluetoothHeadset.STATE_AUDIO_CONNECTED);
         }
-        doAnswer(invocation -> {
-            BluetoothDevice first = getFirstExcluding(devices,
-                    (String) invocation.getArguments()[0]);
-            return first == null ? null : first.getAddress();
-        }).when(mDeviceManager).getMostRecentlyConnectedDevice(nullable(String.class));
-        for (BluetoothDevice device : devices) {
-            when(mDeviceManager.getDeviceFromAddress(device.getAddress())).thenReturn(device);
-        }
     }
 
     static void executeRoutingAction(BluetoothRouteManager brm, int message, String
@@ -165,6 +162,7 @@
     private void resetMocks() {
         reset(mDeviceManager, mListener, mHeadsetProxy, mTimeoutsAdapter);
         when(mDeviceManager.getHeadsetService()).thenReturn(mHeadsetProxy);
+        when(mDeviceManager.getHearingAidService()).thenReturn(mBluetoothHearingAid);
         when(mHeadsetProxy.connectAudio()).thenReturn(true);
         when(mHeadsetProxy.setActiveDevice(nullable(BluetoothDevice.class))).thenReturn(true);
         when(mTimeoutsAdapter.getRetryBluetoothConnectAudioBackoffMillis(
@@ -174,17 +172,6 @@
     }
 
     private void verifyConnectionAttempt(BluetoothDevice device, int numTimes) {
-        verify(mHeadsetProxy, times(numTimes)).setActiveDevice(device);
-        verify(mHeadsetProxy, atLeast(numTimes)).connectAudio();
-    }
-
-    private static BluetoothDevice getFirstExcluding(
-            BluetoothDevice[] devices, String excludeAddress) {
-        for (BluetoothDevice x : devices) {
-            if (!Objects.equals(excludeAddress, x.getAddress())) {
-                return x;
-            }
-        }
-        return null;
+        verify(mDeviceManager, times(numTimes)).connectAudio(device.getAddress());
     }
 }
diff --git a/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java b/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java
index e7cd6ee..f87da3c 100644
--- a/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java
+++ b/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java
@@ -18,9 +18,12 @@
 
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothHearingAid;
 import android.content.ContentResolver;
+import android.telecom.Log;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import com.android.internal.os.SomeArgs;
 import com.android.server.telecom.BluetoothHeadsetProxy;
 import com.android.server.telecom.TelecomSystem;
 import com.android.server.telecom.Timeouts;
@@ -36,19 +39,20 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
-import java.util.Objects;
 
 import static com.android.server.telecom.tests.BluetoothRouteManagerTest.DEVICE1;
 import static com.android.server.telecom.tests.BluetoothRouteManagerTest.DEVICE2;
 import static com.android.server.telecom.tests.BluetoothRouteManagerTest.DEVICE3;
 import static com.android.server.telecom.tests.BluetoothRouteManagerTest.executeRoutingAction;
 import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -56,7 +60,7 @@
 public class BluetoothRouteTransitionTests extends TelecomTestCase {
     private enum ListenerUpdate {
         DEVICE_LIST_CHANGED, ACTIVE_DEVICE_PRESENT, ACTIVE_DEVICE_GONE,
-        AUDIO_CONNECTED, AUDIO_DISCONNECTED
+        AUDIO_CONNECTED, AUDIO_DISCONNECTED, UNEXPECTED_STATE_CHANGE
     }
 
     private static class BluetoothRouteTestParametersBuilder {
@@ -73,6 +77,7 @@
         private BluetoothDevice[] connectedDevices;
         // the active device as returned by BluetoothHeadset#getActiveDevice
         private BluetoothDevice activeDevice = null;
+        private List<BluetoothDevice> hearingAidBtDevices = Collections.emptyList();
 
         public BluetoothRouteTestParametersBuilder setName(String name) {
             this.name = name;
@@ -141,6 +146,12 @@
             return this;
         }
 
+        public BluetoothRouteTestParametersBuilder setHearingAidBtDevices(
+                List<BluetoothDevice> hearingAidBtDevices) {
+            this.hearingAidBtDevices = hearingAidBtDevices;
+            return this;
+        }
+
         public BluetoothRouteTestParameters build() {
             return new BluetoothRouteTestParameters(name,
                     initialBluetoothState,
@@ -153,7 +164,8 @@
                     connectedDevices,
                     messageDevice,
                     audioOnDevice,
-                    activeDevice);
+                    activeDevice,
+                    hearingAidBtDevices);
 
         }
     }
@@ -172,13 +184,15 @@
         public BluetoothDevice[] connectedDevices; // array of connected devices
         // the active device as returned by BluetoothHeadset#getActiveDevice
         private BluetoothDevice activeDevice = null;
+        private List<BluetoothDevice> hearingAidBtDevices;
 
         public BluetoothRouteTestParameters(String name, String initialBluetoothState,
                 BluetoothDevice initialDevice, int messageType, ListenerUpdate[]
                 expectedListenerUpdates, int expectedBluetoothInteraction, BluetoothDevice
                 expectedConnectionDevice, String expectedFinalStateName,
                 BluetoothDevice[] connectedDevices, BluetoothDevice messageDevice,
-                BluetoothDevice audioOnDevice, BluetoothDevice activeDevice) {
+                BluetoothDevice audioOnDevice, BluetoothDevice activeDevice,
+                List<BluetoothDevice> hearingAidBtDevices) {
             this.name = name;
             this.initialBluetoothState = initialBluetoothState;
             this.initialDevice = initialDevice;
@@ -191,6 +205,7 @@
             this.messageDevice = messageDevice;
             this.audioOnDevice = audioOnDevice;
             this.activeDevice = activeDevice;
+            this.hearingAidBtDevices = hearingAidBtDevices;
         }
 
         @Override
@@ -207,6 +222,7 @@
                     ", expectedFinalStateName='" + expectedFinalStateName + '\'' +
                     ", connectedDevices=" + Arrays.toString(connectedDevices) +
                     ", activeDevice='" + activeDevice + '\'' +
+                    ", hearingAidBtDevices ='" + hearingAidBtDevices + '\'' +
                     '}';
         }
     }
@@ -220,6 +236,7 @@
     private final BluetoothRouteTestParameters mParams;
     @Mock private BluetoothDeviceManager mDeviceManager;
     @Mock private BluetoothHeadsetProxy mHeadsetProxy;
+    @Mock private BluetoothHearingAid mBluetoothHearingAid;
     @Mock private Timeouts.Adapter mTimeoutsAdapter;
     @Mock private BluetoothRouteManager.BluetoothStateListener mListener;
 
@@ -241,21 +258,37 @@
 
         setupConnectedDevices(mParams.connectedDevices,
                 mParams.audioOnDevice, mParams.activeDevice);
-        sm.setActiveDeviceCacheForTesting(mParams.activeDevice);
+        sm.setActiveDeviceCacheForTesting(mParams.activeDevice,
+                mParams.hearingAidBtDevices.contains(mParams.messageDevice));
+        if (mParams.initialDevice != null) {
+            doAnswer(invocation -> {
+                SomeArgs args = SomeArgs.obtain();
+                args.arg1 = Log.createSubsession();
+                args.arg2 = mParams.initialDevice.getAddress();
+                sm.sendMessage(BluetoothRouteManager.BT_AUDIO_LOST, args);
+                when(mHeadsetProxy.getAudioState(eq(mParams.initialDevice)))
+                        .thenReturn(BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
+                return true;
+            }).when(mDeviceManager).disconnectAudio();
+        }
 
         // Go through the utility methods for these two messages
         if (mParams.messageType == BluetoothRouteManager.NEW_DEVICE_CONNECTED) {
             sm.onDeviceAdded(mParams.messageDevice.getAddress());
-            sm.onActiveDeviceChanged(mParams.messageDevice);
+            sm.onActiveDeviceChanged(mParams.messageDevice,
+                    mParams.hearingAidBtDevices.contains(mParams.messageDevice));
         } else if (mParams.messageType == BluetoothRouteManager.LOST_DEVICE) {
             sm.onDeviceLost(mParams.messageDevice.getAddress());
-            sm.onActiveDeviceChanged(null);
+            sm.onActiveDeviceChanged(null,
+                    mParams.hearingAidBtDevices.contains(mParams.messageDevice));
         } else {
             executeRoutingAction(sm, mParams.messageType,
                     mParams.messageDevice == null ? null : mParams.messageDevice.getAddress());
         }
 
         waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
+        waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
+        waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
         assertEquals(mParams.expectedFinalStateName, sm.getCurrentState().getName());
 
         for (ListenerUpdate lu : mParams.expectedListenerUpdates) {
@@ -280,19 +313,15 @@
 
         switch (mParams.expectedBluetoothInteraction) {
             case NONE:
-                verify(mHeadsetProxy, never()).connectAudio();
-                verify(mHeadsetProxy, never()).setActiveDevice(nullable(BluetoothDevice.class));
-                verify(mHeadsetProxy, never()).disconnectAudio();
+                verify(mDeviceManager, never()).connectAudio(nullable(String.class));
                 break;
             case CONNECT:
-                verify(mHeadsetProxy).connectAudio();
-                verify(mHeadsetProxy).setActiveDevice(mParams.expectedConnectionDevice);
-                verify(mHeadsetProxy, never()).disconnectAudio();
+                verify(mDeviceManager).connectAudio(mParams.expectedConnectionDevice.getAddress());
+                verify(mDeviceManager, never()).disconnectAudio();
                 break;
             case DISCONNECT:
-                verify(mHeadsetProxy, never()).connectAudio();
-                verify(mHeadsetProxy, never()).setActiveDevice(nullable(BluetoothDevice.class));
-                verify(mHeadsetProxy).disconnectAudio();
+                verify(mDeviceManager, never()).connectAudio(nullable(String.class));
+                verify(mDeviceManager).disconnectAudio();
                 break;
         }
 
@@ -306,23 +335,24 @@
         when(mDeviceManager.getConnectedDevices()).thenReturn(Arrays.asList(devices));
         when(mHeadsetProxy.getConnectedDevices()).thenReturn(Arrays.asList(devices));
         when(mHeadsetProxy.getActiveDevice()).thenReturn(activeDevice);
+        when(mHeadsetProxy.getAudioState(any(BluetoothDevice.class)))
+                .thenReturn(BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
         if (audioOnDevice != null) {
             when(mHeadsetProxy.getAudioState(eq(audioOnDevice)))
                     .thenReturn(BluetoothHeadset.STATE_AUDIO_CONNECTED);
         }
-        doAnswer(invocation -> {
-            BluetoothDevice first = getFirstExcluding(devices,
-                    (String) invocation.getArguments()[0]);
-            return first == null ? null : first.getAddress();
-        }).when(mDeviceManager).getMostRecentlyConnectedDevice(nullable(String.class));
-        for (BluetoothDevice device : devices) {
-            when(mDeviceManager.getDeviceFromAddress(device.getAddress())).thenReturn(device);
-        }
     }
 
     private BluetoothRouteManager setupStateMachine(String initialState,
             BluetoothDevice initialDevice) {
         resetMocks();
+        when(mDeviceManager.getHeadsetService()).thenReturn(mHeadsetProxy);
+        when(mDeviceManager.getHearingAidService()).thenReturn(mBluetoothHearingAid);
+        when(mDeviceManager.connectAudio(nullable(String.class))).thenReturn(true);
+        when(mTimeoutsAdapter.getRetryBluetoothConnectAudioBackoffMillis(
+                nullable(ContentResolver.class))).thenReturn(100000L);
+        when(mTimeoutsAdapter.getBluetoothPendingTimeoutMillis(
+                nullable(ContentResolver.class))).thenReturn(100000L);
         BluetoothRouteManager sm = new BluetoothRouteManager(mContext,
                 new TelecomSystem.SyncRoot() { }, mDeviceManager, mTimeoutsAdapter);
         sm.setListener(mListener);
@@ -333,24 +363,7 @@
     }
 
     private void resetMocks() {
-        reset(mDeviceManager, mListener, mHeadsetProxy, mTimeoutsAdapter);
-        when(mDeviceManager.getHeadsetService()).thenReturn(mHeadsetProxy);
-        when(mHeadsetProxy.connectAudio()).thenReturn(true);
-        when(mHeadsetProxy.setActiveDevice(nullable(BluetoothDevice.class))).thenReturn(true);
-        when(mTimeoutsAdapter.getRetryBluetoothConnectAudioBackoffMillis(
-                nullable(ContentResolver.class))).thenReturn(100000L);
-        when(mTimeoutsAdapter.getBluetoothPendingTimeoutMillis(
-                nullable(ContentResolver.class))).thenReturn(100000L);
-    }
-
-    private static BluetoothDevice getFirstExcluding(
-            BluetoothDevice[] devices, String excludeAddress) {
-        for (BluetoothDevice x : devices) {
-            if (!Objects.equals(excludeAddress, x.getAddress())) {
-                return x;
-            }
-        }
-        return null;
+        clearInvocations(mDeviceManager, mListener, mHeadsetProxy, mTimeoutsAdapter);
     }
 
     @Parameterized.Parameters(name = "{0}")
@@ -390,7 +403,7 @@
                 .setInitialDevice(DEVICE2)
                 .setAudioOnDevice(DEVICE2)
                 .setConnectedDevices(DEVICE2, DEVICE1)
-                .setMessageType(BluetoothRouteManager.HFP_IS_ON)
+                .setMessageType(BluetoothRouteManager.BT_AUDIO_IS_ON)
                 .setMessageDevice(DEVICE2)
                 .setExpectedListenerUpdates(ListenerUpdate.AUDIO_CONNECTED)
                 .setExpectedBluetoothInteraction(NONE)
@@ -404,7 +417,7 @@
                 .setInitialBluetoothState(BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX)
                 .setInitialDevice(DEVICE2)
                 .setConnectedDevices(DEVICE2)
-                .setMessageType(BluetoothRouteManager.HFP_LOST)
+                .setMessageType(BluetoothRouteManager.BT_AUDIO_LOST)
                 .setMessageDevice(DEVICE2)
                 .setExpectedListenerUpdates(ListenerUpdate.AUDIO_DISCONNECTED)
                 .setExpectedBluetoothInteraction(NONE)
@@ -418,7 +431,7 @@
                 .setInitialBluetoothState(BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX)
                 .setInitialDevice(DEVICE2)
                 .setConnectedDevices(DEVICE2, DEVICE1, DEVICE3)
-                .setMessageType(BluetoothRouteManager.HFP_LOST)
+                .setMessageType(BluetoothRouteManager.BT_AUDIO_LOST)
                 .setMessageDevice(DEVICE2)
                 .setExpectedListenerUpdates(ListenerUpdate.AUDIO_DISCONNECTED)
                 .setExpectedBluetoothInteraction(NONE)
@@ -503,7 +516,7 @@
                 .setInitialDevice(DEVICE2)
                 .setConnectedDevices(DEVICE2, DEVICE1)
                 .setAudioOnDevice(DEVICE1)
-                .setMessageType(BluetoothRouteManager.HFP_IS_ON)
+                .setMessageType(BluetoothRouteManager.BT_AUDIO_IS_ON)
                 .setMessageDevice(DEVICE1)
                 .setExpectedListenerUpdates(ListenerUpdate.AUDIO_CONNECTED)
                 .setExpectedBluetoothInteraction(NONE)
@@ -526,6 +539,18 @@
                 .build());
 
         result.add(new BluetoothRouteTestParametersBuilder()
+                .setName("Audio disconnect comes with a null device")
+                .setInitialBluetoothState(BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX)
+                .setInitialDevice(DEVICE2)
+                .setConnectedDevices(DEVICE2)
+                .setMessageType(BluetoothRouteManager.BT_AUDIO_LOST)
+                .setMessageDevice(null)
+                .setExpectedListenerUpdates(ListenerUpdate.AUDIO_DISCONNECTED)
+                .setExpectedBluetoothInteraction(NONE)
+                .setExpectedFinalStateName(BluetoothRouteManager.AUDIO_OFF_STATE_NAME)
+                .build());
+
+        result.add(new BluetoothRouteTestParametersBuilder()
                 .setName("Device gets disconnected while pending. No fallback.")
                 .setInitialBluetoothState(BluetoothRouteManager.AUDIO_CONNECTING_STATE_NAME_PREFIX)
                 .setInitialDevice(DEVICE2)
@@ -539,6 +564,19 @@
                 .build());
 
         result.add(new BluetoothRouteTestParametersBuilder()
+                .setName("Device gets audio-off while in another device's audio on state")
+                .setInitialBluetoothState(BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX)
+                .setInitialDevice(DEVICE2)
+                .setConnectedDevices(DEVICE2, DEVICE1)
+                .setMessageType(BluetoothRouteManager.BT_AUDIO_LOST)
+                .setMessageDevice(DEVICE1)
+                .setExpectedListenerUpdates(ListenerUpdate.UNEXPECTED_STATE_CHANGE)
+                .setExpectedBluetoothInteraction(NONE)
+                .setExpectedFinalStateName(BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX
+                        + ":" + DEVICE2)
+                .build());
+
+        result.add(new BluetoothRouteTestParametersBuilder()
                 .setName("Audio routing requests HFP disconnection while a device is active")
                 .setInitialBluetoothState(BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX)
                 .setInitialDevice(DEVICE2)
@@ -565,7 +603,7 @@
                 .setInitialBluetoothState(BluetoothRouteManager.AUDIO_OFF_STATE_NAME)
                 .setInitialDevice(null)
                 .setConnectedDevices(DEVICE2, DEVICE3)
-                .setMessageType(BluetoothRouteManager.HFP_IS_ON)
+                .setMessageType(BluetoothRouteManager.BT_AUDIO_IS_ON)
                 .setMessageDevice(DEVICE3)
                 .setExpectedListenerUpdates(ListenerUpdate.AUDIO_CONNECTED)
                 .setExpectedBluetoothInteraction(NONE)
@@ -573,6 +611,19 @@
                         + ":" + DEVICE3)
                 .build());
 
+        result.add(new BluetoothRouteTestParametersBuilder()
+                .setName("Hearing aid device disconnects with headset present")
+                .setInitialBluetoothState(BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX)
+                .setInitialDevice(DEVICE2)
+                .setConnectedDevices(DEVICE2, DEVICE3)
+                .setHearingAidBtDevices(Collections.singletonList(DEVICE2))
+                .setMessageType(BluetoothRouteManager.LOST_DEVICE)
+                .setMessageDevice(DEVICE2)
+                .setExpectedListenerUpdates(ListenerUpdate.AUDIO_DISCONNECTED,
+                        ListenerUpdate.DEVICE_LIST_CHANGED)
+                .setExpectedBluetoothInteraction(NONE)
+                .setExpectedFinalStateName(BluetoothRouteManager.AUDIO_OFF_STATE_NAME)
+                .build());
         return result;
     }
 }
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioManagerTest.java b/tests/src/com/android/server/telecom/tests/CallAudioManagerTest.java
index 5e23dcc..01add22 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioManagerTest.java
@@ -77,7 +77,7 @@
             InCallTonePlayer mockInCallTonePlayer = mock(InCallTonePlayer.class);
             doAnswer((invocation2) -> {
                 mCallAudioManager.setIsTonePlaying(true);
-                return null;
+                return true;
             }).when(mockInCallTonePlayer).startTone();
             return mockInCallTonePlayer;
         }).when(mPlayerFactory).createPlayer(anyInt());
@@ -198,13 +198,16 @@
         Call call = createIncomingCall();
         when(call.can(android.telecom.Call.Details.CAPABILITY_SPEED_UP_MT_AUDIO))
                 .thenReturn(true);
+        when(call.getState()).thenReturn(CallState.ANSWERED);
 
         ArgumentCaptor<CallAudioModeStateMachine.MessageArgs> captor =
                 ArgumentCaptor.forClass(CallAudioModeStateMachine.MessageArgs.class);
         // Answer the incoming call
-        mCallAudioManager.onIncomingCallAnswered(call);
+        mCallAudioManager.onCallStateChanged(call, CallState.RINGING, CallState.ANSWERED);
         verify(mCallAudioModeStateMachine).sendMessageWithArgs(
-                eq(CallAudioModeStateMachine.MT_AUDIO_SPEEDUP_FOR_RINGING_CALL), captor.capture());
+                eq(CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL), captor.capture());
+        verify(mCallAudioModeStateMachine).sendMessageWithArgs(
+                eq(CallAudioModeStateMachine.NO_MORE_RINGING_CALLS), captor.capture());
         CallAudioModeStateMachine.MessageArgs correctArgs =
                 new CallAudioModeStateMachine.MessageArgs(
                         true, // hasActiveOrDialingCalls
@@ -217,7 +220,7 @@
         assertMessageArgEquality(correctArgs, captor.getValue());
         assertMessageArgEquality(correctArgs, captor.getValue());
         when(call.getState()).thenReturn(CallState.ACTIVE);
-        mCallAudioManager.onCallStateChanged(call, CallState.RINGING, CallState.ACTIVE);
+        mCallAudioManager.onCallStateChanged(call, CallState.ANSWERED, CallState.ACTIVE);
 
         disconnectCall(call);
         stopTone();
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioModeStateMachineTest.java b/tests/src/com/android/server/telecom/tests/CallAudioModeStateMachineTest.java
index f253d19..56f585f 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioModeStateMachineTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioModeStateMachineTest.java
@@ -22,6 +22,7 @@
 import com.android.server.telecom.CallAudioManager;
 import com.android.server.telecom.CallAudioModeStateMachine;
 import com.android.server.telecom.CallAudioRouteStateMachine;
+import com.android.server.telecom.SystemStateHelper;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -30,9 +31,11 @@
 import org.mockito.Mock;
 
 import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -40,6 +43,7 @@
 public class CallAudioModeStateMachineTest extends TelecomTestCase {
     private static final int TEST_TIMEOUT = 1000;
 
+    @Mock private SystemStateHelper mSystemStateHelper;
     @Mock private AudioManager mAudioManager;
     @Mock private CallAudioManager mCallAudioManager;
 
@@ -52,7 +56,8 @@
     @SmallTest
     @Test
     public void testNoFocusWhenRingerSilenced() throws Throwable {
-        CallAudioModeStateMachine sm = new CallAudioModeStateMachine(mAudioManager);
+        CallAudioModeStateMachine sm = new CallAudioModeStateMachine(mSystemStateHelper,
+                mAudioManager);
         sm.setCallAudioManager(mCallAudioManager);
         sm.sendMessage(CallAudioModeStateMachine.ABANDON_FOCUS_FOR_TESTING);
         waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
@@ -83,8 +88,47 @@
 
     @SmallTest
     @Test
+    public void testNoRingWhenDeviceIsAtEar() {
+        CallAudioModeStateMachine sm = new CallAudioModeStateMachine(mSystemStateHelper,
+                mAudioManager);
+        sm.setCallAudioManager(mCallAudioManager);
+        sm.sendMessage(CallAudioModeStateMachine.ABANDON_FOCUS_FOR_TESTING);
+        sm.sendMessage(CallAudioModeStateMachine.NEW_HOLDING_CALL,
+                new CallAudioModeStateMachine.MessageArgs(
+                        false, // hasActiveOrDialingCalls
+                        false, // hasRingingCalls
+                        true, // hasHoldingCalls
+                        false, // isTonePlaying
+                        false, // foregroundCallIsVoip
+                        null // session
+                ));
+        waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
+        assertEquals(CallAudioModeStateMachine.TONE_HOLD_STATE_NAME, sm.getCurrentStateName());
+        when(mSystemStateHelper.isDeviceAtEar()).thenReturn(true);
+
+        resetMocks();
+        sm.sendMessage(CallAudioModeStateMachine.NEW_RINGING_CALL,
+                new CallAudioModeStateMachine.MessageArgs(
+                        false, // hasActiveOrDialingCalls
+                        true, // hasRingingCalls
+                        true, // hasHoldingCalls
+                        false, // isTonePlaying
+                        false, // foregroundCallIsVoip
+                        null // session
+                ));
+        waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
+
+        verify(mAudioManager, never()).requestAudioFocusForCall(anyInt(), anyInt());
+        verify(mAudioManager, never()).setMode(anyInt());
+        verify(mCallAudioManager, never()).startRinging();
+        verify(mCallAudioManager).startCallWaiting(nullable(String.class));
+    }
+
+    @SmallTest
+    @Test
     public void testRegainFocusWhenHfpIsConnectedSilenced() throws Throwable {
-        CallAudioModeStateMachine sm = new CallAudioModeStateMachine(mAudioManager);
+        CallAudioModeStateMachine sm = new CallAudioModeStateMachine(mSystemStateHelper,
+                mAudioManager);
         sm.setCallAudioManager(mCallAudioManager);
         sm.sendMessage(CallAudioModeStateMachine.ABANDON_FOCUS_FOR_TESTING);
         waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
@@ -117,7 +161,7 @@
         sm.sendMessage(CallAudioModeStateMachine.RINGER_MODE_CHANGE);
         waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
 
-        verify(mCallAudioManager).startRinging();
+        verify(mCallAudioManager, times(2)).startRinging();
         verify(mAudioManager).requestAudioFocusForCall(AudioManager.STREAM_RING,
                 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
         verify(mAudioManager).setMode(AudioManager.MODE_RINGTONE);
@@ -127,6 +171,6 @@
 
 
     private void resetMocks() {
-        reset(mCallAudioManager, mAudioManager);
+        clearInvocations(mCallAudioManager, mAudioManager);
     }
 }
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioModeTransitionTests.java b/tests/src/com/android/server/telecom/tests/CallAudioModeTransitionTests.java
index b8b4859..81339ed 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioModeTransitionTests.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioModeTransitionTests.java
@@ -16,11 +16,13 @@
 
 package com.android.server.telecom.tests;
 
+import android.content.Context;
 import android.media.AudioManager;
 import android.test.suitebuilder.annotation.SmallTest;
 
 import com.android.server.telecom.CallAudioManager;
 import com.android.server.telecom.CallAudioModeStateMachine;
+import com.android.server.telecom.SystemStateHelper;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -35,6 +37,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
@@ -95,6 +98,7 @@
 
     private static final int TEST_TIMEOUT = 1000;
 
+    @Mock private SystemStateHelper mSystemStateHelper;
     @Mock private AudioManager mAudioManager;
     @Mock private CallAudioManager mCallAudioManager;
     private final ModeTestParameters mParams;
@@ -112,7 +116,8 @@
     @Test
     @SmallTest
     public void modeTransitionTest() {
-        CallAudioModeStateMachine sm = new CallAudioModeStateMachine(mAudioManager);
+        CallAudioModeStateMachine sm = new CallAudioModeStateMachine(mSystemStateHelper,
+                mAudioManager);
         sm.setCallAudioManager(mCallAudioManager);
         sm.sendMessage(mParams.initialAudioState);
         waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
@@ -163,11 +168,11 @@
 
         switch (mParams.expectedCallWaitingInteraction) {
             case NO_CHANGE:
-                verify(mCallAudioManager, never()).startCallWaiting();
+                verify(mCallAudioManager, never()).startCallWaiting(nullable(String.class));
                 verify(mCallAudioManager, never()).stopCallWaiting();
                 break;
             case ON:
-                verify(mCallAudioManager).startCallWaiting();
+                verify(mCallAudioManager).startCallWaiting(nullable(String.class));
                 break;
             case OFF:
                 verify(mCallAudioManager).stopCallWaiting();
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
index 9c90d3e..7f289b8 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
@@ -538,12 +538,35 @@
         initializationTestHelper(expectedState, CallAudioRouteStateMachine.EARPIECE_FORCE_DISABLED);
     }
 
+    @SmallTest
+    @Test
+    public void testInitializationWithAvailableButInactiveBtDevice() {
+        CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_EARPIECE,
+                CallAudioState.ROUTE_SPEAKER | CallAudioState.ROUTE_BLUETOOTH
+                        | CallAudioState.ROUTE_EARPIECE);
+        when(mockBluetoothRouteManager.isBluetoothAvailable()).thenReturn(true);
+        when(mockBluetoothRouteManager.hasBtActiveDevice()).thenReturn(false);
+
+        CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
+                mContext,
+                mockCallsManager,
+                mockBluetoothRouteManager,
+                mockWiredHeadsetManager,
+                mockStatusBarNotifier,
+                mAudioServiceFactory,
+                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED);
+        stateMachine.initialize();
+        assertEquals(expectedState, stateMachine.getCurrentCallAudioState());
+    }
+
     private void initializationTestHelper(CallAudioState expectedState,
             int earpieceControl) {
         when(mockWiredHeadsetManager.isPluggedIn()).thenReturn(
                 (expectedState.getSupportedRouteMask() & CallAudioState.ROUTE_WIRED_HEADSET) != 0);
         when(mockBluetoothRouteManager.isBluetoothAvailable()).thenReturn(
                 (expectedState.getSupportedRouteMask() & CallAudioState.ROUTE_BLUETOOTH) != 0);
+        when(mockBluetoothRouteManager.hasBtActiveDevice()).thenReturn(
+                (expectedState.getSupportedRouteMask() & CallAudioState.ROUTE_BLUETOOTH) != 0);
 
         CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
                 mContext,
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java
index 19630b1..e63fe9b 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java
@@ -16,6 +16,18 @@
 
 package com.android.server.telecom.tests;
 
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.ArgumentMatchers.same;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
 import android.bluetooth.BluetoothDevice;
 import android.content.Context;
 import android.media.AudioManager;
@@ -26,7 +38,6 @@
 
 import com.android.server.telecom.Call;
 import com.android.server.telecom.CallAudioManager;
-import com.android.server.telecom.CallAudioModeStateMachine;
 import com.android.server.telecom.CallAudioRouteStateMachine;
 import com.android.server.telecom.CallsManager;
 import com.android.server.telecom.ConnectionServiceWrapper;
@@ -41,6 +52,7 @@
 import org.junit.runners.Parameterized;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
 import java.util.ArrayList;
@@ -48,21 +60,6 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
-import java.util.concurrent.CountDownLatch;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.nullable;
-import static org.mockito.ArgumentMatchers.same;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.mock;
-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;
 
 @RunWith(Parameterized.class)
 public class CallAudioRouteTransitionTests extends TelecomTestCase {
@@ -191,16 +188,16 @@
     private void setupMocksForParams(final CallAudioRouteStateMachine sm,
             RoutingTestParameters params) {
         // Set up bluetooth and speakerphone state
-        when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(
-                params.initialRoute == CallAudioState.ROUTE_BLUETOOTH);
-        when(mockBluetoothRouteManager.isBluetoothAvailable()).thenReturn(
-                (params.availableRoutes & CallAudioState.ROUTE_BLUETOOTH) != 0
-                        || (params.expectedAvailableRoutes & CallAudioState.ROUTE_BLUETOOTH) != 0);
-        when(mockBluetoothRouteManager.getConnectedDevices())
-                .thenReturn(params.availableBluetoothDevices);
+        doReturn(params.initialRoute == CallAudioState.ROUTE_BLUETOOTH)
+                .when(mockBluetoothRouteManager).isBluetoothAudioConnectedOrPending();
+        doReturn((params.availableRoutes & CallAudioState.ROUTE_BLUETOOTH) != 0
+                || (params.expectedAvailableRoutes & CallAudioState.ROUTE_BLUETOOTH) != 0)
+                .when(mockBluetoothRouteManager).isBluetoothAvailable();
+        doReturn(params.availableBluetoothDevices)
+                .when(mockBluetoothRouteManager).getConnectedDevices();
         if (params.initialBluetoothDevice != null) {
-            when(mockBluetoothRouteManager.getBluetoothAudioConnectedDevice())
-                    .thenReturn(params.initialBluetoothDevice);
+            doReturn(params.initialBluetoothDevice)
+                    .when(mockBluetoothRouteManager).getBluetoothAudioConnectedDevice();
         }
 
 
@@ -264,9 +261,8 @@
         }
         waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
 
-        // Reset mocks to discard stuff from initialization
-        resetMocks();
-        setupMocksForParams(stateMachine, mParams);
+        // Clear invocations on mocks to discard stuff from initialization
+        clearInvocations();
 
         sendActionToStateMachine(stateMachine);
 
@@ -337,11 +333,11 @@
         stateMachine.setCallAudioManager(mockCallAudioManager);
 
         // Set up bluetooth and speakerphone state
-        when(mockBluetoothRouteManager.isBluetoothAvailable()).thenReturn(
-                (mParams.availableRoutes & CallAudioState.ROUTE_BLUETOOTH) != 0
-                || (mParams.expectedAvailableRoutes & CallAudioState.ROUTE_BLUETOOTH) != 0);
-        when(mockBluetoothRouteManager.getConnectedDevices())
-                .thenReturn(mParams.availableBluetoothDevices);
+        doReturn((mParams.availableRoutes & CallAudioState.ROUTE_BLUETOOTH) != 0 ||
+                (mParams.expectedAvailableRoutes & CallAudioState.ROUTE_BLUETOOTH) != 0)
+                .when(mockBluetoothRouteManager).isBluetoothAvailable();
+        doReturn(mParams.availableBluetoothDevices)
+                .when(mockBluetoothRouteManager).getConnectedDevices();
         when(mockAudioManager.isSpeakerphoneOn()).thenReturn(
                 mParams.initialRoute == CallAudioState.ROUTE_SPEAKER);
         when(fakeCall.getSupportedAudioRoutes()).thenReturn(mParams.callSupportedRoutes);
@@ -511,7 +507,7 @@
                 CallAudioState.ROUTE_BLUETOOTH, // initialRoute
                 CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // availableRoutes
                 OPTIONAL, // speakerInteraction
-                OFF, // bluetoothInteraction
+                NONE, // bluetoothInteraction
                 SPECIAL_DISCONNECT_BT_ACTION, // action
                 CallAudioState.ROUTE_EARPIECE, // expectedRoute
                 CallAudioState.ROUTE_EARPIECE, // expectedAvailableRoutes
@@ -523,7 +519,7 @@
                 CallAudioState.ROUTE_BLUETOOTH, // initialRoute
                 CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_BLUETOOTH, // availableRou
                 OPTIONAL, // speakerInteraction
-                OFF, // bluetoothInteraction
+                NONE, // bluetoothInteraction
                 SPECIAL_DISCONNECT_BT_ACTION, // action
                 CallAudioState.ROUTE_WIRED_HEADSET, // expectedRoute
                 CallAudioState.ROUTE_WIRED_HEADSET, // expectedAvailableRoutes
@@ -715,7 +711,7 @@
                 CallAudioState.ROUTE_BLUETOOTH, // initialRoute
                 CallAudioState.ROUTE_BLUETOOTH,  // availableRoutes
                 ON, // speakerInteraction
-                OFF, // bluetoothInteraction
+                NONE, // bluetoothInteraction
                 SPECIAL_DISCONNECT_BT_ACTION, // action
                 CallAudioState.ROUTE_SPEAKER, // expectedRoute
                 CallAudioState.ROUTE_SPEAKER, // expectedAvailableRoutes
@@ -727,7 +723,7 @@
                 CallAudioState.ROUTE_BLUETOOTH, // initialRoute
                 CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // availableRoutes
                 OPTIONAL, // speakerInteraction
-                OFF, // bluetoothInteraction
+                NONE, // bluetoothInteraction
                 CallAudioRouteStateMachine.BT_ACTIVE_DEVICE_GONE, // action
                 CallAudioState.ROUTE_EARPIECE, // expectedRoute
                 CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // expectedAvailabl
@@ -777,16 +773,8 @@
                 any(Call.class), any(CallAudioState.class));
     }
 
-    private void resetMocks() {
-        reset(mockAudioManager, mockBluetoothRouteManager, mockCallsManager,
+    private void clearInvocations() {
+        Mockito.clearInvocations(mockAudioManager, mockBluetoothRouteManager, mockCallsManager,
                 mockConnectionServiceWrapper);
-        fakeCall = mock(Call.class);
-        when(mockCallsManager.getForegroundCall()).thenReturn(fakeCall);
-        when(fakeCall.getConnectionService()).thenReturn(mockConnectionServiceWrapper);
-        when(fakeCall.isAlive()).thenReturn(true);
-        when(fakeCall.getSupportedAudioRoutes()).thenReturn(CallAudioState.ROUTE_ALL);
-        when(mockCallsManager.getLock()).thenReturn(mLock);
-        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/CallExtrasTest.java b/tests/src/com/android/server/telecom/tests/CallExtrasTest.java
index 44578c5..3bedb75 100644
--- a/tests/src/com/android/server/telecom/tests/CallExtrasTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallExtrasTest.java
@@ -22,6 +22,7 @@
 import android.telecom.Connection;
 import android.telecom.InCallService;
 import android.telecom.ParcelableCall;
+import android.support.test.filters.FlakyTest;
 import android.test.suitebuilder.annotation.LargeTest;
 import android.test.suitebuilder.annotation.MediumTest;
 
@@ -398,6 +399,7 @@
      * @throws Exception
      */
     @LargeTest
+    @FlakyTest(bugId = 117751305)
     @Test
     public void testConferenceExtraOperations() throws Exception {
         ParcelableCall call = makeConferenceCall();
diff --git a/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java b/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java
index 690a38a..c83c3f3 100644
--- a/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java
@@ -538,6 +538,7 @@
     }
 
     @MediumTest
+    @FlakyTest(bugId = 117751305)
     @Test
     public void testLogCallDirectionIncomingWithMultiUserCapability() {
         when(mMockPhoneAccountRegistrar.getPhoneAccountUnchecked(any(PhoneAccountHandle.class)))
diff --git a/tests/src/com/android/server/telecom/tests/CallRedirectionProcessorTest.java b/tests/src/com/android/server/telecom/tests/CallRedirectionProcessorTest.java
new file mode 100644
index 0000000..fa78383
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/CallRedirectionProcessorTest.java
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2018 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.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.IBinder;
+import android.os.UserHandle;
+import android.telecom.GatewayInfo;
+import android.telecom.PhoneAccountHandle;
+import com.android.internal.telecom.ICallRedirectionService;
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.PhoneAccountRegistrar;
+import com.android.server.telecom.SystemStateHelper;
+import com.android.server.telecom.TelecomSystem;
+import com.android.server.telecom.Timeouts;
+
+import com.android.server.telecom.callredirection.CallRedirectionProcessor;
+import com.android.server.telecom.callredirection.CallRedirectionProcessorHelper;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+
+import org.junit.Before;
+
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(JUnit4.class)
+public class CallRedirectionProcessorTest extends TelecomTestCase {
+    @Mock private Context mContext;
+    @Mock private CallsManager mCallsManager;
+    @Mock private PhoneAccountRegistrar mPhoneAccountRegistrar;
+    @Mock private PhoneAccountHandle mPhoneAccountHandle;
+    private TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() { };
+
+    @Mock private Call mCall;
+
+    @Mock private PackageManager mPackageManager;
+    @Mock private IBinder mBinder;
+    @Mock private ICallRedirectionService mCallRedirectionService;
+
+    @Mock private SystemStateHelper mSystemStateHelper;
+    @Mock private CallRedirectionProcessorHelper mCallRedirectionProcessorHelper;
+
+    @Mock private Uri mHandle;
+    @Mock private GatewayInfo mGatewayInfo;
+    @Mock private UserHandle mUserHandle;
+    @Mock private ContentResolver mContentResolver;
+
+    @Mock private Timeouts.Adapter mTimeoutsAdapter;
+
+    private static final String USER_DEFINED_PKG_NAME = "user_defined_pkg";
+    private static final String USER_DEFINED_CLS_NAME = "user_defined_cls";
+    private static final String CARRIER_PKG_NAME = "carrier_pkg";
+    private static final String CARRIER_CLS_NAME = "carrier_cls";
+
+    private static final long HANDLER_TIMEOUT_DELAY = 5000;
+    private static final long USER_DEFINED_SHORT_TIMEOUT_MS = 1200;
+    private static final long CARRIER_SHORT_TIMEOUT_MS = 400;
+    private static final long CODE_EXECUTION_DELAY = 500;
+
+    // TODO integerate with a test user-defined service
+    private static final ComponentName USER_DEFINED_SERVICE_TEST_COMPONENT_NAME =
+            new ComponentName(USER_DEFINED_PKG_NAME, USER_DEFINED_CLS_NAME);
+    // TODO integerate with a test carrier service
+    private static final ComponentName CARRIER_SERVICE_TEST_COMPONENT_NAME =
+            new ComponentName(CARRIER_PKG_NAME, CARRIER_CLS_NAME);
+
+    private static final boolean SPEAKER_PHONE_ON = true;
+    private static final int VIDEO_STATE = 0;
+
+    private CallRedirectionProcessor mProcessor;
+
+    @Override
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        when(mCall.getTargetPhoneAccount()).thenReturn(mPhoneAccountHandle);
+        when(mCallsManager.getCurrentUserHandle()).thenReturn(UserHandle.CURRENT);
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+        when(mContext.getContentResolver()).thenReturn(mContentResolver);
+        doReturn(mCallRedirectionService).when(mBinder).queryLocalInterface(anyString());
+        when(mCallsManager.getSystemStateHelper()).thenReturn(mSystemStateHelper);
+        when(mCallsManager.getTimeoutsAdapter()).thenReturn(mTimeoutsAdapter);
+        when(mTimeoutsAdapter.getUserDefinedCallRedirectionTimeoutMillis(mContentResolver))
+                .thenReturn(USER_DEFINED_SHORT_TIMEOUT_MS);
+        when(mTimeoutsAdapter.getCarrierCallRedirectionTimeoutMillis(mContentResolver))
+                .thenReturn(CARRIER_SHORT_TIMEOUT_MS);
+        when(mCallsManager.getLock()).thenReturn(mLock);
+        when(mCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
+        when(mContext.bindServiceAsUser(nullable(Intent.class), nullable(ServiceConnection.class),
+                anyInt(), eq(UserHandle.CURRENT))).thenReturn(true);
+    }
+
+    private void setIsInCarMode(boolean isInCarMode) {
+        when(mSystemStateHelper.isCarMode()).thenReturn(isInCarMode);
+    }
+
+    private void enableUserDefinedCallRedirectionService() {
+        when(mCallRedirectionProcessorHelper.getUserDefinedCallRedirectionService()).thenReturn(
+                USER_DEFINED_SERVICE_TEST_COMPONENT_NAME);
+    }
+
+    private void enableCarrierCallRedirectionService() {
+        when(mCallRedirectionProcessorHelper.getCarrierCallRedirectionService(
+                any(PhoneAccountHandle.class))).thenReturn(CARRIER_SERVICE_TEST_COMPONENT_NAME);
+    }
+
+    private void disableUserDefinedCallRedirectionService() {
+        when(mCallRedirectionProcessorHelper.getUserDefinedCallRedirectionService()).thenReturn(
+                null);
+    }
+
+    private void disableCarrierCallRedirectionService() {
+        when(mCallRedirectionProcessorHelper.getCarrierCallRedirectionService(any())).thenReturn(
+                null);
+    }
+
+    private void startProcessWithNoGateWayInfo() {
+        mProcessor = new CallRedirectionProcessor(mContext, mCallsManager, mCall, mHandle,
+                mPhoneAccountRegistrar, null, SPEAKER_PHONE_ON, VIDEO_STATE);
+        mProcessor.setCallRedirectionServiceHelper(mCallRedirectionProcessorHelper);
+    }
+
+    private void startProcessWithGateWayInfo() {
+        mProcessor = new CallRedirectionProcessor(mContext, mCallsManager, mCall, mHandle,
+                mPhoneAccountRegistrar, mGatewayInfo, SPEAKER_PHONE_ON, VIDEO_STATE);
+        mProcessor.setCallRedirectionServiceHelper(mCallRedirectionProcessorHelper);
+    }
+
+    @Test
+    public void testNoUserDefinedServiceNoCarrierSerivce() {
+        startProcessWithNoGateWayInfo();
+        disableUserDefinedCallRedirectionService();
+        disableCarrierCallRedirectionService();
+        mProcessor.performCallRedirection();
+        verify(mContext, times(0)).bindServiceAsUser(any(Intent.class),
+                any(ServiceConnection.class), anyInt(), any(UserHandle.class));
+        verify(mCallsManager, times(1)).onCallRedirectionComplete(eq(mCall), eq(mHandle),
+                eq(mPhoneAccountHandle), eq(null), eq(SPEAKER_PHONE_ON), eq(VIDEO_STATE),
+                eq(false), eq(CallRedirectionProcessor.UI_TYPE_NO_ACTION));
+    }
+
+    @Test
+    public void testCarrierServiceTimeoutNoUserDefinedService() throws Exception {
+        startProcessWithNoGateWayInfo();
+        // To make sure tests are not flaky, clean all the previous handler messages
+        waitForHandlerAction(mProcessor.getHandler(), HANDLER_TIMEOUT_DELAY);
+        disableUserDefinedCallRedirectionService();
+        enableCarrierCallRedirectionService();
+        mProcessor.performCallRedirection();
+        verify(mContext, times(1)).bindServiceAsUser(any(Intent.class),
+                any(ServiceConnection.class), anyInt(), any(UserHandle.class));
+        verify(mCallsManager, times(0)).onCallRedirectionComplete(eq(mCall), any(),
+                eq(mPhoneAccountHandle), eq(null), eq(SPEAKER_PHONE_ON), eq(VIDEO_STATE),
+                eq(false), eq(CallRedirectionProcessor.UI_TYPE_NO_ACTION));
+        waitForHandlerActionDelayed(mProcessor.getHandler(), HANDLER_TIMEOUT_DELAY,
+                CARRIER_SHORT_TIMEOUT_MS + CODE_EXECUTION_DELAY);
+        verify(mCallsManager, times(1)).onCallRedirectionComplete(eq(mCall), any(),
+                eq(mPhoneAccountHandle), eq(null), eq(SPEAKER_PHONE_ON), eq(VIDEO_STATE),
+                eq(false), eq(CallRedirectionProcessor.UI_TYPE_NO_ACTION));
+    }
+
+    @Test
+    public void testUserDefinedServiceTimeoutNoCarrierService() throws Exception {
+        startProcessWithNoGateWayInfo();
+        // To make sure tests are not flaky, clean all the previous handler messages
+        waitForHandlerAction(mProcessor.getHandler(), HANDLER_TIMEOUT_DELAY);
+        enableUserDefinedCallRedirectionService();
+        disableCarrierCallRedirectionService();
+        mProcessor.performCallRedirection();
+        verify(mContext, times(1)).bindServiceAsUser(any(Intent.class),
+                any(ServiceConnection.class), anyInt(), any(UserHandle.class));
+        verify(mCallsManager, times(0)).onCallRedirectionComplete(eq(mCall), any(),
+                eq(mPhoneAccountHandle), eq(null), eq(SPEAKER_PHONE_ON), eq(VIDEO_STATE),
+                eq(false), eq(CallRedirectionProcessor.UI_TYPE_USER_DEFINED_TIMEOUT));
+
+        // Test it is waiting for a User-defined timeout, not a Carrier timeout
+        Thread.sleep(CARRIER_SHORT_TIMEOUT_MS + CODE_EXECUTION_DELAY);
+        verify(mCallsManager, times(0)).onCallRedirectionComplete(eq(mCall), any(),
+                eq(mPhoneAccountHandle), eq(null), eq(SPEAKER_PHONE_ON), eq(VIDEO_STATE),
+                eq(false), eq(CallRedirectionProcessor.UI_TYPE_USER_DEFINED_TIMEOUT));
+
+        // Wait for the rest of user-defined timeout time.
+        waitForHandlerActionDelayed(mProcessor.getHandler(), HANDLER_TIMEOUT_DELAY,
+                USER_DEFINED_SHORT_TIMEOUT_MS - CARRIER_SHORT_TIMEOUT_MS + CODE_EXECUTION_DELAY);
+        verify(mCallsManager, times(1)).onCallRedirectionComplete(eq(mCall), any(),
+                eq(mPhoneAccountHandle), eq(null), eq(SPEAKER_PHONE_ON), eq(VIDEO_STATE),
+                eq(true), eq(CallRedirectionProcessor.UI_TYPE_USER_DEFINED_TIMEOUT));
+    }
+
+    @Test
+    public void testUserDefinedServiceTimeoutAndCarrierServiceTimeout() throws Exception {
+        startProcessWithNoGateWayInfo();
+        // To make sure tests are not flaky, clean all the previous handler messages
+        waitForHandlerAction(mProcessor.getHandler(), HANDLER_TIMEOUT_DELAY);
+        enableUserDefinedCallRedirectionService();
+        enableCarrierCallRedirectionService();
+        mProcessor.performCallRedirection();
+
+        verify(mContext, times(1)).bindServiceAsUser(any(Intent.class),
+                any(ServiceConnection.class), anyInt(), any(UserHandle.class));
+        verify(mCallsManager, times(0)).onCallRedirectionComplete(eq(mCall), any(),
+                eq(mPhoneAccountHandle), eq(null), eq(SPEAKER_PHONE_ON), eq(VIDEO_STATE),
+                eq(false), eq(CallRedirectionProcessor.UI_TYPE_USER_DEFINED_TIMEOUT));
+
+        // Test it is waiting for a User-defined timeout, not a Carrier timeout
+        Thread.sleep(CARRIER_SHORT_TIMEOUT_MS + CODE_EXECUTION_DELAY);
+        verify(mContext, times(1)).bindServiceAsUser(any(Intent.class),
+                any(ServiceConnection.class), anyInt(), any(UserHandle.class));
+        verify(mCallsManager, times(0)).onCallRedirectionComplete(eq(mCall), any(),
+                eq(mPhoneAccountHandle), eq(null), eq(SPEAKER_PHONE_ON), eq(VIDEO_STATE),
+                eq(false), eq(CallRedirectionProcessor.UI_TYPE_USER_DEFINED_TIMEOUT));
+
+        // Wait for the rest of user-defined timeout time.
+        waitForHandlerActionDelayed(mProcessor.getHandler(), HANDLER_TIMEOUT_DELAY,
+                USER_DEFINED_SHORT_TIMEOUT_MS - CARRIER_SHORT_TIMEOUT_MS + CODE_EXECUTION_DELAY);
+        verify(mCallsManager, times(1)).onCallRedirectionComplete(eq(mCall), any(),
+                eq(mPhoneAccountHandle), eq(null), eq(SPEAKER_PHONE_ON), eq(VIDEO_STATE),
+                eq(true), eq(CallRedirectionProcessor.UI_TYPE_USER_DEFINED_TIMEOUT));
+
+        // Wait for another carrier timeout time, but should not expect any carrier service request
+        // is triggered.
+        Thread.sleep(CARRIER_SHORT_TIMEOUT_MS + CODE_EXECUTION_DELAY);
+        verify(mContext, times(1)).bindServiceAsUser(any(Intent.class),
+                any(ServiceConnection.class), anyInt(), any(UserHandle.class));
+        verify(mCallsManager, times(1)).onCallRedirectionComplete(eq(mCall), any(),
+                eq(mPhoneAccountHandle), eq(null), eq(SPEAKER_PHONE_ON), eq(VIDEO_STATE),
+                eq(true), eq(CallRedirectionProcessor.UI_TYPE_USER_DEFINED_TIMEOUT));
+    }
+
+    @Test
+    public void testProcessGatewayCall() {
+        startProcessWithGateWayInfo();
+        enableUserDefinedCallRedirectionService();
+        enableCarrierCallRedirectionService();
+        mProcessor.performCallRedirection();
+        verify(mContext, times(0)).bindServiceAsUser(any(Intent.class),
+                any(ServiceConnection.class), anyInt(), any(UserHandle.class));
+        verify(mCallsManager, times(1)).onCallRedirectionComplete(eq(mCall), eq(mHandle),
+                eq(mPhoneAccountHandle), eq(mGatewayInfo), eq(SPEAKER_PHONE_ON), eq(VIDEO_STATE),
+                eq(false), eq(CallRedirectionProcessor.UI_TYPE_NO_ACTION));
+    }
+}
diff --git a/tests/src/com/android/server/telecom/tests/CallScreeningServiceControllerTest.java b/tests/src/com/android/server/telecom/tests/CallScreeningServiceControllerTest.java
new file mode 100644
index 0000000..1a01a95
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/CallScreeningServiceControllerTest.java
@@ -0,0 +1,373 @@
+/*
+ * Copyright (C) 2018 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.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.net.Uri;
+import android.os.PersistableBundle;
+import android.os.UserHandle;
+import android.provider.CallLog;
+import android.telecom.TelecomManager;
+import android.telephony.CarrierConfigManager;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.internal.telephony.CallerInfo;
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallScreeningServiceHelper;
+import com.android.server.telecom.CallerInfoLookupHelper;
+import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.ParcelableCallUtils;
+import com.android.server.telecom.PhoneAccountRegistrar;
+import com.android.server.telecom.RoleManagerAdapter;
+import com.android.server.telecom.TelecomServiceImpl;
+import com.android.server.telecom.TelecomSystem;
+import com.android.server.telecom.callfiltering.CallFilterResultCallback;
+import com.android.server.telecom.callfiltering.CallFilteringResult;
+import com.android.server.telecom.callfiltering.CallScreeningServiceController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+
+import java.util.Collections;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(JUnit4.class)
+public class CallScreeningServiceControllerTest extends TelecomTestCase {
+
+    @Mock Context mContext;
+    @Mock Call mCall;
+    @Mock private CallFilterResultCallback mCallback;
+    @Mock CallsManager mCallsManager;
+    @Mock RoleManagerAdapter mRoleManagerAdapter;
+    @Mock CarrierConfigManager mCarrierConfigManager;
+    @Mock private TelecomManager mTelecomManager;
+    @Mock PackageManager mPackageManager;
+    @Mock ParcelableCallUtils.Converter mParcelableCallUtilsConverter;
+    @Mock PhoneAccountRegistrar mPhoneAccountRegistrar;
+    @Mock private CallerInfoLookupHelper mCallerInfoLookupHelper;
+
+    CallScreeningServiceHelper.AppLabelProxy mAppLabelProxy =
+            new CallScreeningServiceHelper.AppLabelProxy() {
+        @Override
+        public CharSequence getAppLabel(String packageName) {
+            return APP_NAME;
+        }
+    };
+
+    private ResolveInfo mResolveInfo;
+    private TelecomServiceImpl.SettingsSecureAdapter mSettingsSecureAdapter =
+            spy(new CallScreeningServiceFilterTest.SettingsSecureAdapterFake());
+    private TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() { };
+
+    private static final String CALL_ID = "u89prgt9ps78y5";
+    private static final Uri TEST_HANDLE = Uri.parse("tel:1235551234");
+    private static final String DEFAULT_DIALER_PACKAGE = "com.android.dialer";
+    private static final String PKG_NAME = "com.android.services.telecom.tests";
+    private static final String CLS_NAME = "CallScreeningService";
+    private static final String APP_NAME = "Screeny McScreenface";
+    private static final ComponentName CARRIER_DEFINED_CALL_SCREENING = new ComponentName(
+            "com.android.carrier", "com.android.carrier.callscreeningserviceimpl");
+    private static final ComponentName DEFAULT_DIALER_CALL_SCREENING = new ComponentName(
+            "com.android.dialer", "com.android.dialer.callscreeningserviceimpl");
+    private static final ComponentName USER_CHOSEN_CALL_SCREENING = new ComponentName(
+            "com.android.userchosen", "com.android.userchosen.callscreeningserviceimpl");
+
+    private static final CallFilteringResult PASS_RESULT = new CallFilteringResult(
+            true, // shouldAllowCall
+            false, // shouldReject
+            true, // shouldAddToCallLog
+            true // shouldShowNotification
+    );
+
+    public static class SettingsSecureAdapterFake implements
+            TelecomServiceImpl.SettingsSecureAdapter {
+        @Override
+        public void putStringForUser(ContentResolver resolver, String name, String value,
+                                     int userHandle) {
+
+        }
+
+        @Override
+        public String getStringForUser(ContentResolver resolver, String name, int userHandle) {
+            return USER_CHOSEN_CALL_SCREENING.flattenToString();
+        }
+    }
+
+    @Override
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        when(mRoleManagerAdapter.getCallCompanionApps()).thenReturn(Collections.emptyList());
+        when(mRoleManagerAdapter.getDefaultCallScreeningApp()).thenReturn(null);
+        when(mRoleManagerAdapter.getCarModeDialerApp()).thenReturn(null);
+        when(mCallsManager.getRoleManagerAdapter()).thenReturn(mRoleManagerAdapter);
+        when(mCallsManager.getCurrentUserHandle()).thenReturn(UserHandle.CURRENT);
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+        when(mCall.getId()).thenReturn(CALL_ID);
+
+        setCarrierDefinedCallScreeningApplication();
+        when(TelecomManager.from(mContext)).thenReturn(mTelecomManager);
+        when(mTelecomManager.getDefaultDialerPackage()).thenReturn(DEFAULT_DIALER_PACKAGE);
+
+        mResolveInfo =  new ResolveInfo() {{
+            serviceInfo = new ServiceInfo();
+            serviceInfo.packageName = PKG_NAME;
+            serviceInfo.name = CLS_NAME;
+            serviceInfo.permission = Manifest.permission.BIND_SCREENING_SERVICE;
+        }};
+
+        when(mPackageManager.queryIntentServicesAsUser(nullable(Intent.class), anyInt(), anyInt()))
+                .thenReturn(Collections.singletonList(mResolveInfo));
+        when(mParcelableCallUtilsConverter.toParcelableCall(
+                eq(mCall), anyBoolean(), eq(mPhoneAccountRegistrar))).thenReturn(null);
+        when(mContext.bindServiceAsUser(nullable(Intent.class), nullable(ServiceConnection.class),
+                anyInt(), eq(UserHandle.CURRENT))).thenReturn(true);
+        when(mCall.getHandle()).thenReturn(TEST_HANDLE);
+    }
+
+    @SmallTest
+    @Test
+    public void testAllAllowCall() {
+        when(mRoleManagerAdapter.getDefaultCallScreeningApp()).thenReturn(
+                USER_CHOSEN_CALL_SCREENING.getPackageName());
+        CallScreeningServiceController controller = new CallScreeningServiceController(mContext,
+                mCallsManager, mPhoneAccountRegistrar,
+                mParcelableCallUtilsConverter, mLock,
+                mSettingsSecureAdapter, mCallerInfoLookupHelper, mAppLabelProxy);
+
+        controller.startFilterLookup(mCall, mCallback);
+
+        controller.onCallScreeningFilterComplete(mCall, PASS_RESULT,
+                CARRIER_DEFINED_CALL_SCREENING.getPackageName());
+
+        CallerInfoLookupHelper.OnQueryCompleteListener queryListener = verifyLookupStart();
+        CallerInfo callerInfo = new CallerInfo();
+        callerInfo.contactExists = false;
+        queryListener.onCallerInfoQueryComplete(TEST_HANDLE, callerInfo);
+
+        controller.onCallScreeningFilterComplete(mCall, PASS_RESULT,
+                DEFAULT_DIALER_CALL_SCREENING.getPackageName());
+        controller.onCallScreeningFilterComplete(mCall, PASS_RESULT, USER_CHOSEN_CALL_SCREENING
+                .getPackageName());
+
+        verify(mContext, times(3)).bindServiceAsUser(any(Intent.class), any(ServiceConnection
+                        .class),
+                eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE),
+                eq(UserHandle.CURRENT));
+
+        verify(mCallback).onCallFilteringComplete(eq(mCall), eq(PASS_RESULT));
+    }
+
+    @SmallTest
+    @Test
+    public void testCarrierAllowCallAndContactExists() {
+        CallScreeningServiceController controller = new CallScreeningServiceController(mContext,
+                mCallsManager,
+                mPhoneAccountRegistrar, mParcelableCallUtilsConverter, mLock,
+                mSettingsSecureAdapter, mCallerInfoLookupHelper, mAppLabelProxy);
+
+        controller.startFilterLookup(mCall, mCallback);
+
+        controller.onCallScreeningFilterComplete(mCall, PASS_RESULT,
+                CARRIER_DEFINED_CALL_SCREENING.getPackageName());
+
+        CallerInfoLookupHelper.OnQueryCompleteListener queryListener = verifyLookupStart();
+        CallerInfo callerInfo = new CallerInfo();
+        callerInfo.contactExists = true;
+        queryListener.onCallerInfoQueryComplete(TEST_HANDLE, callerInfo);
+
+        verify(mContext, times(1)).bindServiceAsUser(any(Intent.class), any(ServiceConnection
+                        .class),
+                eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE),
+                eq(UserHandle.CURRENT));
+
+        verify(mCallback).onCallFilteringComplete(eq(mCall), eq(PASS_RESULT));
+    }
+
+    @SmallTest
+    @Test
+    public void testCarrierCallScreeningRejectCall() {
+        CallScreeningServiceController controller = new CallScreeningServiceController(mContext,
+                mCallsManager,
+                mPhoneAccountRegistrar, mParcelableCallUtilsConverter, mLock,
+                mSettingsSecureAdapter, mCallerInfoLookupHelper, mAppLabelProxy);
+
+        controller.startFilterLookup(mCall, mCallback);
+
+        controller.onCallScreeningFilterComplete(mCall, new CallFilteringResult(
+                false, // shouldAllowCall
+                true, // shouldReject
+                false, // shouldAddToCallLog
+                true, // shouldShowNotification
+                CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE,
+                APP_NAME,
+                CARRIER_DEFINED_CALL_SCREENING.flattenToString()
+        ), CARRIER_DEFINED_CALL_SCREENING.getPackageName());
+
+        verify(mContext, times(1)).bindServiceAsUser(any(Intent.class),
+                any(ServiceConnection.class),
+                eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE),
+                eq(UserHandle.CURRENT));
+
+        verify(mCallback).onCallFilteringComplete(eq(mCall), eq(new CallFilteringResult(
+                false, // shouldAllowCall
+                true, // shouldReject
+                false, // shouldAddToCallLog
+                true, // shouldShowNotification
+                CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE, //callBlockReason
+                APP_NAME, //callScreeningAppName
+                CARRIER_DEFINED_CALL_SCREENING.flattenToString() //callScreeningComponentName
+        )));
+    }
+
+    @SmallTest
+    @Test
+    public void testDefaultDialerRejectCall() {
+        when(mRoleManagerAdapter.getDefaultCallScreeningApp()).thenReturn(
+                USER_CHOSEN_CALL_SCREENING.getPackageName());
+        CallScreeningServiceController controller = new CallScreeningServiceController(mContext,
+                mCallsManager,
+                mPhoneAccountRegistrar, mParcelableCallUtilsConverter, mLock,
+                mSettingsSecureAdapter, mCallerInfoLookupHelper, mAppLabelProxy);
+
+        controller.startFilterLookup(mCall, mCallback);
+
+        controller.onCallScreeningFilterComplete(mCall, PASS_RESULT,
+                CARRIER_DEFINED_CALL_SCREENING.getPackageName());
+
+        CallerInfoLookupHelper.OnQueryCompleteListener queryListener = verifyLookupStart();
+        CallerInfo callerInfo = new CallerInfo();
+        callerInfo.contactExists = false;
+        queryListener.onCallerInfoQueryComplete(TEST_HANDLE, callerInfo);
+
+        controller.onCallScreeningFilterComplete(mCall, new CallFilteringResult(
+                false, // shouldAllowCall
+                true, // shouldReject
+                false, // shouldAddToCallLog
+                true, // shouldShowNotification
+                CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE,
+                APP_NAME,
+                DEFAULT_DIALER_CALL_SCREENING.flattenToString()
+        ), DEFAULT_DIALER_CALL_SCREENING.getPackageName());
+
+        verify(mContext, times(3)).bindServiceAsUser(any(Intent.class),
+                any(ServiceConnection.class),
+                eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE),
+                eq(UserHandle.CURRENT));
+
+        verify(mCallback).onCallFilteringComplete(eq(mCall), eq(new CallFilteringResult(
+                false, // shouldAllowCall
+                true, // shouldReject
+                true, // shouldAddToCallLog (we don't allow services to skip call log)
+                true, // shouldShowNotification
+                CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE, //callBlockReason
+                APP_NAME, //callScreeningAppName
+                DEFAULT_DIALER_CALL_SCREENING.flattenToString() //callScreeningComponentName
+        )));
+    }
+
+    @SmallTest
+    @Test
+    public void testUserChosenRejectCall() {
+        when(mRoleManagerAdapter.getDefaultCallScreeningApp()).thenReturn(
+                USER_CHOSEN_CALL_SCREENING.getPackageName());
+        CallScreeningServiceController controller = new CallScreeningServiceController(mContext,
+                mCallsManager,
+                mPhoneAccountRegistrar, mParcelableCallUtilsConverter, mLock,
+                mSettingsSecureAdapter, mCallerInfoLookupHelper, mAppLabelProxy);
+
+        controller.startFilterLookup(mCall, mCallback);
+
+        controller.onCallScreeningFilterComplete(mCall, PASS_RESULT,
+                CARRIER_DEFINED_CALL_SCREENING.getPackageName());
+
+        CallerInfoLookupHelper.OnQueryCompleteListener queryListener = verifyLookupStart();
+        CallerInfo callerInfo = new CallerInfo();
+        callerInfo.contactExists = false;
+        queryListener.onCallerInfoQueryComplete(TEST_HANDLE, callerInfo);
+
+        controller.onCallScreeningFilterComplete(mCall, PASS_RESULT,
+                DEFAULT_DIALER_CALL_SCREENING.getPackageName());
+        controller.onCallScreeningFilterComplete(mCall, new CallFilteringResult(
+                false, // shouldAllowCall
+                true, // shouldReject
+                false, // shouldAddToCallLog
+                true, // shouldShowNotification
+                CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE,
+                APP_NAME,
+                USER_CHOSEN_CALL_SCREENING.flattenToString()
+        ), USER_CHOSEN_CALL_SCREENING.getPackageName());
+
+        verify(mContext, times(3)).bindServiceAsUser(any(Intent.class),
+                any(ServiceConnection.class),
+                eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE),
+                eq(UserHandle.CURRENT));
+
+        verify(mCallback).onCallFilteringComplete(eq(mCall), eq(new CallFilteringResult(
+                false, // shouldAllowCall
+                true, // shouldReject
+                true, // shouldAddToCallLog (we don't allow services to skip call log)
+                true, // shouldShowNotification
+                CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE, //callBlockReason
+                APP_NAME, //callScreeningAppName
+                USER_CHOSEN_CALL_SCREENING.flattenToString() //callScreeningComponentName
+        )));
+    }
+
+    private CallerInfoLookupHelper.OnQueryCompleteListener verifyLookupStart() {
+        return verifyLookupStart(TEST_HANDLE);
+    }
+
+    private CallerInfoLookupHelper.OnQueryCompleteListener verifyLookupStart(Uri handle) {
+
+        ArgumentCaptor<CallerInfoLookupHelper.OnQueryCompleteListener> captor =
+                ArgumentCaptor.forClass(CallerInfoLookupHelper.OnQueryCompleteListener.class);
+        verify(mCallerInfoLookupHelper).startLookup(eq(handle), captor.capture());
+        return captor.getValue();
+    }
+
+    private void setCarrierDefinedCallScreeningApplication() {
+        String carrierDefined = CARRIER_DEFINED_CALL_SCREENING.flattenToString();
+        PersistableBundle bundle = new PersistableBundle();
+        bundle.putString(CarrierConfigManager.KEY_CARRIER_CALL_SCREENING_APP_STRING,
+                carrierDefined);
+        when(mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE))
+                .thenReturn(mCarrierConfigManager);
+        when(mCarrierConfigManager.getConfig()).thenReturn(bundle);
+    }
+}
diff --git a/tests/src/com/android/server/telecom/tests/CallScreeningServiceFilterTest.java b/tests/src/com/android/server/telecom/tests/CallScreeningServiceFilterTest.java
index a319fad..2b65e07 100644
--- a/tests/src/com/android/server/telecom/tests/CallScreeningServiceFilterTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallScreeningServiceFilterTest.java
@@ -18,27 +18,33 @@
 
 import android.Manifest;
 import android.content.ComponentName;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
+import android.graphics.drawable.Icon;
 import android.os.IBinder;
+import android.os.PersistableBundle;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.provider.CallLog;
+import android.telecom.CallIdentification;
 import android.telecom.CallScreeningService;
 import android.telecom.ParcelableCall;
+import android.telecom.TelecomManager;
+import android.telephony.CarrierConfigManager;
 import android.test.suitebuilder.annotation.SmallTest;
 
 import com.android.internal.telecom.ICallScreeningAdapter;
 import com.android.internal.telecom.ICallScreeningService;
 import com.android.server.telecom.Call;
 import com.android.server.telecom.CallsManager;
-import com.android.server.telecom.DefaultDialerCache;
 import com.android.server.telecom.ParcelableCallUtils;
 import com.android.server.telecom.PhoneAccountRegistrar;
-import com.android.server.telecom.callfiltering.CallFilterResultCallback;
+import com.android.server.telecom.TelecomServiceImpl;
 import com.android.server.telecom.callfiltering.CallFilteringResult;
 import com.android.server.telecom.callfiltering.CallScreeningServiceFilter;
 import com.android.server.telecom.TelecomSystem;
@@ -60,6 +66,7 @@
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -68,22 +75,35 @@
     @Mock Context mContext;
     @Mock CallsManager mCallsManager;
     @Mock PhoneAccountRegistrar mPhoneAccountRegistrar;
-    @Mock DefaultDialerCache mDefaultDialerCache;
     @Mock ParcelableCallUtils.Converter mParcelableCallUtilsConverter;
     private TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() { };
 
     @Mock Call mCall;
-    @Mock CallFilterResultCallback mCallback;
+    @Mock CallScreeningServiceFilter.CallScreeningFilterResultCallback mCallback;
 
     @Mock PackageManager mPackageManager;
     @Mock IBinder mBinder;
     @Mock ICallScreeningService mCallScreeningService;
+    @Mock CarrierConfigManager mCarrierConfigManager;
+    @Mock private TelecomManager mTelecomManager;
+    private TelecomServiceImpl.SettingsSecureAdapter mSettingsSecureAdapter =
+        spy(new SettingsSecureAdapterFake());
 
     private static final String PKG_NAME = "com.android.services.telecom.tests";
+    private static final String APP_NAME = "TeleTestApp";
     private static final String CLS_NAME = "CallScreeningService";
     private static final ComponentName COMPONENT_NAME = new ComponentName(PKG_NAME, CLS_NAME);
     private static final String CALL_ID = "u89prgt9ps78y5";
-
+    private static final String DEFAULT_DIALER_PACKAGE = "com.android.dialer";
+    private static final ComponentName CARRIER_DEFINED_CALL_SCREENING = new ComponentName(
+        "com.android.carrier", "com.android.carrier.callscreeningserviceimpl");
+    private static final String CARRIER_DEFINED_CALL_SCREENING_APP_NAME = "GMob";
+    private static final ComponentName DEFAULT_DIALER_CALL_SCREENING = new ComponentName(
+        "com.android.dialer", "com.android.dialer.callscreeningserviceimpl");
+    private static final String DEFAULT_DIALER_APP_NAME = "Dialer";
+    private static final ComponentName USER_CHOSEN_CALL_SCREENING = new ComponentName(
+        "com.android.userchosen", "com.android.userchosen.callscreeningserviceimpl");
+    private static final String USER_CHOSEN_CALL_SCREENING_APP_NAME = "UserChosen";
     private ResolveInfo mResolveInfo;
 
     private static final CallFilteringResult PASS_RESULT = new CallFilteringResult(
@@ -95,6 +115,20 @@
 
     private CallScreeningServiceFilter mFilter;
 
+    public static class SettingsSecureAdapterFake implements
+        TelecomServiceImpl.SettingsSecureAdapter {
+        @Override
+        public void putStringForUser(ContentResolver resolver, String name, String value,
+            int userHandle) {
+
+        }
+
+        @Override
+        public String getStringForUser(ContentResolver resolver, String name, int userHandle) {
+            return USER_CHOSEN_CALL_SCREENING.flattenToString();
+        }
+    }
+
     @Override
     @Before
     public void setUp() throws Exception {
@@ -112,10 +146,8 @@
         }};
 
         mFilter = new CallScreeningServiceFilter(mContext, mCallsManager, mPhoneAccountRegistrar,
-                mDefaultDialerCache, mParcelableCallUtilsConverter, mLock);
+                mParcelableCallUtilsConverter, mLock, mSettingsSecureAdapter);
 
-        when(mDefaultDialerCache.getDefaultDialerApplication(eq(UserHandle.USER_CURRENT)))
-                .thenReturn(PKG_NAME);
         when(mPackageManager.queryIntentServicesAsUser(nullable(Intent.class), anyInt(), anyInt()))
                 .thenReturn(Collections.singletonList(mResolveInfo));
         when(mParcelableCallUtilsConverter.toParcelableCall(
@@ -126,11 +158,9 @@
 
     @SmallTest
     @Test
-    public void testNoDefaultDialer() {
-        when(mDefaultDialerCache.getDefaultDialerApplication(eq(UserHandle.USER_CURRENT)))
-                .thenReturn(null);
-        mFilter.startFilterLookup(mCall, mCallback);
-        verify(mCallback).onCallFilteringComplete(eq(mCall), eq(PASS_RESULT));
+    public void testNoPackageName() {
+        mFilter.startCallScreeningFilter(mCall, mCallback, null, null);
+        verify(mCallback).onCallScreeningFilterComplete(eq(mCall), eq(PASS_RESULT), eq(null));
     }
 
     @SmallTest
@@ -138,24 +168,24 @@
     public void testNoResolveEntries() {
         when(mPackageManager.queryIntentServicesAsUser(nullable(Intent.class), anyInt(), anyInt()))
                 .thenReturn(Collections.emptyList());
-        mFilter.startFilterLookup(mCall, mCallback);
-        verify(mCallback).onCallFilteringComplete(eq(mCall), eq(PASS_RESULT));
+        mFilter.startCallScreeningFilter(mCall, mCallback, PKG_NAME, APP_NAME);
+        verify(mCallback).onCallScreeningFilterComplete(eq(mCall), eq(PASS_RESULT), eq(PKG_NAME));
     }
 
     @SmallTest
     @Test
     public void testBadResolveEntry() {
         mResolveInfo.serviceInfo = null;
-        mFilter.startFilterLookup(mCall, mCallback);
-        verify(mCallback).onCallFilteringComplete(eq(mCall), eq(PASS_RESULT));
+        mFilter.startCallScreeningFilter(mCall, mCallback, PKG_NAME, APP_NAME);
+        verify(mCallback).onCallScreeningFilterComplete(eq(mCall), eq(PASS_RESULT), eq(PKG_NAME));
     }
 
     @SmallTest
     @Test
     public void testPermissionlessFilterService() {
         mResolveInfo.serviceInfo.permission = null;
-        mFilter.startFilterLookup(mCall, mCallback);
-        verify(mCallback).onCallFilteringComplete(eq(mCall), eq(PASS_RESULT));
+        mFilter.startCallScreeningFilter(mCall, mCallback, PKG_NAME, APP_NAME);
+        verify(mCallback).onCallScreeningFilterComplete(eq(mCall), eq(PASS_RESULT), eq(PKG_NAME));
     }
 
     @SmallTest
@@ -163,8 +193,8 @@
     public void testContextFailToBind() {
         when(mContext.bindServiceAsUser(nullable(Intent.class), nullable(ServiceConnection.class),
                 anyInt(), eq(UserHandle.CURRENT))).thenReturn(false);
-        mFilter.startFilterLookup(mCall, mCallback);
-        verify(mCallback).onCallFilteringComplete(eq(mCall), eq(PASS_RESULT));
+        mFilter.startCallScreeningFilter(mCall, mCallback, PKG_NAME, APP_NAME);
+        verify(mCallback).onCallScreeningFilterComplete(eq(mCall), eq(PASS_RESULT), eq(PKG_NAME));
     }
 
     @SmallTest
@@ -172,41 +202,148 @@
     public void testExceptionInScreeningService() throws Exception {
         doThrow(new RemoteException()).when(mCallScreeningService).screenCall(
                 nullable(ICallScreeningAdapter.class), nullable(ParcelableCall.class));
-        mFilter.startFilterLookup(mCall, mCallback);
+        mFilter.startCallScreeningFilter(mCall, mCallback, PKG_NAME, APP_NAME);
         ServiceConnection serviceConnection = verifyBindingIntent();
         serviceConnection.onServiceConnected(COMPONENT_NAME, mBinder);
-        verify(mCallback).onCallFilteringComplete(eq(mCall), eq(PASS_RESULT));
+        verify(mCallback).onCallScreeningFilterComplete(eq(mCall), eq(PASS_RESULT), eq(PKG_NAME));
     }
 
     @SmallTest
     @Test
     public void testAllowCall() throws Exception {
-        mFilter.startFilterLookup(mCall, mCallback);
+        mFilter.startCallScreeningFilter(mCall, mCallback, PKG_NAME, APP_NAME);
         ServiceConnection serviceConnection = verifyBindingIntent();
         serviceConnection.onServiceConnected(COMPONENT_NAME, mBinder);
         ICallScreeningAdapter csAdapter = getCallScreeningAdapter();
         csAdapter.allowCall(CALL_ID);
-        verify(mCallback).onCallFilteringComplete(eq(mCall), eq(PASS_RESULT));
+        verify(mCallback).onCallScreeningFilterComplete(eq(mCall), eq(PASS_RESULT), eq(PKG_NAME));
     }
 
     @SmallTest
     @Test
-    public void testDisallowCall() throws Exception {
-        mFilter.startFilterLookup(mCall, mCallback);
+    public void testDisallowCallForCarrierDefined() throws Exception {
+        mResolveInfo.serviceInfo.packageName = CARRIER_DEFINED_CALL_SCREENING.getPackageName();
+        mResolveInfo.serviceInfo.name = CARRIER_DEFINED_CALL_SCREENING.getClassName();
+        setCarrierDefinedCallScreeningApplication();
+        when(TelecomManager.from(mContext)).thenReturn(mTelecomManager);
+        when(mTelecomManager.getDefaultDialerPackage()).thenReturn(DEFAULT_DIALER_PACKAGE);
+
+        mFilter.startCallScreeningFilter(mCall, mCallback,
+                CARRIER_DEFINED_CALL_SCREENING.getPackageName(),
+                CARRIER_DEFINED_CALL_SCREENING_APP_NAME);
         ServiceConnection serviceConnection = verifyBindingIntent();
         serviceConnection.onServiceConnected(COMPONENT_NAME, mBinder);
         ICallScreeningAdapter csAdapter = getCallScreeningAdapter();
         csAdapter.disallowCall(CALL_ID,
                 true, // shouldReject
                 false, // shouldAddToCallLog
-                true // shouldShowNotification
+                true, // shouldShowNotification
+                CARRIER_DEFINED_CALL_SCREENING
         );
-        verify(mCallback).onCallFilteringComplete(eq(mCall), eq(new CallFilteringResult(
+        verify(mCallback).onCallScreeningFilterComplete(eq(mCall), eq(new CallFilteringResult(
                 false, // shouldAllowCall
                 true, // shouldReject
                 false, // shouldAddToCallLog
-                true // shouldShowNotification
-        )));
+                true, // shouldShowNotification
+                CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE, //callBlockReason
+                CARRIER_DEFINED_CALL_SCREENING_APP_NAME, //callScreeningAppName
+                CARRIER_DEFINED_CALL_SCREENING.flattenToString() //callScreeningComponentName
+        )), eq(CARRIER_DEFINED_CALL_SCREENING.getPackageName()));
+    }
+
+    @SmallTest
+    @Test
+    public void testDisallowCallForDefaultDialer() throws Exception {
+        mResolveInfo.serviceInfo.packageName = DEFAULT_DIALER_CALL_SCREENING.getPackageName();
+        mResolveInfo.serviceInfo.name = DEFAULT_DIALER_CALL_SCREENING.getClassName();
+        setCarrierDefinedCallScreeningApplication();
+        when(TelecomManager.from(mContext)).thenReturn(mTelecomManager);
+        when(mTelecomManager.getDefaultDialerPackage()).thenReturn(DEFAULT_DIALER_PACKAGE);
+
+        mFilter.startCallScreeningFilter(mCall, mCallback,
+                DEFAULT_DIALER_CALL_SCREENING.getPackageName(),
+                DEFAULT_DIALER_APP_NAME);
+        ServiceConnection serviceConnection = verifyBindingIntent();
+        serviceConnection.onServiceConnected(COMPONENT_NAME, mBinder);
+        ICallScreeningAdapter csAdapter = getCallScreeningAdapter();
+        csAdapter.disallowCall(CALL_ID,
+            true, // shouldReject
+            false, // shouldAddToCallLog
+            true, // shouldShowNotification
+            DEFAULT_DIALER_CALL_SCREENING
+        );
+        verify(mCallback).onCallScreeningFilterComplete(eq(mCall), eq(new CallFilteringResult(
+            false, // shouldAllowCall
+            true, // shouldReject
+            true, // shouldAddToCallLog
+            true, // shouldShowNotification
+            CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE, //callBlockReason
+            DEFAULT_DIALER_APP_NAME, //callScreeningAppName
+            DEFAULT_DIALER_CALL_SCREENING.flattenToString() //callScreeningComponentName
+        )), eq(DEFAULT_DIALER_CALL_SCREENING.getPackageName()));
+    }
+
+    @SmallTest
+    @Test
+    public void testDisallowCallForUserChosen() throws Exception {
+        mResolveInfo.serviceInfo.packageName = USER_CHOSEN_CALL_SCREENING.getPackageName();
+        mResolveInfo.serviceInfo.name = USER_CHOSEN_CALL_SCREENING.getClassName();
+        setCarrierDefinedCallScreeningApplication();
+        when(TelecomManager.from(mContext)).thenReturn(mTelecomManager);
+        when(mTelecomManager.getDefaultDialerPackage()).thenReturn(DEFAULT_DIALER_PACKAGE);
+
+        mFilter.startCallScreeningFilter(mCall, mCallback,
+                USER_CHOSEN_CALL_SCREENING.getPackageName(),
+                USER_CHOSEN_CALL_SCREENING_APP_NAME);
+        ServiceConnection serviceConnection = verifyBindingIntent();
+        serviceConnection.onServiceConnected(COMPONENT_NAME, mBinder);
+        ICallScreeningAdapter csAdapter = getCallScreeningAdapter();
+        csAdapter.disallowCall(CALL_ID,
+            true, // shouldReject
+            false, // shouldAddToCallLog
+            true, // shouldShowNotification
+            USER_CHOSEN_CALL_SCREENING
+        );
+        verify(mCallback).onCallScreeningFilterComplete(eq(mCall), eq(new CallFilteringResult(
+            false, // shouldAllowCall
+            true, // shouldReject
+            true, // shouldAddToCallLog
+            true, // shouldShowNotification
+            CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE, //callBlockReason
+            USER_CHOSEN_CALL_SCREENING_APP_NAME, //callScreeningAppName
+            USER_CHOSEN_CALL_SCREENING.flattenToString() //callScreeningComponentName
+        )), eq(USER_CHOSEN_CALL_SCREENING.getPackageName()));
+    }
+
+    /**
+     * Verify that call identification information provided via a {@link CallScreeningService} is
+     * propagated to the Telecom call.
+     * @throws Exception
+     */
+    @SmallTest
+    @Test
+    public void testProvideCallIdentification() throws Exception {
+        mResolveInfo.serviceInfo.packageName = USER_CHOSEN_CALL_SCREENING.getPackageName();
+        mResolveInfo.serviceInfo.name = USER_CHOSEN_CALL_SCREENING.getClassName();
+        when(TelecomManager.from(mContext)).thenReturn(mTelecomManager);
+        when(mTelecomManager.getDefaultDialerPackage()).thenReturn(DEFAULT_DIALER_PACKAGE);
+
+        mFilter.startCallScreeningFilter(mCall, mCallback,
+                USER_CHOSEN_CALL_SCREENING.getPackageName(),
+                USER_CHOSEN_CALL_SCREENING_APP_NAME);
+        ServiceConnection serviceConnection = verifyBindingIntent();
+        serviceConnection.onServiceConnected(COMPONENT_NAME, mBinder);
+        ICallScreeningAdapter csAdapter = getCallScreeningAdapter();
+
+        CallIdentification callIdentification = new CallIdentification.Builder()
+                .setNuisanceConfidence(CallIdentification.CONFIDENCE_NOT_NUISANCE)
+                .setName("Joe's Laundry")
+                .setDescription("1234 DirtySocks Lane")
+                .setDetails("Open 24 hrs")
+                .build();
+        csAdapter.provideCallIdentification(CALL_ID, callIdentification);
+
+        verify(mCall).setCallIdentification(eq(callIdentification));
     }
 
     private ServiceConnection verifyBindingIntent() {
@@ -219,8 +356,9 @@
 
         Intent capturedIntent = intentCaptor.getValue();
         assertEquals(CallScreeningService.SERVICE_INTERFACE, capturedIntent.getAction());
-        assertEquals(PKG_NAME, capturedIntent.getPackage());
-        assertEquals(COMPONENT_NAME, capturedIntent.getComponent());
+        assertEquals(mResolveInfo.serviceInfo.packageName, capturedIntent.getPackage());
+        assertEquals(new ComponentName(mResolveInfo.serviceInfo.packageName, mResolveInfo
+                .serviceInfo.name), capturedIntent.getComponent());
 
         return serviceCaptor.getValue();
     }
@@ -231,4 +369,14 @@
         verify(mCallScreeningService).screenCall(captor.capture(), nullable(ParcelableCall.class));
         return captor.getValue();
     }
+
+    private void setCarrierDefinedCallScreeningApplication() {
+        String carrierDefined = CARRIER_DEFINED_CALL_SCREENING.flattenToString();
+        PersistableBundle bundle = new PersistableBundle();
+        bundle.putString(CarrierConfigManager.KEY_CARRIER_CALL_SCREENING_APP_STRING,
+            carrierDefined);
+        when(mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE))
+            .thenReturn(mCarrierConfigManager);
+        when(mCarrierConfigManager.getConfig()).thenReturn(bundle);
+    }
 }
diff --git a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
index ed2f6b1..bf8604a 100644
--- a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
@@ -16,13 +16,18 @@
 
 package com.android.server.telecom.tests;
 
+import static junit.framework.TestCase.fail;
+
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyChar;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
@@ -31,20 +36,28 @@
 
 import android.content.ComponentName;
 import android.net.Uri;
+import android.os.Bundle;
+import android.os.Process;
 import android.os.SystemClock;
 import android.telecom.Connection;
+import android.telecom.Log;
 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.MediumTest;
-
 import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.internal.telephony.CallerInfo;
 import com.android.server.telecom.AsyncRingtonePlayer;
 import com.android.server.telecom.Call;
 import com.android.server.telecom.CallAudioManager;
+import com.android.server.telecom.CallAudioModeStateMachine;
+import com.android.server.telecom.CallAudioRouteStateMachine;
 import com.android.server.telecom.CallState;
 import com.android.server.telecom.CallerInfoAsyncQueryFactory;
+import com.android.server.telecom.CallerInfoLookupHelper;
 import com.android.server.telecom.CallsManager;
 import com.android.server.telecom.ClockProxy;
 import com.android.server.telecom.ConnectionServiceFocusManager;
@@ -65,7 +78,8 @@
 import com.android.server.telecom.PhoneNumberUtilsAdapter;
 import com.android.server.telecom.ProximitySensorManager;
 import com.android.server.telecom.ProximitySensorManagerFactory;
-import com.android.server.telecom.SystemStateProvider;
+import com.android.server.telecom.RoleManagerAdapter;
+import com.android.server.telecom.SystemStateHelper;
 import com.android.server.telecom.TelecomSystem;
 import com.android.server.telecom.Timeouts;
 import com.android.server.telecom.WiredHeadsetManager;
@@ -81,14 +95,21 @@
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
 
 @RunWith(JUnit4.class)
 public class CallsManagerTest extends TelecomTestCase {
+    private static final int TEST_TIMEOUT = 5000;  // milliseconds
     private static final PhoneAccountHandle SIM_1_HANDLE = new PhoneAccountHandle(
             ComponentName.unflattenFromString("com.foo/.Blah"), "Sim1");
     private static final PhoneAccountHandle SIM_2_HANDLE = new PhoneAccountHandle(
@@ -112,10 +133,16 @@
             .setIsEnabled(true)
             .build();
     private static final Uri TEST_ADDRESS = Uri.parse("tel:555-1212");
+    private static final Uri TEST_ADDRESS2 = Uri.parse("tel:555-1213");
+    private static final Uri TEST_ADDRESS3 = Uri.parse("tel:555-1214");
+    private static final Map<Uri, PhoneAccountHandle> CONTACT_PREFERRED_ACCOUNT =
+            new HashMap<Uri, PhoneAccountHandle>() {{
+                put(TEST_ADDRESS2, SIM_1_HANDLE);
+                put(TEST_ADDRESS3, SIM_2_HANDLE);
+    }};
 
     private final TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() { };
-    @Mock private ContactsAsyncHelper mContactsAsyncHelper;
-    @Mock private CallerInfoAsyncQueryFactory mCallerInfoAsyncQueryFactory;
+    @Mock private CallerInfoLookupHelper mCallerInfoLookupHelper;
     @Mock private MissedCallNotifier mMissedCallNotifier;
     @Mock private PhoneAccountRegistrar mPhoneAccountRegistrar;
     @Mock private HeadsetMediaButton mHeadsetMediaButton;
@@ -128,7 +155,7 @@
     @Mock private CallAudioManager.AudioServiceFactory mAudioServiceFactory;
     @Mock private BluetoothRouteManager mBluetoothRouteManager;
     @Mock private WiredHeadsetManager mWiredHeadsetManager;
-    @Mock private SystemStateProvider mSystemStateProvider;
+    @Mock private SystemStateHelper mSystemStateHelper;
     @Mock private DefaultDialerCache mDefaultDialerCache;
     @Mock private Timeouts.Adapter mTimeoutsAdapter;
     @Mock private AsyncRingtonePlayer mAsyncRingtonePlayer;
@@ -139,7 +166,12 @@
     @Mock private InCallControllerFactory mInCallControllerFactory;
     @Mock private InCallController mInCallController;
     @Mock private ConnectionServiceFocusManager mConnectionSvrFocusMgr;
+    @Mock private CallAudioRouteStateMachine mCallAudioRouteStateMachine;
+    @Mock private CallAudioRouteStateMachine.Factory mCallAudioRouteStateMachineFactory;
+    @Mock private CallAudioModeStateMachine mCallAudioModeStateMachine;
+    @Mock private CallAudioModeStateMachine.Factory mCallAudioModeStateMachineFactory;
     @Mock private BluetoothStateReceiver mBluetoothStateReceiver;
+    @Mock private RoleManagerAdapter mRoleManagerAdapter;
 
     private CallsManager mCallsManager;
 
@@ -156,14 +188,18 @@
                 mProximitySensorManager);
         when(mInCallControllerFactory.create(any(), any(), any(), any(), any(), any(),
                 any())).thenReturn(mInCallController);
+        when(mCallAudioRouteStateMachineFactory.create(any(), any(), any(), any(), any(), any(),
+                anyInt())).thenReturn(mCallAudioRouteStateMachine);
+        when(mCallAudioModeStateMachineFactory.create(any(), any()))
+                .thenReturn(mCallAudioModeStateMachine);
         when(mClockProxy.currentTimeMillis()).thenReturn(System.currentTimeMillis());
         when(mClockProxy.elapsedRealtime()).thenReturn(SystemClock.elapsedRealtime());
-        when(mConnSvrFocusManagerFactory.create(any(), any())).thenReturn(mConnectionSvrFocusMgr);
+        when(mConnSvrFocusManagerFactory.create(any())).thenReturn(mConnectionSvrFocusMgr);
+        doNothing().when(mRoleManagerAdapter).setCurrentUserHandle(any());
         mCallsManager = new CallsManager(
                 mComponentContextFixture.getTestDouble().getApplicationContext(),
                 mLock,
-                mContactsAsyncHelper,
-                mCallerInfoAsyncQueryFactory,
+                mCallerInfoLookupHelper,
                 mMissedCallNotifier,
                 mPhoneAccountRegistrar,
                 mHeadsetMediaButtonFactory,
@@ -173,7 +209,7 @@
                 mAudioServiceFactory,
                 mBluetoothRouteManager,
                 mWiredHeadsetManager,
-                mSystemStateProvider,
+                mSystemStateHelper,
                 mDefaultDialerCache,
                 mTimeoutsAdapter,
                 mAsyncRingtonePlayer,
@@ -182,7 +218,10 @@
                 mToneGeneratorFactory,
                 mClockProxy,
                 mBluetoothStateReceiver,
-                mInCallControllerFactory);
+                mCallAudioRouteStateMachineFactory,
+                mCallAudioModeStateMachineFactory,
+                mInCallControllerFactory,
+                mRoleManagerAdapter);
 
         when(mPhoneAccountRegistrar.getPhoneAccount(
                 eq(SELF_MANAGED_HANDLE), any())).thenReturn(SELF_MANAGED_ACCOUNT);
@@ -215,8 +254,6 @@
                 mCallsManager,
                 mLock,
                 null /* ConnectionServiceRepository */,
-                mContactsAsyncHelper,
-                mCallerInfoAsyncQueryFactory,
                 mPhoneNumberUtilsAdapter,
                 TEST_ADDRESS,
                 null /* GatewayInfo */,
@@ -249,6 +286,21 @@
         assertEquals(2, phoneAccountHandles.size());
     }
 
+    private void setupCallerInfoLookupHelper() {
+        doAnswer(invocation -> {
+            Uri handle = invocation.getArgument(0);
+            CallerInfoLookupHelper.OnQueryCompleteListener listener = invocation.getArgument(1);
+            CallerInfo info = new CallerInfo();
+            if (CONTACT_PREFERRED_ACCOUNT.get(handle) != null) {
+                PhoneAccountHandle pah = CONTACT_PREFERRED_ACCOUNT.get(handle);
+                info.preferredPhoneAccountComponent = pah.getComponentName();
+                info.preferredPhoneAccountId = pah.getId();
+            }
+            listener.onCallerInfoQueryComplete(handle, info);
+            return null;
+        }).when(mCallerInfoLookupHelper).startLookup(any(Uri.class),
+                any(CallerInfoLookupHelper.OnQueryCompleteListener.class));
+    }
     /**
      * Tests finding the outgoing call phone account where the call is being placed on a
      * self-managed ConnectionService.
@@ -257,8 +309,10 @@
     @MediumTest
     @Test
     public void testFindOutgoingCallPhoneAccountSelfManaged() throws Exception {
+        setupCallerInfoLookupHelper();
         List<PhoneAccountHandle> accounts = mCallsManager.findOutgoingCallPhoneAccount(
-                SELF_MANAGED_HANDLE, TEST_ADDRESS, false /* isVideo */, null /* userHandle */);
+                SELF_MANAGED_HANDLE, TEST_ADDRESS, false /* isVideo */, null /* userHandle */)
+                .get();
         assertEquals(1, accounts.size());
         assertEquals(SELF_MANAGED_HANDLE, accounts.get(0));
     }
@@ -271,6 +325,7 @@
     @MediumTest
     @Test
     public void testFindOutgoingCallAccountDefault() throws Exception {
+        setupCallerInfoLookupHelper();
         when(mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(any(), any())).thenReturn(
                 SIM_1_HANDLE);
         when(mPhoneAccountRegistrar.getCallCapablePhoneAccounts(any(), anyBoolean(),
@@ -278,7 +333,8 @@
                 new ArrayList<>(Arrays.asList(SIM_1_HANDLE, SIM_2_HANDLE)));
 
         List<PhoneAccountHandle> accounts = mCallsManager.findOutgoingCallPhoneAccount(
-                null /* phoneAcct */, TEST_ADDRESS, false /* isVideo */, null /* userHandle */);
+                null /* phoneAcct */, TEST_ADDRESS, false /* isVideo */, null /* userHandle */)
+                .get();
 
         // Should have found just the default.
         assertEquals(1, accounts.size());
@@ -293,6 +349,7 @@
     @MediumTest
     @Test
     public void testFindOutgoingCallAccountNoDefault() throws Exception {
+        setupCallerInfoLookupHelper();
         when(mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(any(), any())).thenReturn(
                 null);
         when(mPhoneAccountRegistrar.getCallCapablePhoneAccounts(any(), anyBoolean(),
@@ -300,7 +357,8 @@
                 new ArrayList<>(Arrays.asList(SIM_1_HANDLE, SIM_2_HANDLE)));
 
         List<PhoneAccountHandle> accounts = mCallsManager.findOutgoingCallPhoneAccount(
-                null /* phoneAcct */, TEST_ADDRESS, false /* isVideo */, null /* userHandle */);
+                null /* phoneAcct */, TEST_ADDRESS, false /* isVideo */, null /* userHandle */)
+                .get();
 
         assertEquals(2, accounts.size());
         assertTrue(accounts.contains(SIM_1_HANDLE));
@@ -315,6 +373,7 @@
     @MediumTest
     @Test
     public void testFindOutgoingCallAccountVideo() throws Exception {
+        setupCallerInfoLookupHelper();
         when(mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(any(), any())).thenReturn(
                 null);
         when(mPhoneAccountRegistrar.getCallCapablePhoneAccounts(any(), anyBoolean(),
@@ -322,7 +381,8 @@
                 new ArrayList<>(Arrays.asList(SIM_2_HANDLE)));
 
         List<PhoneAccountHandle> accounts = mCallsManager.findOutgoingCallPhoneAccount(
-                null /* phoneAcct */, TEST_ADDRESS, true /* isVideo */, null /* userHandle */);
+                null /* phoneAcct */, TEST_ADDRESS, true /* isVideo */, null /* userHandle */)
+                .get();
 
         assertEquals(1, accounts.size());
         assertTrue(accounts.contains(SIM_2_HANDLE));
@@ -336,6 +396,7 @@
     @MediumTest
     @Test
     public void testFindOutgoingCallAccountVideoNotAvailable() throws Exception {
+        setupCallerInfoLookupHelper();
         when(mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(any(), any())).thenReturn(
                 null);
         // When querying for video capable accounts, return nothing.
@@ -347,7 +408,8 @@
                 any(), eq(0 /* none specified */))).thenReturn(
                 new ArrayList<>(Arrays.asList(SIM_1_HANDLE)));
         List<PhoneAccountHandle> accounts = mCallsManager.findOutgoingCallPhoneAccount(
-                null /* phoneAcct */, TEST_ADDRESS, true /* isVideo */, null /* userHandle */);
+                null /* phoneAcct */, TEST_ADDRESS, true /* isVideo */, null /* userHandle */)
+                .get();
 
         // Should have found one.
         assertEquals(1, accounts.size());
@@ -361,6 +423,7 @@
     @MediumTest
     @Test
     public void testUseSpecifiedAccount() throws Exception {
+        setupCallerInfoLookupHelper();
         when(mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(any(), any())).thenReturn(
                 null);
         when(mPhoneAccountRegistrar.getCallCapablePhoneAccounts(any(), anyBoolean(),
@@ -368,13 +431,34 @@
                 new ArrayList<>(Arrays.asList(SIM_1_HANDLE, SIM_2_HANDLE)));
 
         List<PhoneAccountHandle> accounts = mCallsManager.findOutgoingCallPhoneAccount(
-                SIM_2_HANDLE, TEST_ADDRESS, false /* isVideo */, null /* userHandle */);
+                SIM_2_HANDLE, TEST_ADDRESS, false /* isVideo */, null /* userHandle */).get();
 
         assertEquals(1, accounts.size());
         assertTrue(accounts.contains(SIM_2_HANDLE));
     }
 
     /**
+     * Tests that we will use the provided target phone account if it exists.
+     * @throws Exception
+     */
+    @MediumTest
+    @Test
+    public void testUseContactSpecificAcct() throws Exception {
+        setupCallerInfoLookupHelper();
+        when(mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(any(), any())).thenReturn(
+                null);
+        when(mPhoneAccountRegistrar.getCallCapablePhoneAccounts(any(), anyBoolean(),
+                any(), anyInt())).thenReturn(
+                new ArrayList<>(Arrays.asList(SIM_1_HANDLE, SIM_2_HANDLE)));
+
+        List<PhoneAccountHandle> accounts = mCallsManager.findOutgoingCallPhoneAccount(
+                null, TEST_ADDRESS2, false /* isVideo */, Process.myUserHandle()).get();
+
+        assertEquals(1, accounts.size());
+        assertTrue(accounts.contains(SIM_1_HANDLE));
+    }
+
+    /**
      * Verifies that an active call will result in playing a DTMF tone when requested.
      * @throws Exception
      */
@@ -564,6 +648,9 @@
         Call ongoingCall = addSpyCallWithConnectionService(connSvr1);
         doReturn(false).when(ongoingCall).can(Connection.CAPABILITY_HOLD);
         doReturn(true).when(ongoingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
+        doReturn(CallState.ACTIVE).when(ongoingCall).getState();
+        when(mConnectionSvrFocusMgr.getCurrentFocusCall()).thenReturn(ongoingCall);
+
         Call heldCall = addSpyCallWithConnectionService(connSvr1);
         doReturn(CallState.ON_HOLD).when(heldCall).getState();
 
@@ -573,6 +660,7 @@
 
         // WHEN answer an incoming call which ConnectionService is connSvr1
         Call incomingCall = addSpyCallWithConnectionService(connSvr1);
+        doReturn(true).when(incomingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
         mCallsManager.answerCall(incomingCall, VideoProfile.STATE_AUDIO_ONLY);
 
         // THEN the previous held call is disconnected
@@ -609,6 +697,25 @@
 
     @SmallTest
     @Test
+    public void testAnswerAlreadyActiveCall() {
+        // GIVEN a CallsManager with no ongoing call.
+
+        // WHEN answer an already active call
+        Call incomingCall = addSpyCall();
+        mCallsManager.answerCall(incomingCall, VideoProfile.STATE_AUDIO_ONLY);
+
+        // THEN the focus request for incoming call is sent
+        verifyFocusRequestAndExecuteCallback(incomingCall);
+
+        // and the incoming call is answered.
+        verify(incomingCall).answer(VideoProfile.STATE_AUDIO_ONLY);
+
+        // and the incoming call's state is still ACTIVE
+        assertEquals(CallState.ACTIVE, incomingCall.getState());
+    }
+
+    @SmallTest
+    @Test
     public void testSetActiveCallWhenOngoingCallCanNotBeHeldAndFromDifferentConnectionService() {
         ConnectionServiceWrapper connSvr1 = Mockito.mock(ConnectionServiceWrapper.class);
         ConnectionServiceWrapper connSvr2 = Mockito.mock(ConnectionServiceWrapper.class);
@@ -684,6 +791,241 @@
         assertEquals(CallState.ACTIVE, newCall.getState());
     }
 
+    @SmallTest
+    @Test
+    public void testNoFilteringOfSelfManagedCalls() {
+        ConnectionServiceWrapper connSvr1 = Mockito.mock(ConnectionServiceWrapper.class);
+
+        // GIVEN an incoming call which is self managed.
+        Call incomingCall = addSpyCallWithConnectionService(connSvr1);
+        doReturn(false).when(incomingCall).can(Connection.CAPABILITY_HOLD);
+        doReturn(false).when(incomingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
+        doReturn(true).when(incomingCall).isSelfManaged();
+        doReturn(true).when(incomingCall).setState(anyInt(), any());
+
+        // WHEN the incoming call is successfully added.
+        mCallsManager.onSuccessfulIncomingCall(incomingCall);
+
+        // THEN the incoming call is not using call filtering
+        verify(incomingCall).setIsUsingCallFiltering(eq(false));
+    }
+
+    @SmallTest
+    @Test
+    public void testAcceptIncomingCallWhenHeadsetMediaButtonShortPress() {
+        // GIVEN an incoming call
+        Call incomingCall = addSpyCall();
+        doReturn(CallState.RINGING).when(incomingCall).getState();
+
+        // WHEN media button short press
+        mCallsManager.onMediaButton(HeadsetMediaButton.SHORT_PRESS);
+
+        // THEN the incoming call is answered
+        verify(incomingCall).answer(VideoProfile.STATE_AUDIO_ONLY);
+    }
+
+    @SmallTest
+    @Test
+    public void testRejectIncomingCallWhenHeadsetMediaButtonLongPress() {
+        // GIVEN an incoming call
+        Call incomingCall = addSpyCall();
+        doReturn(CallState.RINGING).when(incomingCall).getState();
+
+        // WHEN media button long press
+        mCallsManager.onMediaButton(HeadsetMediaButton.LONG_PRESS);
+
+        // THEN the incoming call is rejected
+        verify(incomingCall).reject(false, null);
+    }
+
+    @SmallTest
+    @Test
+    public void testHangupOngoingCallWhenHeadsetMediaButtonShortPress() {
+        // GIVEN an ongoing call
+        Call ongoingCall = addSpyCall();
+        doReturn(CallState.ACTIVE).when(ongoingCall).getState();
+
+        // WHEN media button short press
+        mCallsManager.onMediaButton(HeadsetMediaButton.SHORT_PRESS);
+
+        // THEN the active call is disconnected
+        verify(ongoingCall).disconnect();
+    }
+
+    @SmallTest
+    @Test
+    public void testToggleMuteWhenHeadsetMediaButtonLongPressDuringOngoingCall() {
+        // GIVEN an ongoing call
+        Call ongoingCall = addSpyCall();
+        doReturn(CallState.ACTIVE).when(ongoingCall).getState();
+
+        // WHEN media button long press
+        mCallsManager.onMediaButton(HeadsetMediaButton.LONG_PRESS);
+
+        // THEN the microphone toggle mute
+        verify(mCallAudioRouteStateMachine)
+                .sendMessageWithSessionInfo(CallAudioRouteStateMachine.TOGGLE_MUTE);
+    }
+
+    @SmallTest
+    @Test
+    public void testSwapCallsWhenHeadsetMediaButtonShortPressDuringTwoCalls() {
+        // GIVEN an ongoing call, and this call can be held
+        Call ongoingCall = addSpyCall();
+        doReturn(CallState.ACTIVE).when(ongoingCall).getState();
+        doReturn(true).when(ongoingCall).can(Connection.CAPABILITY_HOLD);
+        doReturn(true).when(ongoingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
+        when(mConnectionSvrFocusMgr.getCurrentFocusCall()).thenReturn(ongoingCall);
+
+        // and a held call
+        Call heldCall = addSpyCall();
+        doReturn(CallState.ON_HOLD).when(heldCall).getState();
+
+        // WHEN media button short press
+        mCallsManager.onMediaButton(HeadsetMediaButton.SHORT_PRESS);
+
+        // THEN the ongoing call is held, and the focus request for heldCall call is sent
+        verify(ongoingCall).hold(nullable(String.class));
+        verifyFocusRequestAndExecuteCallback(heldCall);
+
+        // and held call is unhold now
+        verify(heldCall).unhold(nullable(String.class));
+    }
+
+    @SmallTest
+    @Test
+    public void testHangupActiveCallWhenHeadsetMediaButtonLongPressDuringTwoCalls() {
+        // GIVEN an  ongoing call
+        Call ongoingCall = addSpyCall();
+        doReturn(CallState.ACTIVE).when(ongoingCall).getState();
+
+        // and a held call
+        Call heldCall = addSpyCall();
+        doReturn(CallState.ON_HOLD).when(heldCall).getState();
+
+        // WHEN media button long press
+        mCallsManager.onMediaButton(HeadsetMediaButton.LONG_PRESS);
+
+        // THEN the ongoing call is disconnected
+        verify(ongoingCall).disconnect();
+    }
+
+    @SmallTest
+    @Test
+    public void testNoFilteringOfCallsWhenPhoneAccountRequestsSkipped() {
+        ConnectionServiceWrapper connSvr1 = Mockito.mock(ConnectionServiceWrapper.class);
+
+        // GIVEN an incoming call which is from a PhoneAccount that requested to skip filtering.
+        Call incomingCall = addSpyCallWithConnectionService(connSvr1);
+        Bundle extras = new Bundle();
+        extras.putBoolean(PhoneAccount.EXTRA_SKIP_CALL_FILTERING, true);
+        PhoneAccount skipRequestedAccount = new PhoneAccount.Builder(SIM_2_HANDLE, "Skipper")
+            .setCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION
+                | PhoneAccount.CAPABILITY_CALL_PROVIDER)
+            .setExtras(extras)
+            .setIsEnabled(true)
+            .build();
+        when(mPhoneAccountRegistrar.getPhoneAccountUnchecked(SIM_2_HANDLE))
+            .thenReturn(skipRequestedAccount);
+        doReturn(false).when(incomingCall).can(Connection.CAPABILITY_HOLD);
+        doReturn(false).when(incomingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
+        doReturn(false).when(incomingCall).isSelfManaged();
+        doReturn(true).when(incomingCall).setState(anyInt(), any());
+
+        // WHEN the incoming call is successfully added.
+        mCallsManager.onSuccessfulIncomingCall(incomingCall);
+
+        // THEN the incoming call is not using call filtering
+        verify(incomingCall).setIsUsingCallFiltering(eq(false));
+    }
+
+    @SmallTest
+    @Test
+    public void testIsInEmergencyCallNetwork() {
+        // Setup a call which the network identified as an emergency call.
+        Call ongoingCall = addSpyCall();
+        ongoingCall.setConnectionProperties(Connection.PROPERTY_NETWORK_IDENTIFIED_EMERGENCY_CALL);
+
+        assertFalse(ongoingCall.isEmergencyCall());
+        assertTrue(ongoingCall.isNetworkIdentifiedEmergencyCall());
+        assertTrue(mCallsManager.isInEmergencyCall());
+    }
+
+    @SmallTest
+    @Test
+    public void testIsInEmergencyCallLocal() {
+        // Setup a call which is considered emergency based on its phone number.
+        Call ongoingCall = addSpyCall();
+        when(mPhoneNumberUtilsAdapter.isLocalEmergencyNumber(any(), any())).thenReturn(true);
+        ongoingCall.setHandle(Uri.fromParts("tel", "5551212", null),
+                TelecomManager.PRESENTATION_ALLOWED);
+
+        assertTrue(ongoingCall.isEmergencyCall());
+        assertFalse(ongoingCall.isNetworkIdentifiedEmergencyCall());
+        assertTrue(mCallsManager.isInEmergencyCall());
+    }
+
+    /**
+     * Verifies that changes to a {@link PhoneAccount}'s
+     * {@link PhoneAccount#CAPABILITY_VIDEO_CALLING} capability will be reflected on a call.
+     * @throws Exception
+     */
+    @SmallTest
+    @Test
+    public void testPhoneAccountVideoAvailability() throws InterruptedException {
+        Call ongoingCall = addSpyCall(); // adds to SIM_2_ACCT
+        LinkedBlockingQueue<Integer> capabilitiesQueue = new LinkedBlockingQueue<>(1);
+        ongoingCall.addListener(new Call.ListenerBase() {
+            @Override
+            public void onConnectionCapabilitiesChanged(Call call) {
+                try {
+                    Log.i("TYLER", "Listener got " + call.getConnectionCapabilities());
+                    capabilitiesQueue.put(call.getConnectionCapabilities());
+                } catch (InterruptedException e) {
+                    fail();
+                }
+            }
+        });
+
+        // Lets make the phone account video capable.
+        PhoneAccount videoCapableAccount = new PhoneAccount.Builder(SIM_2_ACCOUNT)
+                .setCapabilities(SIM_2_ACCOUNT.getCapabilities()
+                        | PhoneAccount.CAPABILITY_VIDEO_CALLING)
+                .build();
+        mCallsManager.getPhoneAccountListener().onPhoneAccountChanged(mPhoneAccountRegistrar,
+                videoCapableAccount);
+        // Absorb first update; it'll be from when phone account changed initially (since we force
+        // a capabilities update.
+        int newCapabilities = capabilitiesQueue.poll(TEST_TIMEOUT, TimeUnit.MILLISECONDS);
+
+        // Lets pretend the ConnectionService made it video capable as well.
+        ongoingCall.setConnectionCapabilities(
+                Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL);
+        newCapabilities = capabilitiesQueue.poll(TEST_TIMEOUT, TimeUnit.MILLISECONDS);
+        assertTrue(Connection.can(newCapabilities,
+                Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL));
+        assertTrue(ongoingCall.isVideoCallingSupportedByPhoneAccount());
+
+        // Fire a changed event for the phone account making it not capable.
+        mCallsManager.getPhoneAccountListener().onPhoneAccountChanged(mPhoneAccountRegistrar,
+                SIM_2_ACCOUNT);
+        newCapabilities = capabilitiesQueue.poll(TEST_TIMEOUT, TimeUnit.MILLISECONDS);
+        assertFalse(Connection.can(newCapabilities,
+                Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL));
+        assertFalse(ongoingCall.isVideoCallingSupportedByPhoneAccount());
+
+        // Fire a change for an unrelated phone account.
+        PhoneAccount anotherVideoCapableAcct = new PhoneAccount.Builder(SIM_1_ACCOUNT)
+                .setCapabilities(SIM_2_ACCOUNT.getCapabilities()
+                        | PhoneAccount.CAPABILITY_VIDEO_CALLING)
+                .build();
+        mCallsManager.getPhoneAccountListener().onPhoneAccountChanged(mPhoneAccountRegistrar,
+                anotherVideoCapableAcct);
+        // Call still should not be video capable
+        assertFalse(Connection.can(ongoingCall.getConnectionCapabilities(),
+                Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL));
+    }
+
     private Call addSpyCallWithConnectionService(ConnectionServiceWrapper connSvr) {
         Call call = addSpyCall();
         doReturn(connSvr).when(call).getConnectionService();
@@ -696,8 +1038,6 @@
                 mCallsManager,
                 mLock, /* ConnectionServiceRepository */
                 null,
-                mContactsAsyncHelper,
-                mCallerInfoAsyncQueryFactory,
                 mPhoneNumberUtilsAdapter,
                 TEST_ADDRESS,
                 null /* GatewayInfo */,
diff --git a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
index 01d312b..3be9594 100644
--- a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
+++ b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
@@ -190,6 +190,8 @@
         public String getSystemServiceName(Class<?> svcClass) {
             if (svcClass == UserManager.class) {
                 return Context.USER_SERVICE;
+            } else if (svcClass == AudioManager.class) {
+                return Context.AUDIO_SERVICE;
             }
             throw new UnsupportedOperationException();
         }
@@ -523,6 +525,10 @@
         });
     }
 
+    public void putFloatResource(int id, final float value) {
+        when(mResources.getFloat(eq(id))).thenReturn(value);
+    }
+
     public void putBooleanResource(int id, boolean value) {
         when(mResources.getBoolean(eq(id))).thenReturn(value);
     }
diff --git a/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java b/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
index 3154b7d..6c4e2e0 100644
--- a/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
+++ b/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
@@ -286,7 +286,9 @@
 
         @Override
         public void rejectWithMessage(String callId, String message,
-                Session.Info info) throws RemoteException { }
+                Session.Info info) throws RemoteException {
+            rejectedCallIds.add(callId);
+        }
 
         @Override
         public void disconnect(String callId, Session.Info info) throws RemoteException { }
diff --git a/tests/src/com/android/server/telecom/tests/ConnectionServiceFocusManagerTest.java b/tests/src/com/android/server/telecom/tests/ConnectionServiceFocusManagerTest.java
index 3c2cc61..77a9c0d 100644
--- a/tests/src/com/android/server/telecom/tests/ConnectionServiceFocusManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/ConnectionServiceFocusManagerTest.java
@@ -16,7 +16,6 @@
 
 package com.android.server.telecom.tests;
 
-import android.os.Looper;
 import android.test.suitebuilder.annotation.SmallTest;
 import com.android.server.telecom.Call;
 import com.android.server.telecom.CallState;
@@ -63,8 +62,7 @@
     @Before
     public void setUp() throws Exception {
         super.setUp();
-        mFocusManagerUT = new ConnectionServiceFocusManager(
-                mockCallsManagerRequester, Looper.getMainLooper());
+        mFocusManagerUT = new ConnectionServiceFocusManager(mockCallsManagerRequester);
         mNewCall = createFakeCall(mNewConnectionService, CallState.NEW);
         mActiveCall = createFakeCall(mActiveConnectionService, CallState.ACTIVE);
         ArgumentCaptor<CallsManager.CallsManagerListener> captor =
diff --git a/tests/src/com/android/server/telecom/tests/DirectToVoicemailCallFilterTest.java b/tests/src/com/android/server/telecom/tests/DirectToVoicemailCallFilterTest.java
index af4c168..a685c5e 100644
--- a/tests/src/com/android/server/telecom/tests/DirectToVoicemailCallFilterTest.java
+++ b/tests/src/com/android/server/telecom/tests/DirectToVoicemailCallFilterTest.java
@@ -17,6 +17,7 @@
 package com.android.server.telecom.tests;
 
 import android.net.Uri;
+import android.provider.CallLog;
 import android.test.suitebuilder.annotation.SmallTest;
 
 import com.android.internal.telephony.CallerInfo;
@@ -65,7 +66,10 @@
                         false, // shouldAllowCall
                         true, // shouldReject
                         true, // shouldAddToCallLog
-                        true // shouldShowNotification
+                        true, // shouldShowNotification
+                        CallLog.Calls.BLOCK_REASON_DIRECT_TO_VOICEMAIL, //callBlockReason
+                        null, //callScreeningAppName
+                        null // callScreeningComponentName
                 ));
     }
 
diff --git a/tests/src/com/android/server/telecom/tests/EventManagerTest.java b/tests/src/com/android/server/telecom/tests/EventManagerTest.java
index 24394ec..8bf8d1d 100644
--- a/tests/src/com/android/server/telecom/tests/EventManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/EventManagerTest.java
@@ -16,6 +16,9 @@
 
 package com.android.server.telecom.tests;
 
+import android.net.Uri;
+import android.os.Build;
+import android.telecom.Log;
 import android.telecom.Logging.EventManager;
 import android.test.suitebuilder.annotation.SmallTest;
 
@@ -217,4 +220,23 @@
         assertEquals(0, timings1.size());
         assertEquals(0, timings2.size());
     }
+
+    /**
+     * Ensure PII logging will log the last 2 digits of a phone number.
+     */
+    @SmallTest
+    @Test
+    public void testLogLast2DigitsPhone() {
+        if (Build.IS_USER) {
+            return;
+        }
+        assertEquals("tel:**********12",
+                Log.piiHandle(Uri.fromParts("tel", "+16505551212", null)));
+        assertEquals("tel:*****12",
+                Log.piiHandle(Uri.fromParts("tel", "5551212", null)));
+        assertEquals("tel:*11",
+                Log.piiHandle(Uri.fromParts("tel", "411", null)));
+        assertEquals("tel:1",
+                Log.piiHandle(Uri.fromParts("tel", "1", null)));
+    }
 }
\ No newline at end of file
diff --git a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
index 0671a4e..d76bb6c 100644
--- a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
+++ b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
@@ -50,7 +50,7 @@
 import com.android.server.telecom.InCallController;
 import com.android.server.telecom.PhoneAccountRegistrar;
 import com.android.server.telecom.R;
-import com.android.server.telecom.SystemStateProvider;
+import com.android.server.telecom.SystemStateHelper;
 import com.android.server.telecom.TelecomSystem;
 import com.android.server.telecom.Timeouts;
 
@@ -88,7 +88,7 @@
     @Mock CallsManager mMockCallsManager;
     @Mock PhoneAccountRegistrar mMockPhoneAccountRegistrar;
     @Mock BluetoothHeadsetProxy mMockBluetoothHeadset;
-    @Mock SystemStateProvider mMockSystemStateProvider;
+    @Mock SystemStateHelper mMockSystemStateHelper;
     @Mock PackageManager mMockPackageManager;
     @Mock Call mMockCall;
     @Mock Resources mMockResources;
@@ -122,7 +122,7 @@
         mEmergencyCallHelper = new EmergencyCallHelper(mMockContext, SYS_PKG,
                 mTimeoutsAdapter);
         mInCallController = new InCallController(mMockContext, mLock, mMockCallsManager,
-                mMockSystemStateProvider, mDefaultDialerCache, mTimeoutsAdapter,
+                mMockSystemStateHelper, mDefaultDialerCache, mTimeoutsAdapter,
                 mEmergencyCallHelper);
     }
 
diff --git a/tests/src/com/android/server/telecom/tests/InCallServiceFixture.java b/tests/src/com/android/server/telecom/tests/InCallServiceFixture.java
index 69fcdd8..d114cb8 100644
--- a/tests/src/com/android/server/telecom/tests/InCallServiceFixture.java
+++ b/tests/src/com/android/server/telecom/tests/InCallServiceFixture.java
@@ -76,7 +76,11 @@
         @Override
         public void updateCall(ParcelableCall call) throws RemoteException {
             if (!mCallById.containsKey(call.getId())) {
-                throw new RuntimeException("Call " + call.getId() + " not added yet");
+                // This used to throw an exception, however the actual InCallService implementation
+                // ignores updates for calls which don't yet exist.  This is not a problem as when
+                // a call is added to an InCallService its entire state is parceled and sent to the
+                // InCallService.
+                return;
             }
             mLatestCallId = call.getId();
             mCallById.put(call.getId(), call);
diff --git a/tests/src/com/android/server/telecom/tests/InCallTonePlayerTest.java b/tests/src/com/android/server/telecom/tests/InCallTonePlayerTest.java
new file mode 100644
index 0000000..7cf8edb
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/InCallTonePlayerTest.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2018 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.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.android.server.telecom.CallAudioManager;
+import com.android.server.telecom.CallAudioRoutePeripheralAdapter;
+import com.android.server.telecom.InCallTonePlayer;
+import com.android.server.telecom.TelecomSystem;
+
+import android.media.MediaPlayer;
+import android.media.ToneGenerator;
+import android.support.test.filters.FlakyTest;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+
+@RunWith(JUnit4.class)
+public class InCallTonePlayerTest extends TelecomTestCase {
+
+    private InCallTonePlayer.Factory mFactory;
+
+    @Mock
+    private CallAudioRoutePeripheralAdapter mCallAudioRoutePeripheralAdapter;
+
+    @Mock
+    private TelecomSystem.SyncRoot mLock;
+
+    @Mock
+    private ToneGenerator mToneGenerator;
+
+    @Mock
+    private InCallTonePlayer.ToneGeneratorFactory mToneGeneratorFactory;
+
+    private InCallTonePlayer.MediaPlayerAdapter mMediaPlayerAdapter =
+            new InCallTonePlayer.MediaPlayerAdapter() {
+        private MediaPlayer.OnCompletionListener mListener;
+
+        @Override
+        public void setLooping(boolean isLooping) {
+            // Do nothing.
+        }
+
+        @Override
+        public void setOnCompletionListener(MediaPlayer.OnCompletionListener listener) {
+            mListener = listener;
+        }
+
+        @Override
+        public void start() {
+            mListener.onCompletion(null);
+        }
+
+        @Override
+        public void release() {
+            // Do nothing.
+        }
+
+        @Override
+        public int getDuration() {
+            return 0;
+        }
+    };
+
+    @Mock
+    private InCallTonePlayer.MediaPlayerFactory mMediaPlayerFactory;
+
+    @Mock
+    private InCallTonePlayer.AudioManagerAdapter mAudioManagerAdapter;
+
+    @Mock
+    private CallAudioManager mCallAudioManager;
+
+    private InCallTonePlayer mInCallTonePlayer;
+
+    @Override
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+
+        when(mToneGeneratorFactory.get(anyInt(), anyInt())).thenReturn(mToneGenerator);
+        when(mMediaPlayerFactory.get(anyInt(), any())).thenReturn(mMediaPlayerAdapter);
+
+        mFactory = new InCallTonePlayer.Factory(mCallAudioRoutePeripheralAdapter, mLock,
+                mToneGeneratorFactory, mMediaPlayerFactory, mAudioManagerAdapter);
+        mFactory.setCallAudioManager(mCallAudioManager);
+        mInCallTonePlayer = mFactory.createPlayer(InCallTonePlayer.TONE_CALL_ENDED);
+    }
+
+    @Override
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
+        mInCallTonePlayer.cleanup();
+    }
+
+    @SmallTest
+    @Test
+    public void testNoEndCallToneInSilence() {
+        when(mAudioManagerAdapter.isVolumeOverZero()).thenReturn(false);
+        assertFalse(mInCallTonePlayer.startTone());
+
+        // Verify we didn't play a tone.
+        verify(mCallAudioManager, never()).setIsTonePlaying(eq(true));
+        verify(mMediaPlayerFactory, never()).get(anyInt(), any());
+    }
+
+    @FlakyTest
+    @SmallTest
+    @Test
+    public void testEndCallToneWhenNotSilenced() {
+        when(mAudioManagerAdapter.isVolumeOverZero()).thenReturn(true);
+        assertTrue(mInCallTonePlayer.startTone());
+
+        // Verify we did play a tone.
+        verify(mMediaPlayerFactory, timeout(5000)).get(anyInt(), any());
+        verify(mCallAudioManager).setIsTonePlaying(eq(true));
+    }
+}
diff --git a/tests/src/com/android/server/telecom/tests/IncomingCallFilterTest.java b/tests/src/com/android/server/telecom/tests/IncomingCallFilterTest.java
index d975ec2..e399088 100644
--- a/tests/src/com/android/server/telecom/tests/IncomingCallFilterTest.java
+++ b/tests/src/com/android/server/telecom/tests/IncomingCallFilterTest.java
@@ -19,6 +19,7 @@
 import android.content.ContentResolver;
 import android.content.IContentProvider;
 import android.net.Uri;
+import android.provider.CallLog;
 import android.test.suitebuilder.annotation.SmallTest;
 
 import com.android.server.telecom.Call;
@@ -55,6 +56,7 @@
     @Mock private IncomingCallFilter.CallFilter mFilter1;
     @Mock private IncomingCallFilter.CallFilter mFilter2;
     @Mock private IncomingCallFilter.CallFilter mFilter3;
+    @Mock private IncomingCallFilter.CallFilter mFilter4;
 
     @Mock private Timeouts.Adapter mTimeoutsAdapter;
 
@@ -62,7 +64,7 @@
     private static final long LONG_TIMEOUT = 1000000;
     private static final long SHORT_TIMEOUT = 100;
 
-    private static final CallFilteringResult RESULT1 =
+    private static final CallFilteringResult PASS_CALL_RESULT =
             new CallFilteringResult(
                     true, // shouldAllowCall
                     false, // shouldReject
@@ -70,23 +72,41 @@
                     true // shouldShowNotification
             );
 
-    private static final CallFilteringResult RESULT2 =
-            new CallFilteringResult(
-                    false, // shouldAllowCall
-                    true, // shouldReject
-                    false, // shouldAddToCallLog
-                    true // shouldShowNotification
-            );
-
-    private static final CallFilteringResult RESULT3 =
+    private static final CallFilteringResult ASYNC_BLOCK_CHECK_BLOCK_RESULT =
             new CallFilteringResult(
                     false, // shouldAllowCall
                     true, // shouldReject
                     true, // shouldAddToCallLog
-                    false // shouldShowNotification
+                    false, // shouldShowNotification
+                    CallLog.Calls.BLOCK_REASON_BLOCKED_NUMBER, //callBlockReason
+                    null, //callScreeningAppName
+                    null //callScreeningComponentName
             );
 
-    private static final CallFilteringResult DEFAULT_RESULT = RESULT1;
+    private static final CallFilteringResult DIRECT_TO_VOICEMAIL_CALL_BLOCK_RESULT =
+            new CallFilteringResult(
+                    false, // shouldAllowCall
+                    true, // shouldReject
+                    true, // shouldAddToCallLog
+                    true, // shouldShowNotification
+                    CallLog.Calls.BLOCK_REASON_DIRECT_TO_VOICEMAIL, //callBlockReason
+                    null, //callScreeningAppName
+                    null //callScreeningComponentName
+            );
+
+    private static final CallFilteringResult CALL_SCREENING_SERVICE_BLOCK_RESULT =
+            new CallFilteringResult(
+                    false, // shouldAllowCall
+                    true, // shouldReject
+                    false, // shouldAddToCallLog
+                    true, // shouldShowNotification
+                    CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE, //callBlockReason
+                    "com.android.thirdparty", //callScreeningAppName
+                    "com.android.thirdparty/com.android.thirdparty.callscreeningserviceimpl"
+                    //callScreeningComponentName
+            );
+
+    private static final CallFilteringResult DEFAULT_RESULT = PASS_CALL_RESULT;
 
     @Override
     @Before
@@ -99,20 +119,97 @@
 
     @SmallTest
     @Test
-    public void testSingleFilter() {
+    public void testAsyncBlockCallResultFilter() {
         IncomingCallFilter testFilter = new IncomingCallFilter(mContext, mResultCallback, mCall,
                 mLock, mTimeoutsAdapter, Collections.singletonList(mFilter1));
         testFilter.performFiltering();
         verify(mFilter1).startFilterLookup(mCall, testFilter);
 
-        testFilter.onCallFilteringComplete(mCall, RESULT1);
+        testFilter.onCallFilteringComplete(mCall, ASYNC_BLOCK_CHECK_BLOCK_RESULT);
         waitForHandlerAction(testFilter.getHandler(), SHORT_TIMEOUT * 2);
-        verify(mResultCallback).onCallFilteringComplete(eq(mCall), eq(RESULT1));
+        verify(mResultCallback).onCallFilteringComplete(eq(mCall), eq
+                (ASYNC_BLOCK_CHECK_BLOCK_RESULT));
     }
 
     @SmallTest
     @Test
-    public void testMultipleFilters() {
+    public void testDirectToVoiceMailCallResultFilter() {
+        IncomingCallFilter testFilter = new IncomingCallFilter(mContext, mResultCallback, mCall,
+                mLock, mTimeoutsAdapter, Collections.singletonList(mFilter1));
+        testFilter.performFiltering();
+        verify(mFilter1).startFilterLookup(mCall, testFilter);
+
+        testFilter.onCallFilteringComplete(mCall, DIRECT_TO_VOICEMAIL_CALL_BLOCK_RESULT);
+        waitForHandlerAction(testFilter.getHandler(), SHORT_TIMEOUT * 2);
+        verify(mResultCallback).onCallFilteringComplete(eq(mCall), eq
+                (DIRECT_TO_VOICEMAIL_CALL_BLOCK_RESULT));
+    }
+
+    @SmallTest
+    @Test
+    public void testCallScreeningServiceBlockCallResultFilter() {
+        IncomingCallFilter testFilter = new IncomingCallFilter(mContext, mResultCallback, mCall,
+                mLock, mTimeoutsAdapter, Collections.singletonList(mFilter1));
+        testFilter.performFiltering();
+        verify(mFilter1).startFilterLookup(mCall, testFilter);
+
+        testFilter.onCallFilteringComplete(mCall, CALL_SCREENING_SERVICE_BLOCK_RESULT);
+        waitForHandlerAction(testFilter.getHandler(), SHORT_TIMEOUT * 2);
+        verify(mResultCallback).onCallFilteringComplete(eq(mCall), eq
+                (CALL_SCREENING_SERVICE_BLOCK_RESULT));
+    }
+
+    @SmallTest
+    @Test
+    public void testPassCallResultFilter() {
+        IncomingCallFilter testFilter = new IncomingCallFilter(mContext, mResultCallback, mCall,
+                mLock, mTimeoutsAdapter, Collections.singletonList(mFilter1));
+        testFilter.performFiltering();
+        verify(mFilter1).startFilterLookup(mCall, testFilter);
+
+        testFilter.onCallFilteringComplete(mCall, PASS_CALL_RESULT);
+        waitForHandlerAction(testFilter.getHandler(), SHORT_TIMEOUT * 2);
+        verify(mResultCallback).onCallFilteringComplete(eq(mCall), eq(PASS_CALL_RESULT));
+    }
+
+    @SmallTest
+    @Test
+    public void testMultipleFiltersForAsyncBlockCheckFilter() {
+        List<IncomingCallFilter.CallFilter> filters =
+                new ArrayList<IncomingCallFilter.CallFilter>() {{
+                    add(mFilter1);
+                    add(mFilter2);
+                    add(mFilter3);
+                    add(mFilter4);
+                }};
+        IncomingCallFilter testFilter = new IncomingCallFilter(mContext, mResultCallback, mCall,
+                mLock, mTimeoutsAdapter, filters);
+        testFilter.performFiltering();
+        verify(mFilter1).startFilterLookup(mCall, testFilter);
+        verify(mFilter2).startFilterLookup(mCall, testFilter);
+        verify(mFilter3).startFilterLookup(mCall, testFilter);
+        verify(mFilter4).startFilterLookup(mCall, testFilter);
+
+        testFilter.onCallFilteringComplete(mCall, PASS_CALL_RESULT);
+        testFilter.onCallFilteringComplete(mCall, ASYNC_BLOCK_CHECK_BLOCK_RESULT);
+        testFilter.onCallFilteringComplete(mCall, DIRECT_TO_VOICEMAIL_CALL_BLOCK_RESULT);
+        testFilter.onCallFilteringComplete(mCall, CALL_SCREENING_SERVICE_BLOCK_RESULT);
+        waitForHandlerAction(testFilter.getHandler(), SHORT_TIMEOUT * 2);
+        verify(mResultCallback).onCallFilteringComplete(eq(mCall), eq(
+                new CallFilteringResult(
+                        false, // shouldAllowCall
+                        true, // shouldReject
+                        false, // shouldAddToCallLog
+                        false, // shouldShowNotification
+                        CallLog.Calls.BLOCK_REASON_BLOCKED_NUMBER, //callBlockReason
+                        null, //callScreeningAppName
+                        null //callScreeningComponentName
+                )));
+    }
+
+    @SmallTest
+    @Test
+    public void testMultipleFiltersForDirectToVoicemailCallFilter() {
         List<IncomingCallFilter.CallFilter> filters =
                 new ArrayList<IncomingCallFilter.CallFilter>() {{
                     add(mFilter1);
@@ -126,16 +223,49 @@
         verify(mFilter2).startFilterLookup(mCall, testFilter);
         verify(mFilter3).startFilterLookup(mCall, testFilter);
 
-        testFilter.onCallFilteringComplete(mCall, RESULT1);
-        testFilter.onCallFilteringComplete(mCall, RESULT2);
-        testFilter.onCallFilteringComplete(mCall, RESULT3);
+        testFilter.onCallFilteringComplete(mCall, PASS_CALL_RESULT);
+        testFilter.onCallFilteringComplete(mCall, DIRECT_TO_VOICEMAIL_CALL_BLOCK_RESULT);
+        testFilter.onCallFilteringComplete(mCall, CALL_SCREENING_SERVICE_BLOCK_RESULT);
         waitForHandlerAction(testFilter.getHandler(), SHORT_TIMEOUT * 2);
         verify(mResultCallback).onCallFilteringComplete(eq(mCall), eq(
                 new CallFilteringResult(
                         false, // shouldAllowCall
                         true, // shouldReject
                         false, // shouldAddToCallLog
-                        false // shouldShowNotification
+                        true, // shouldShowNotification
+                        CallLog.Calls.BLOCK_REASON_DIRECT_TO_VOICEMAIL, //callBlockReason
+                        null, ////callScreeningAppName
+                        null ////callScreeningComponentName
+                )));
+    }
+
+    @SmallTest
+    @Test
+    public void testMultipleFiltersForCallScreeningServiceFilter() {
+        List<IncomingCallFilter.CallFilter> filters =
+                new ArrayList<IncomingCallFilter.CallFilter>() {{
+                    add(mFilter1);
+                    add(mFilter2);
+                }};
+        IncomingCallFilter testFilter = new IncomingCallFilter(mContext, mResultCallback, mCall,
+                mLock, mTimeoutsAdapter, filters);
+        testFilter.performFiltering();
+        verify(mFilter1).startFilterLookup(mCall, testFilter);
+        verify(mFilter2).startFilterLookup(mCall, testFilter);
+
+        testFilter.onCallFilteringComplete(mCall, PASS_CALL_RESULT);
+        testFilter.onCallFilteringComplete(mCall, CALL_SCREENING_SERVICE_BLOCK_RESULT);
+        waitForHandlerAction(testFilter.getHandler(), SHORT_TIMEOUT * 2);
+        verify(mResultCallback).onCallFilteringComplete(eq(mCall), eq(
+                new CallFilteringResult(
+                        false, // shouldAllowCall
+                        true, // shouldReject
+                        false, // shouldAddToCallLog
+                        true, // shouldShowNotification
+                        CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE, //callBlockReason
+                        "com.android.thirdparty", //callScreeningAppName
+                        "com.android.thirdparty/com.android.thirdparty.callscreeningserviceimpl"
+                        //callScreeningComponentName
                 )));
     }
 
@@ -148,7 +278,7 @@
         testFilter.performFiltering();
         verify(mResultCallback, timeout((int) SHORT_TIMEOUT * 2)).onCallFilteringComplete(eq(mCall),
                 eq(DEFAULT_RESULT));
-        testFilter.onCallFilteringComplete(mCall, RESULT1);
+        testFilter.onCallFilteringComplete(mCall, PASS_CALL_RESULT);
         waitForHandlerAction(testFilter.getHandler(), SHORT_TIMEOUT * 2);
         // verify that we don't report back again with the result
         verify(mResultCallback, atMost(1)).onCallFilteringComplete(any(Call.class),
@@ -162,7 +292,7 @@
         IncomingCallFilter testFilter = new IncomingCallFilter(mContext, mResultCallback, mCall,
                 mLock, mTimeoutsAdapter, Collections.singletonList(mFilter1));
         testFilter.performFiltering();
-        testFilter.onCallFilteringComplete(mCall, RESULT1);
+        testFilter.onCallFilteringComplete(mCall, PASS_CALL_RESULT);
         waitForHandlerAction(testFilter.getHandler(), SHORT_TIMEOUT * 2);
         Thread.sleep(SHORT_TIMEOUT);
         verify(mResultCallback, atMost(1)).onCallFilteringComplete(any(Call.class),
@@ -172,9 +302,13 @@
     @SmallTest
     @Test
     public void testToString() {
-        assertEquals("[Allow, logged, notified]", RESULT1.toString());
-        assertEquals("[Reject, notified]", RESULT2.toString());
-        assertEquals("[Reject, logged]", RESULT3.toString());
+        assertEquals("[Allow, logged, notified]", PASS_CALL_RESULT.toString());
+        assertEquals("[Reject, notified, mCallBlockReason = 1, mCallScreeningAppName = com" +
+                ".android.thirdparty, mCallScreeningComponentName = com.android.thirdparty/com" +
+                ".android.thirdparty.callscreeningserviceimpl]",
+            CALL_SCREENING_SERVICE_BLOCK_RESULT.toString());
+        assertEquals("[Reject, logged, mCallBlockReason = 3]",
+            ASYNC_BLOCK_CHECK_BLOCK_RESULT.toString());
     }
 
     private void setTimeoutLength(long length) throws Exception {
diff --git a/tests/src/com/android/server/telecom/tests/NewOutgoingCallIntentBroadcasterTest.java b/tests/src/com/android/server/telecom/tests/NewOutgoingCallIntentBroadcasterTest.java
index 4c0e76a..341292c 100644
--- a/tests/src/com/android/server/telecom/tests/NewOutgoingCallIntentBroadcasterTest.java
+++ b/tests/src/com/android/server/telecom/tests/NewOutgoingCallIntentBroadcasterTest.java
@@ -28,6 +28,7 @@
 import android.os.Handler;
 import android.os.UserHandle;
 import android.telecom.GatewayInfo;
+import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
 import android.telecom.VideoProfile;
 import android.telephony.DisconnectCause;
@@ -36,8 +37,10 @@
 import com.android.server.telecom.Call;
 import com.android.server.telecom.CallsManager;
 import com.android.server.telecom.NewOutgoingCallIntentBroadcaster;
+import com.android.server.telecom.PhoneAccountRegistrar;
 import com.android.server.telecom.PhoneNumberUtilsAdapter;
 import com.android.server.telecom.PhoneNumberUtilsAdapterImpl;
+import com.android.server.telecom.SystemStateHelper;
 import com.android.server.telecom.TelecomSystem;
 
 import org.junit.Before;
@@ -78,6 +81,9 @@
 
     @Mock private CallsManager mCallsManager;
     @Mock private Call mCall;
+    @Mock private SystemStateHelper mSystemStateHelper;
+    @Mock private UserHandle mUserHandle;
+    @Mock private PhoneAccountRegistrar mPhoneAccountRegistrar;
 
     private PhoneNumberUtilsAdapter mPhoneNumberUtilsAdapterSpy;
 
@@ -89,13 +95,19 @@
         mPhoneNumberUtilsAdapterSpy = spy(new PhoneNumberUtilsAdapterImpl());
         when(mCall.getInitiatingUser()).thenReturn(UserHandle.CURRENT);
         when(mCallsManager.getLock()).thenReturn(new TelecomSystem.SyncRoot() { });
+        when(mCallsManager.getSystemStateHelper()).thenReturn(mSystemStateHelper);
+        when(mCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
+        when(mCallsManager.getPhoneAccountRegistrar()).thenReturn(mPhoneAccountRegistrar);
+        when(mPhoneAccountRegistrar.getSubscriptionIdForPhoneAccount(
+                any(PhoneAccountHandle.class))).thenReturn(-1);
+        when(mSystemStateHelper.isCarMode()).thenReturn(false);
     }
 
     @SmallTest
     @Test
     public void testNullHandle() {
         Intent intent = new Intent(Intent.ACTION_CALL, null);
-        int result = processIntent(intent, true);
+        int result = processIntent(intent, true).disconnectCause;
         assertEquals(DisconnectCause.INVALID_NUMBER, result);
         verifyNoBroadcastSent();
         verifyNoCallPlaced();
@@ -108,7 +120,7 @@
         Intent intent = new Intent(Intent.ACTION_CALL, Uri.parse(voicemailNumber));
         intent.putExtra(TelecomManager.EXTRA_START_CALL_WITH_SPEAKERPHONE, true);
 
-        int result = processIntent(intent, true);
+        int result = processIntent(intent, true).disconnectCause;
 
         assertEquals(DisconnectCause.NOT_DISCONNECTED, result);
         verify(mCallsManager).placeOutgoingCall(eq(mCall), eq(Uri.parse(voicemailNumber)),
@@ -136,7 +148,7 @@
     private void badCallActionHelper(Uri handle, int expectedCode) {
         Intent intent = new Intent(Intent.ACTION_ALARM_CHANGED, handle);
 
-        int result = processIntent(intent, true);
+        int result = processIntent(intent, true).disconnectCause;
 
         assertEquals(expectedCode, result);
         verifyNoBroadcastSent();
@@ -164,7 +176,7 @@
         Uri handle = Uri.parse("tel:");
         Intent intent = new Intent(Intent.ACTION_CALL, handle);
 
-        int result = processIntent(intent, true);
+        int result = processIntent(intent, true).disconnectCause;
 
         assertEquals(DisconnectCause.NO_PHONE_NUMBER_SUPPLIED, result);
         verifyNoBroadcastSent();
@@ -185,7 +197,7 @@
         mComponentContextFixture.putResource(R.string.dialer_default_class,
                 dialer_default_class_string);
 
-        int result = processIntent(intent, false);
+        int result = processIntent(intent, false).disconnectCause;
 
         assertEquals(DisconnectCause.OUTGOING_CANCELED, result);
         verifyNoBroadcastSent();
@@ -245,7 +257,7 @@
         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);
+        int result = processIntent(intent, true).disconnectCause;
 
         assertEquals(DisconnectCause.OUTGOING_CANCELED, result);
         verifyNoCallPlaced();
@@ -260,7 +272,7 @@
                 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);
+        int result = processIntent(intent, true).disconnectCause;
 
         assertEquals(DisconnectCause.NOT_DISCONNECTED, result);
         verify(mCallsManager).placeOutgoingCall(eq(mCall), eq(handle), isNull(GatewayInfo.class),
@@ -389,7 +401,7 @@
         intent.putExtra(TelecomManager.EXTRA_START_CALL_WITH_SPEAKERPHONE, isSpeakerphoneOn);
         intent.putExtra(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, videoState);
 
-        int result = processIntent(intent, true);
+        int result = processIntent(intent, true).disconnectCause;
 
         assertEquals(DisconnectCause.NOT_DISCONNECTED, result);
         Bundle expectedExtras = createNumberExtras(handle.getSchemeSpecificPart());
@@ -407,12 +419,16 @@
         return i;
     }
 
-    private int processIntent(Intent intent,
+    private NewOutgoingCallIntentBroadcaster.CallDisposition processIntent(Intent intent,
             boolean isDefaultPhoneApp) {
         NewOutgoingCallIntentBroadcaster b = new NewOutgoingCallIntentBroadcaster(
                 mContext, mCallsManager, mCall, intent, mPhoneNumberUtilsAdapterSpy,
                 isDefaultPhoneApp);
-        return b.processIntent();
+        NewOutgoingCallIntentBroadcaster.CallDisposition cd = b.evaluateCall();
+        if (cd.disconnectCause == DisconnectCause.NOT_DISCONNECTED) {
+            b.processCall(cd);
+        }
+        return cd;
     }
 
     private ReceiverIntentPair verifyBroadcastSent(String number, Bundle expectedExtras) {
diff --git a/tests/src/com/android/server/telecom/tests/ParcelableCallUtilsTest.java b/tests/src/com/android/server/telecom/tests/ParcelableCallUtilsTest.java
new file mode 100644
index 0000000..753b635
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/ParcelableCallUtilsTest.java
@@ -0,0 +1,122 @@
+package com.android.server.telecom.tests;
+
+import static com.android.server.telecom.TelecomSystem.*;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.telecom.Connection;
+import android.telecom.GatewayInfo;
+import android.telecom.ParcelableCall;
+import android.telecom.PhoneAccountHandle;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallerInfoLookupHelper;
+import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.ClockProxy;
+import com.android.server.telecom.ConnectionServiceRepository;
+import com.android.server.telecom.ParcelableCallUtils;
+import com.android.server.telecom.PhoneAccountRegistrar;
+import com.android.server.telecom.PhoneNumberUtilsAdapter;
+import com.android.server.telecom.TelecomSystem;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(JUnit4.class)
+public class ParcelableCallUtilsTest extends TelecomTestCase {
+
+    private SyncRoot mLock = new SyncRoot() {};
+    @Mock private ClockProxy mClockProxy;
+    @Mock private CallsManager mCallsManager;
+    @Mock private CallerInfoLookupHelper mCallerInfoLookupHelper;
+    @Mock private PhoneNumberUtilsAdapter mPhoneNumberUtilsAdapter;
+    @Mock private PhoneAccountRegistrar mPhoneAccountRegistrar;
+    private Call mCall;
+
+    @Override
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        MockitoAnnotations.initMocks(this);
+        when(mClockProxy.currentTimeMillis()).thenReturn(System.currentTimeMillis());
+        when(mClockProxy.elapsedRealtime()).thenReturn(SystemClock.elapsedRealtime());
+        when(mCallsManager.getCallerInfoLookupHelper()).thenReturn(mCallerInfoLookupHelper);
+        when(mCallsManager.getPhoneAccountRegistrar()).thenReturn(mPhoneAccountRegistrar);
+        when(mPhoneAccountRegistrar.getPhoneAccountUnchecked(any())).thenReturn(null);
+        when(mPhoneNumberUtilsAdapter.isLocalEmergencyNumber(any(), any())).thenReturn(false);
+        mCall = new Call("1",
+                null /* context */,
+                mCallsManager,
+                mLock,
+                null /* ConnectionServiceRepository */,
+                mPhoneNumberUtilsAdapter,
+                Uri.fromParts("tel", "6505551212", null),
+                null /* GatewayInfo */,
+                null /* connectionMgr */,
+                new PhoneAccountHandle(
+                        ComponentName.unflattenFromString("com.test/Class"), "test"),
+                Call.CALL_DIRECTION_INCOMING,
+                false /* shouldAttachToExistingConnection */,
+                false /* isConference */,
+                mClockProxy /* ClockProxy */);
+    }
+
+    @SmallTest
+    @Test
+    public void testParcelForNonSystemDialer() {
+        mCall.putExtras(Call.SOURCE_CONNECTION_SERVICE, getSomeExtras());
+        ParcelableCall call = ParcelableCallUtils.toParcelableCall(mCall,
+                false /* includevideoProvider */,
+                null /* phoneAccountRegistrar */,
+                false /* supportsExternalCalls */,
+                false /* includeRttCall */,
+                false /* isForSystemDialer */);
+
+        Bundle parceledExtras = call.getExtras();
+        assertFalse(parceledExtras.containsKey(Connection.EXTRA_SIP_INVITE));
+        assertFalse(parceledExtras.containsKey("SomeExtra"));
+        assertTrue(parceledExtras.containsKey(Connection.EXTRA_CALL_SUBJECT));
+    }
+
+    @SmallTest
+    @Test
+    public void testParcelForSystemDialer() {
+        mCall.putExtras(Call.SOURCE_CONNECTION_SERVICE, getSomeExtras());
+        ParcelableCall call = ParcelableCallUtils.toParcelableCall(mCall,
+                false /* includevideoProvider */,
+                null /* phoneAccountRegistrar */,
+                false /* supportsExternalCalls */,
+                false /* includeRttCall */,
+                true /* isForSystemDialer */);
+
+        Bundle parceledExtras = call.getExtras();
+        assertTrue(parceledExtras.containsKey(Connection.EXTRA_SIP_INVITE));
+        assertTrue(parceledExtras.containsKey("SomeExtra"));
+        assertTrue(parceledExtras.containsKey(Connection.EXTRA_CALL_SUBJECT));
+    }
+
+    private Bundle getSomeExtras() {
+        Bundle extras = new Bundle();
+        extras.putString(Connection.EXTRA_SIP_INVITE, "scary data");
+        extras.putString("SomeExtra", "Extra Extra");
+        extras.putString(Connection.EXTRA_CALL_SUBJECT, "Blah");
+        return extras;
+    }
+}
diff --git a/tests/src/com/android/server/telecom/tests/RingerTest.java b/tests/src/com/android/server/telecom/tests/RingerTest.java
index 25460de..02ee137 100644
--- a/tests/src/com/android/server/telecom/tests/RingerTest.java
+++ b/tests/src/com/android/server/telecom/tests/RingerTest.java
@@ -18,12 +18,12 @@
 
 import android.app.NotificationManager;
 import android.content.Context;
-import android.content.res.Resources;
 import android.media.AudioAttributes;
 import android.media.AudioManager;
 import android.media.Ringtone;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.Parcel;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
 import android.telecom.TelecomManager;
@@ -42,20 +42,56 @@
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 import org.mockito.Mock;
+import org.mockito.Spy;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import java.util.Objects;
+
 @RunWith(JUnit4.class)
 public class RingerTest extends TelecomTestCase {
     private static final Uri FAKE_RINGTONE_URI = Uri.parse("content://media/fake/audio/1729");
+    private static class UriVibrationEffect extends VibrationEffect {
+        final Uri mUri;
+
+        private UriVibrationEffect(Uri uri) {
+            mUri = uri;
+        }
+
+        @Override
+        public void validate() {
+            // not needed
+        }
+
+        @Override
+        public long getDuration() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            // not needed
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            UriVibrationEffect that = (UriVibrationEffect) o;
+            return Objects.equals(mUri, that.mUri);
+        }
+    }
 
     @Mock InCallTonePlayer.Factory mockPlayerFactory;
     @Mock SystemSettingsUtil mockSystemSettingsUtil;
@@ -63,6 +99,7 @@
     @Mock RingtoneFactory mockRingtoneFactory;
     @Mock Vibrator mockVibrator;
     @Mock InCallController mockInCallController;
+    @Spy Ringer.VibrationEffectProxy spyVibrationEffectProxy;
 
     @Mock InCallTonePlayer mockTonePlayer;
     @Mock Call mockCall1;
@@ -76,15 +113,21 @@
     public void setUp() throws Exception {
         super.setUp();
         mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
-        mRingerUnderTest = new Ringer(mockPlayerFactory, mContext, mockSystemSettingsUtil,
-                mockRingtonePlayer, mockRingtoneFactory, mockVibrator, mockInCallController);
+        doAnswer(invocation -> {
+            Uri ringtoneUriForEffect = invocation.getArgument(0);
+            return new UriVibrationEffect(ringtoneUriForEffect);
+        }).when(spyVibrationEffectProxy).get(any(), any());
         when(mockPlayerFactory.createPlayer(anyInt())).thenReturn(mockTonePlayer);
         mockAudioManager =
                 (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
         when(mockAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
         NotificationManager notificationManager =
                 (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+        when(mockTonePlayer.startTone()).thenReturn(true);
         when(notificationManager.matchesCallFilter(any(Bundle.class))).thenReturn(true);
+        mRingerUnderTest = new Ringer(mockPlayerFactory, mContext, mockSystemSettingsUtil,
+                mockRingtonePlayer, mockRingtoneFactory, mockVibrator, spyVibrationEffectProxy,
+                mockInCallController);
     }
 
     @SmallTest
@@ -207,9 +250,6 @@
     @SmallTest
     @Test
     public void testCustomVibrationForRingtone() {
-        Resources resources = mContext.getResources();
-        when(resources.getStringArray(com.android.internal.R.array.config_ringtoneEffectUris))
-                .thenReturn(new String[] { FAKE_RINGTONE_URI.toString() });
         mRingerUnderTest.startCallWaiting(mockCall1);
         Ringtone mockRingtone = mock(Ringtone.class);
         when(mockRingtoneFactory.getRingtone(any(Call.class))).thenReturn(mockRingtone);
@@ -218,7 +258,7 @@
         assertTrue(mRingerUnderTest.startRinging(mockCall2, false));
         verify(mockTonePlayer).stopTone();
         verify(mockRingtonePlayer).play(any(RingtoneFactory.class), any(Call.class));
-        verify(mockVibrator).vibrate(eq(VibrationEffect.get(FAKE_RINGTONE_URI, mContext)),
+        verify(mockVibrator).vibrate(eq(spyVibrationEffectProxy.get(FAKE_RINGTONE_URI, mContext)),
                 any(AudioAttributes.class));
     }
 
diff --git a/tests/src/com/android/server/telecom/tests/SystemStateHelperTest.java b/tests/src/com/android/server/telecom/tests/SystemStateHelperTest.java
new file mode 100644
index 0000000..efe8796
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/SystemStateHelperTest.java
@@ -0,0 +1,294 @@
+/*
+ * 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.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+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 android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.telecom.SystemStateHelper;
+import com.android.server.telecom.SystemStateHelper.SystemStateListener;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.internal.util.reflection.FieldSetter;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+
+/**
+ * Unit tests for SystemStateHelper
+ */
+@RunWith(JUnit4.class)
+public class SystemStateHelperTest extends TelecomTestCase {
+
+    Context mContext;
+    @Mock SystemStateListener mSystemStateListener;
+    @Mock Sensor mGravitySensor;
+    @Mock Sensor mProxSensor;
+    @Mock UiModeManager mUiModeManager;
+    @Mock SensorManager mSensorManager;
+    @Mock Intent mIntentEnter;
+    @Mock Intent mIntentExit;
+
+    @Override
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        MockitoAnnotations.initMocks(this);
+        mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
+        doReturn(mSensorManager).when(mContext).getSystemService(SensorManager.class);
+        when(mGravitySensor.getType()).thenReturn(Sensor.TYPE_GRAVITY);
+        when(mProxSensor.getType()).thenReturn(Sensor.TYPE_PROXIMITY);
+        when(mProxSensor.getMaximumRange()).thenReturn(5.0f);
+        when(mSensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY)).thenReturn(mGravitySensor);
+        when(mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY)).thenReturn(mProxSensor);
+
+        mComponentContextFixture.putFloatResource(
+                R.dimen.device_on_ear_xy_gravity_threshold, 5.5f);
+        mComponentContextFixture.putFloatResource(
+                R.dimen.device_on_ear_y_gravity_negative_threshold, -1f);
+    }
+
+    @Override
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    @SmallTest
+    @Test
+    public void testListeners() throws Exception {
+        SystemStateHelper systemStateHelper = new SystemStateHelper(mContext);
+
+        assertFalse(systemStateHelper.removeListener(mSystemStateListener));
+        systemStateHelper.addListener(mSystemStateListener);
+        assertTrue(systemStateHelper.removeListener(mSystemStateListener));
+        assertFalse(systemStateHelper.removeListener(mSystemStateListener));
+    }
+
+    @SmallTest
+    @Test
+    public void testQuerySystemForCarMode_True() {
+        when(mContext.getSystemService(Context.UI_MODE_SERVICE)).thenReturn(mUiModeManager);
+        when(mUiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_CAR);
+        assertTrue(new SystemStateHelper(mContext).isCarMode());
+    }
+
+    @SmallTest
+    @Test
+    public void testQuerySystemForCarMode_False() {
+        when(mContext.getSystemService(Context.UI_MODE_SERVICE)).thenReturn(mUiModeManager);
+        when(mUiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_NORMAL);
+        assertFalse(new SystemStateHelper(mContext).isCarMode());
+    }
+
+    @SmallTest
+    @Test
+    public void testReceiverAndIntentFilter() {
+        ArgumentCaptor<IntentFilter> intentFilter = ArgumentCaptor.forClass(IntentFilter.class);
+        new SystemStateHelper(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));
+    }
+
+    @SmallTest
+    @Test
+    public void testOnEnterExitCarMode() {
+        ArgumentCaptor<BroadcastReceiver> receiver =
+                ArgumentCaptor.forClass(BroadcastReceiver.class);
+        new SystemStateHelper(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"));
+    }
+
+    @SmallTest
+    @Test
+    public void testDeviceOnEarCorrectlyDetected() {
+        doAnswer(invocation -> {
+            SensorEventListener listener = invocation.getArgument(0);
+            Sensor sensor = invocation.getArgument(1);
+            if (sensor.getType() == Sensor.TYPE_GRAVITY) {
+                listener.onSensorChanged(makeSensorEvent(
+                        new float[]{1.0f, 9.0f, 1.0f}, Sensor.TYPE_GRAVITY));
+            } else {
+                listener.onSensorChanged(makeSensorEvent(
+                        new float[]{0.0f}, Sensor.TYPE_PROXIMITY));
+            }
+            return true;
+        }).when(mSensorManager)
+                .registerListener(any(SensorEventListener.class), any(Sensor.class), anyInt());
+
+        assertTrue(SystemStateHelper.isDeviceAtEar(mContext));
+        verify(mSensorManager).unregisterListener(any(SensorEventListener.class));
+    }
+
+    @SmallTest
+    @Test
+    public void testDeviceIsNotOnEarWithProxNotSensed() {
+        doAnswer(invocation -> {
+            SensorEventListener listener = invocation.getArgument(0);
+            Sensor sensor = invocation.getArgument(1);
+            if (sensor.getType() == Sensor.TYPE_GRAVITY) {
+                listener.onSensorChanged(makeSensorEvent(
+                        new float[]{1.0f, 9.0f, 1.0f}, Sensor.TYPE_GRAVITY));
+            } else {
+                // do nothing to simulate proximity sensor not reporting
+            }
+            return true;
+        }).when(mSensorManager)
+                .registerListener(any(SensorEventListener.class), any(Sensor.class), anyInt());
+
+        assertFalse(SystemStateHelper.isDeviceAtEar(mContext));
+        verify(mSensorManager).unregisterListener(any(SensorEventListener.class));
+    }
+
+    @SmallTest
+    @Test
+    public void testDeviceIsNotOnEarWithWrongOrientation() {
+        doAnswer(invocation -> {
+            SensorEventListener listener = invocation.getArgument(0);
+            Sensor sensor = invocation.getArgument(1);
+            if (sensor.getType() == Sensor.TYPE_GRAVITY) {
+                listener.onSensorChanged(makeSensorEvent(
+                        new float[]{1.0f, 1.0f, 9.0f}, Sensor.TYPE_GRAVITY));
+            } else {
+                listener.onSensorChanged(makeSensorEvent(
+                        new float[]{0.0f}, Sensor.TYPE_PROXIMITY));
+            }
+            return true;
+        }).when(mSensorManager)
+                .registerListener(any(SensorEventListener.class), any(Sensor.class), anyInt());
+
+        assertFalse(SystemStateHelper.isDeviceAtEar(mContext));
+        verify(mSensorManager).unregisterListener(any(SensorEventListener.class));
+    }
+
+    @SmallTest
+    @Test
+    public void testDeviceIsNotOnEarWithMissingSensor() {
+        when(mSensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY)).thenReturn(null);
+        doAnswer(invocation -> {
+            SensorEventListener listener = invocation.getArgument(0);
+            Sensor sensor = invocation.getArgument(1);
+            if (sensor.getType() == Sensor.TYPE_GRAVITY) {
+                listener.onSensorChanged(makeSensorEvent(
+                        new float[]{1.0f, 9.0f, 1.0f}, Sensor.TYPE_GRAVITY));
+            } else {
+                listener.onSensorChanged(makeSensorEvent(
+                        new float[]{0.0f}, Sensor.TYPE_PROXIMITY));
+            }
+            return true;
+        }).when(mSensorManager)
+                .registerListener(any(SensorEventListener.class), any(Sensor.class), anyInt());
+
+        assertFalse(SystemStateHelper.isDeviceAtEar(mContext));
+    }
+
+    @SmallTest
+    @Test
+    public void testDeviceIsNotOnEarWithTimeout() {
+        doAnswer(invocation -> {
+            SensorEventListener listener = invocation.getArgument(0);
+            Sensor sensor = invocation.getArgument(1);
+            if (sensor.getType() == Sensor.TYPE_GRAVITY) {
+                // do nothing
+            } else {
+                listener.onSensorChanged(makeSensorEvent(
+                        new float[]{0.0f}, Sensor.TYPE_PROXIMITY));
+            }
+            return true;
+        }).when(mSensorManager)
+                .registerListener(any(SensorEventListener.class), any(Sensor.class), anyInt());
+
+        assertFalse(SystemStateHelper.isDeviceAtEar(mContext));
+    }
+
+    @SmallTest
+    @Test
+    public void testDeviceIsOnEarWithMultiSensorInputs() {
+        doAnswer(invocation -> {
+            SensorEventListener listener = invocation.getArgument(0);
+            Sensor sensor = invocation.getArgument(1);
+            if (sensor.getType() == Sensor.TYPE_GRAVITY) {
+                listener.onSensorChanged(makeSensorEvent(
+                        new float[]{1.0f, 9.0f, 1.0f}, Sensor.TYPE_GRAVITY));
+                listener.onSensorChanged(makeSensorEvent(
+                        new float[]{1.0f, -9.0f, 1.0f}, Sensor.TYPE_GRAVITY));
+                listener.onSensorChanged(makeSensorEvent(
+                        new float[]{1.0f, 0.0f, 8.0f}, Sensor.TYPE_GRAVITY));
+            } else {
+                listener.onSensorChanged(makeSensorEvent(
+                        new float[]{0.0f}, Sensor.TYPE_PROXIMITY));
+            }
+            return true;
+        }).when(mSensorManager)
+                .registerListener(any(SensorEventListener.class), any(Sensor.class), anyInt());
+
+        assertTrue(SystemStateHelper.isDeviceAtEar(mContext));
+        verify(mSensorManager).unregisterListener(any(SensorEventListener.class));
+    }
+
+    private SensorEvent makeSensorEvent(float[] values, int sensorType) throws Exception {
+        SensorEvent event = mock(SensorEvent.class);
+        Sensor mockSensor = mock(Sensor.class);
+        when(mockSensor.getType()).thenReturn(sensorType);
+        FieldSetter.setField(event, SensorEvent.class.getDeclaredField("sensor"), mockSensor);
+        FieldSetter.setField(event, SensorEvent.class.getDeclaredField("values"), values);
+        return event;
+    }
+}
diff --git a/tests/src/com/android/server/telecom/tests/SystemStateProviderTest.java b/tests/src/com/android/server/telecom/tests/SystemStateProviderTest.java
deleted file mode 100644
index 033f929..0000000
--- a/tests/src/com/android/server/telecom/tests/SystemStateProviderTest.java
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * 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.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-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 android.test.suitebuilder.annotation.SmallTest;
-
-import com.android.server.telecom.SystemStateProvider;
-import com.android.server.telecom.SystemStateProvider.SystemStateListener;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/**
- * Unit tests for SystemStateProvider
- */
-@RunWith(JUnit4.class)
-public class SystemStateProviderTest extends TelecomTestCase {
-
-    @Mock Context mContext;
-    @Mock SystemStateListener mSystemStateListener;
-    @Mock UiModeManager mUiModeManager;
-    @Mock Intent mIntentEnter;
-    @Mock Intent mIntentExit;
-
-    @Override
-    @Before
-    public void setUp() throws Exception {
-        super.setUp();
-        MockitoAnnotations.initMocks(this);
-    }
-
-    @Override
-    @After
-    public void tearDown() throws Exception {
-        super.tearDown();
-    }
-
-    @SmallTest
-    @Test
-    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));
-    }
-
-    @SmallTest
-    @Test
-    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());
-    }
-
-    @SmallTest
-    @Test
-    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());
-    }
-
-    @SmallTest
-    @Test
-    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));
-    }
-
-    @SmallTest
-    @Test
-    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
index cbca5e1..512dbe0 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
@@ -22,9 +22,12 @@
 import static android.Manifest.permission.READ_PHONE_STATE;
 import static android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE;
 
+import android.annotation.MainThread;
+import android.annotation.WorkerThread;
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
 import android.content.ComponentName;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
@@ -48,6 +51,7 @@
 import com.android.server.telecom.CallState;
 import com.android.server.telecom.CallsManager;
 import com.android.server.telecom.DefaultDialerCache;
+import com.android.server.telecom.NuisanceCallReporter;
 import com.android.server.telecom.PhoneAccountRegistrar;
 import com.android.server.telecom.TelecomServiceImpl;
 import com.android.server.telecom.TelecomSystem;
@@ -87,6 +91,7 @@
 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;
 
@@ -98,7 +103,7 @@
     public static class CallIntentProcessAdapterFake implements CallIntentProcessor.Adapter {
         @Override
         public void processOutgoingCallIntent(Context context, CallsManager callsManager,
-                Intent intent) {
+                Intent intent, String callingPackage) {
 
         }
 
@@ -121,6 +126,20 @@
         }
     }
 
+    public static class SettingsSecureAdapterFake implements
+        TelecomServiceImpl.SettingsSecureAdapter {
+        @Override
+        public void putStringForUser(ContentResolver resolver, String name, String value,
+            int userHandle) {
+
+        }
+
+        @Override
+        public String getStringForUser(ContentResolver resolver, String name, int userHandle) {
+            return THIRD_PARTY_CALL_SCREENING.flattenToString();
+        }
+    }
+
     private static class AnyStringIn implements ArgumentMatcher<String> {
         private Collection<String> mStrings;
         public AnyStringIn(Collection<String> strings) {
@@ -145,7 +164,10 @@
     @Mock private DefaultDialerCache mDefaultDialerCache;
     private TelecomServiceImpl.SubscriptionManagerAdapter mSubscriptionManagerAdapter =
             spy(new SubscriptionManagerAdapterFake());
+    private TelecomServiceImpl.SettingsSecureAdapter mSettingsSecureAdapter =
+        spy(new SettingsSecureAdapterFake());
     @Mock private UserCallIntentProcessor mUserCallIntentProcessor;
+    @Mock private NuisanceCallReporter mNuisanceCallReporter;
 
     private final TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() { };
 
@@ -160,6 +182,8 @@
             new ComponentName("test", "telComponentName"), "2", Binder.getCallingUserHandle());
     private static final PhoneAccountHandle SIP_PA_HANDLE_CURRENT = new PhoneAccountHandle(
             new ComponentName("test", "sipComponentName"), "3", Binder.getCallingUserHandle());
+    private static final ComponentName THIRD_PARTY_CALL_SCREENING = new ComponentName("com.android" +
+            ".thirdparty", "com.android.thirdparty.callscreeningserviceimpl");
 
     @Override
     @Before
@@ -185,6 +209,8 @@
                 },
                 mDefaultDialerCache,
                 mSubscriptionManagerAdapter,
+                mSettingsSecureAdapter,
+                mNuisanceCallReporter,
                 mLock);
         mTSIBinder = telecomServiceImpl.getBinder();
         mComponentContextFixture.setTelecomManager(mTelecomManager);
@@ -250,7 +276,8 @@
                 makeMultiUserPhoneAccount(TEL_PA_HANDLE_16).build());
 
         PhoneAccountHandle returnedHandle
-                = mTSIBinder.getUserSelectedOutgoingPhoneAccount();
+                = mTSIBinder.getUserSelectedOutgoingPhoneAccount(
+                        TEL_PA_HANDLE_16.getComponentName().getPackageName());
         assertEquals(TEL_PA_HANDLE_16, returnedHandle);
     }
 
diff --git a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
index 4cf7644..1197396 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
@@ -69,6 +69,7 @@
 import com.android.server.telecom.AsyncRingtonePlayer;
 import com.android.server.telecom.BluetoothPhoneServiceImpl;
 import com.android.server.telecom.CallAudioManager;
+import com.android.server.telecom.CallAudioRouteStateMachine;
 import com.android.server.telecom.CallerInfoLookupHelper;
 import com.android.server.telecom.CallsManager;
 import com.android.server.telecom.CallsManagerListenerBase;
@@ -85,8 +86,12 @@
 import com.android.server.telecom.PhoneNumberUtilsAdapterImpl;
 import com.android.server.telecom.ProximitySensorManager;
 import com.android.server.telecom.ProximitySensorManagerFactory;
+import com.android.server.telecom.RoleManagerAdapter;
+import com.android.server.telecom.StatusBarNotifier;
 import com.android.server.telecom.TelecomSystem;
 import com.android.server.telecom.Timeouts;
+import com.android.server.telecom.WiredHeadsetManager;
+import com.android.server.telecom.bluetooth.BluetoothRouteManager;
 import com.android.server.telecom.components.UserCallIntentProcessor;
 import com.android.server.telecom.ui.IncomingCallNotifier;
 import com.android.server.telecom.ui.MissedCallNotifierImpl.MissedCallNotifierImplFactory;
@@ -98,7 +103,9 @@
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
 
+import java.io.File;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -208,6 +215,7 @@
     @Mock AsyncRingtonePlayer mAsyncRingtonePlayer;
     @Mock IncomingCallNotifier mIncomingCallNotifier;
     @Mock ClockProxy mClockProxy;
+    @Mock RoleManagerAdapter mRoleManagerAdapter;
 
     final ComponentName mInCallServiceComponentNameX =
             new ComponentName(
@@ -383,8 +391,8 @@
 
         IInCallAdapter inCallAdapter = mInCallServiceFixtureX.getInCallAdapter();
         inCallAdapter.conference(callId1.mCallId, callId2.mCallId);
-        // Wait for wacky non-deterministic behavior
-        Thread.sleep(200);
+        // Wait for the handler in ConnectionService
+        waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
         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
@@ -402,6 +410,15 @@
     }
 
     private void setupTelecomSystem() throws Exception {
+        // Remove any cached PhoneAccount xml
+        File phoneAccountFile =
+                new File(mComponentContextFixture.getTestDouble()
+                        .getApplicationContext().getFilesDir(),
+                        PhoneAccountRegistrar.FILE_NAME);
+        if (phoneAccountFile.exists()) {
+            phoneAccountFile.delete();
+        }
+
         // Use actual implementations instead of mocking the interface out.
         HeadsetMediaButtonFactory headsetMediaButtonFactory =
                 spy(new HeadsetMediaButtonFactoryF());
@@ -420,48 +437,46 @@
         mClockProxy = mock(ClockProxy.class);
         when(mClockProxy.currentTimeMillis()).thenReturn(TEST_CREATE_TIME);
         when(mClockProxy.elapsedRealtime()).thenReturn(TEST_CREATE_ELAPSED_TIME);
+        when(mRoleManagerAdapter.getCallCompanionApps()).thenReturn(Collections.emptyList());
+        when(mRoleManagerAdapter.getDefaultCallScreeningApp()).thenReturn(null);
+        when(mRoleManagerAdapter.getCarModeDialerApp()).thenReturn(null);
         mTelecomSystem = new TelecomSystem(
                 mComponentContextFixture.getTestDouble(),
-                new MissedCallNotifierImplFactory() {
-                    @Override
-                    public MissedCallNotifier makeMissedCallNotifierImpl(Context context,
-                            PhoneAccountRegistrar phoneAccountRegistrar,
-                            DefaultDialerCache defaultDialerCache) {
-                        return mMissedCallNotifier;
-                    }
-                },
+                (context, phoneAccountRegistrar, defaultDialerCache) -> mMissedCallNotifier,
                 mCallerInfoAsyncQueryFactoryFixture.getTestDouble(),
                 headsetMediaButtonFactory,
                 proximitySensorManagerFactory,
                 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;
-                    }
-                },
-                new ConnectionServiceFocusManager.ConnectionServiceFocusManagerFactory() {
-                    @Override
-                    public ConnectionServiceFocusManager create(
-                            ConnectionServiceFocusManager.CallsManagerRequester requester,
-                            Looper looper) {
-                        return new ConnectionServiceFocusManager(requester, looper);
-                    }
-                },
+                () -> mAudioService,
+                (context, lock, callsManager, phoneAccountRegistrar) -> mBluetoothPhoneServiceImpl,
+                ConnectionServiceFocusManager::new,
                 mTimeoutsAdapter,
                 mAsyncRingtonePlayer,
                 mPhoneNumberUtilsAdapter,
                 mIncomingCallNotifier,
                 (streamType, volume) -> mock(ToneGenerator.class),
-                mClockProxy);
+                new CallAudioRouteStateMachine.Factory() {
+                    @Override
+                    public CallAudioRouteStateMachine create(
+                            Context context,
+                            CallsManager callsManager,
+                            BluetoothRouteManager bluetoothManager,
+                            WiredHeadsetManager wiredHeadsetManager,
+                            StatusBarNotifier statusBarNotifier,
+                            CallAudioManager.AudioServiceFactory audioServiceFactory,
+                            int earpieceControl) {
+                        return new CallAudioRouteStateMachine(context,
+                                callsManager,
+                                bluetoothManager,
+                                wiredHeadsetManager,
+                                statusBarNotifier,
+                                audioServiceFactory,
+                                // Force enable an earpiece for the end-to-end tests
+                                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED);
+                    }
+                },
+                mClockProxy,
+                mRoleManagerAdapter);
 
         mComponentContextFixture.setTelecomManager(new TelecomManager(
                 mComponentContextFixture.getTestDouble(),
@@ -553,8 +568,11 @@
             ConnectionServiceFixture connectionServiceFixture)
             throws Exception {
 
-        return startOutgoingPhoneCallPendingCreateConnection(number, null,
-                connectionServiceFixture, Process.myUserHandle(), VideoProfile.STATE_AUDIO_ONLY);
+        startOutgoingPhoneCallWaitForBroadcaster(number, null,
+                connectionServiceFixture, Process.myUserHandle(), VideoProfile.STATE_AUDIO_ONLY,
+                false /*isEmergency*/);
+
+        return mInCallServiceFixtureX.mLatestCallId;
     }
 
     protected IdPair outgoingCallPhoneAccountSelected(PhoneAccountHandle phoneAccountHandle,
@@ -674,23 +692,22 @@
         Context localAppContext = mComponentContextFixture.getTestDouble().getApplicationContext();
         new UserCallIntentProcessor(localAppContext, userHandle).processIntent(
                 actionCallIntent, null, true /* hasCallAppOp*/, false /* isLocal */);
-        // 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);
         // Wait for handler to start CallerInfo lookup.
         waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
         // Send the CallerInfo lookup reply.
         mCallerInfoAsyncQueryFactoryFixture.mRequests.forEach(
                 CallerInfoAsyncQueryFactoryFixture.Request::reply);
+        if (phoneAccountHandle != null) {
+            mTelecomSystem.getCallsManager().getLatestPostSelectionProcessingFuture().join();
+        }
+        waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
 
         boolean isSelfManaged = phoneAccountHandle == mPhoneAccountSelfManaged.getAccountHandle();
         if (!hasInCallAdapter && !isSelfManaged) {
-            verify(mInCallServiceFixtureX.getTestDouble())
+            verify(mInCallServiceFixtureX.getTestDouble(), timeout(TEST_TIMEOUT))
                     .setInCallAdapter(
                             any(IInCallAdapter.class));
-            verify(mInCallServiceFixtureY.getTestDouble())
+            verify(mInCallServiceFixtureY.getTestDouble(), timeout(TEST_TIMEOUT))
                     .setInCallAdapter(
                             any(IInCallAdapter.class));
         }
@@ -702,7 +719,13 @@
             int videoState) throws Exception {
         startOutgoingPhoneCallWaitForBroadcaster(number,phoneAccountHandle,
                 connectionServiceFixture, initiatingUser, videoState, false /*isEmergency*/);
+        waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
 
+        verifyAndProcessOutgoingCallBroadcast(phoneAccountHandle);
+        return mInCallServiceFixtureX.mLatestCallId;
+    }
+
+    protected void verifyAndProcessOutgoingCallBroadcast(PhoneAccountHandle phoneAccountHandle) {
         ArgumentCaptor<Intent> newOutgoingCallIntent =
                 ArgumentCaptor.forClass(Intent.class);
         ArgumentCaptor<BroadcastReceiver> newOutgoingCallReceiver =
@@ -731,7 +754,6 @@
                     newOutgoingCallIntent.getValue());
         }
 
-        return mInCallServiceFixtureX.mLatestCallId;
     }
 
     // When Telecom is redialing due to an error, we need to make sure the number of connections
@@ -763,14 +785,16 @@
             ConnectionServiceFixture connectionServiceFixture) throws Exception {
 
         // Wait for the focus tracker.
-        waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
+        waitForHandlerAction(mTelecomSystem.getCallsManager()
+                .getConnectionServiceFocusManager().getHandler(), TEST_TIMEOUT);
 
         verify(connectionServiceFixture.getTestDouble())
                 .createConnection(eq(phoneAccountHandle), anyString(), any(ConnectionRequest.class),
                         eq(false)/*isIncoming*/, anyBoolean(), any());
         // Wait for handleCreateConnectionComplete
         waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
-        assertEquals(startingNumConnections + 1, connectionServiceFixture.mConnectionById.size());
+        assertEquals(startingNumConnections + 1,
+                connectionServiceFixture.mConnectionById.size());
 
         // Wait for the callback in ConnectionService#onAdapterAttached to execute.
         waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
@@ -847,14 +871,6 @@
         //Wait for/Verify call blocking happened asynchronously
         incomingCallAddedLatch.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS);
 
-        IContentProvider blockedNumberProvider =
-                mSpyContext.getContentResolver().acquireProvider(BlockedNumberContract.AUTHORITY);
-        verify(blockedNumberProvider, timeout(TEST_TIMEOUT)).call(
-                anyString(),
-                eq(BlockedNumberContract.SystemContract.METHOD_SHOULD_SYSTEM_BLOCK_NUMBER),
-                eq(number),
-                isNotNull(Bundle.class));
-
         // 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
         // is added, future interactions as triggered by the ConnectionService, through the various
@@ -972,6 +988,9 @@
 
             mInCallServiceFixtureX.mInCallAdapter
                     .answerCall(ids.mCallId, videoState);
+            // Wait on the CS focus manager handler
+            waitForHandlerAction(mTelecomSystem.getCallsManager()
+                    .getConnectionServiceFocusManager().getHandler(), TEST_TIMEOUT);
 
             if (!VideoProfile.isVideo(videoState)) {
                 verify(connectionServiceFixture.getTestDouble(), timeout(TEST_TIMEOUT))
diff --git a/tests/src/com/android/server/telecom/tests/VideoProviderProxyTest.java b/tests/src/com/android/server/telecom/tests/VideoProviderProxyTest.java
new file mode 100644
index 0000000..b09aa5b
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/VideoProviderProxyTest.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2018 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.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.os.IBinder;
+import android.telecom.VideoProfile;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.internal.telecom.IVideoProvider;
+import com.android.server.telecom.Analytics;
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CurrentUserProxy;
+import com.android.server.telecom.TelecomSystem;
+import com.android.server.telecom.VideoProviderProxy;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class VideoProviderProxyTest extends TelecomTestCase {
+
+    private TelecomSystem.SyncRoot mLock;
+    private VideoProviderProxy mVideoProviderProxy;
+    @Mock private IVideoProvider mVideoProvider;
+    @Mock private IBinder mIBinder;
+    @Mock private Call mCall;
+    @Mock private Analytics.CallInfo mCallInfo;
+    @Mock private CurrentUserProxy mCurrentUserProxy;
+    @Mock private VideoProviderProxy.Listener mListener;
+
+    @Override
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        MockitoAnnotations.initMocks(this);
+        mLock = new TelecomSystem.SyncRoot() { };
+
+        when(mVideoProvider.asBinder()).thenReturn(mIBinder);
+        doNothing().when(mIBinder).linkToDeath(any(), anyInt());
+        when(mCall.getAnalytics()).thenReturn(mCallInfo);
+        doNothing().when(mCallInfo).addVideoEvent(anyInt(), anyInt());
+        doNothing().when(mCall).maybeEnableSpeakerForVideoUpgrade(anyInt());
+        mVideoProviderProxy = new VideoProviderProxy(mLock, mVideoProvider, mCall,
+                mCurrentUserProxy);
+        mVideoProviderProxy.addListener(mListener);
+    }
+
+    /**
+     * Tests the case where we receive a request to upgrade to video, except:
+     * 1. Phone account says we support video.
+     * 2. Call says we don't support video.
+     *
+     * Ensures that we send back a response immediately to indicate the call should remain as
+     * audio-only.
+     * @throws Exception
+     */
+    @SmallTest
+    @Test
+    public void testReceiveUpgradeRequestWhenLocalDoesntSupportVideo() throws Exception {
+        // Given a call which supports video at the phone account level, but is not currently
+        // marked as supporting video locally.
+        when(mCall.isLocallyVideoCapable()).thenReturn(false);
+        when(mCall.isVideoCallingSupportedByPhoneAccount()).thenReturn(true);
+
+        // Simulate receiving a request to upgrade to video.
+        mVideoProviderProxy.getVideoCallListenerBinder().receiveSessionModifyRequest(
+                new VideoProfile(VideoProfile.STATE_BIDIRECTIONAL));
+
+        // Make sure that we send back a response rejecting the request.
+        ArgumentCaptor<VideoProfile> capturedProfile = ArgumentCaptor.forClass(VideoProfile.class);
+        verify(mVideoProvider).sendSessionModifyResponse(capturedProfile.capture());
+        assertEquals(VideoProfile.STATE_AUDIO_ONLY, capturedProfile.getValue().getVideoState());
+    }
+
+    /**
+     * Tests the case where we receive a request to upgrade to video and video is supported.
+     * @throws Exception
+     */
+    @SmallTest
+    @Test
+    public void testReceiveUpgradeRequestWhenVideoIsSupported() throws Exception {
+        // Given a call which supports video at the phone account level, and is currently marked as
+        // supporting video locally.
+        when(mCall.isLocallyVideoCapable()).thenReturn(true);
+        when(mCall.isVideoCallingSupportedByPhoneAccount()).thenReturn(true);
+
+        // Simulate receiving a request to upgrade to video.
+        mVideoProviderProxy.getVideoCallListenerBinder().receiveSessionModifyRequest(
+                new VideoProfile(VideoProfile.STATE_BIDIRECTIONAL));
+
+        // Ensure it gets proxied back to the caller.
+
+        ArgumentCaptor<VideoProfile> capturedProfile = ArgumentCaptor.forClass(VideoProfile.class);
+        verify(mListener).onSessionModifyRequestReceived(any(), capturedProfile.capture());
+        assertEquals(VideoProfile.STATE_BIDIRECTIONAL, capturedProfile.getValue().getVideoState());
+    }
+
+    /**
+     * Tests the case where dialer requests an upgrade to video; we should try to change to speaker.
+     * @throws Exception
+     */
+    @SmallTest
+    @Test
+    public void testTryToEnableSpeakerOnVideoUpgrade() throws Exception {
+        mVideoProviderProxy.onSendSessionModifyRequest(
+                new VideoProfile(VideoProfile.STATE_AUDIO_ONLY),
+                new VideoProfile(VideoProfile.STATE_BIDIRECTIONAL));
+        verify(mCall).maybeEnableSpeakerForVideoUpgrade(eq(VideoProfile.STATE_BIDIRECTIONAL));
+    }
+}
diff --git a/tests/src/com/android/server/telecom/tests/VideoProviderTest.java b/tests/src/com/android/server/telecom/tests/VideoProviderTest.java
index 75dc36f..eacecf9 100644
--- a/tests/src/com/android/server/telecom/tests/VideoProviderTest.java
+++ b/tests/src/com/android/server/telecom/tests/VideoProviderTest.java
@@ -34,6 +34,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.UserHandle;
+import android.telecom.Connection;
 import android.telecom.Connection.VideoProvider;
 import android.telecom.InCallService;
 import android.telecom.InCallService.VideoCall;
@@ -96,7 +97,8 @@
         super.setUp();
         mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
         mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
-
+        mConnectionServiceFixtureA.mConnectionServiceDelegate.mCapabilities
+                |= Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL;
         mCallIds = startAndMakeActiveOutgoingCall(
                 "650-555-1212",
                 mPhoneAccountA0.getAccountHandle(),