[automerger skipped] Import translations. DO NOT MERGE ANYWHERE am: 68e5094069 -s ours

am skip reason: subject contains skip directive

Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/services/Telecomm/+/15860030

Change-Id: Ied50f472c05acc3749eaa8e28dfb54682bb8bfaa
diff --git a/Android.bp b/Android.bp
index 0d89b00..b7eb450 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1,3 +1,7 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 genrule {
     name: "statslog-telecom-java-gen",
     tools: ["stats-log-api-gen"],
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 7c57599..45e3151 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -15,80 +15,76 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
-        package="com.android.server.telecom"
-        coreApp="true"
-        android:sharedUserId="android.uid.system">
+     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+     package="com.android.server.telecom"
+     coreApp="true"
+     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" />
+    <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
          the user presses "home". -->
-    <uses-permission android:name="android.permission.BIND_CONNECTION_SERVICE" />
-    <uses-permission android:name="android.permission.BIND_INCALL_SERVICE" />
-    <uses-permission android:name="android.permission.BLUETOOTH" />
-    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
-    <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.HANDLE_CAR_MODE_CHANGES" />
-    <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" />
-    <uses-permission android:name="android.permission.MANAGE_ROLE_HOLDERS" />
-    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
+    <uses-permission android:name="android.permission.BIND_CONNECTION_SERVICE"/>
+    <uses-permission android:name="android.permission.BIND_INCALL_SERVICE"/>
+    <uses-permission android:name="android.permission.BLUETOOTH"/>
+    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
+    <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.HANDLE_CAR_MODE_CHANGES"/>
+    <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"/>
+    <uses-permission android:name="android.permission.MANAGE_ROLE_HOLDERS"/>
+    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
     <!-- Required to determine source of ongoing audio recordings. -->
-    <uses-permission android:name="android.permission.MODIFY_AUDIO_ROUTING" />
-    <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
-    <uses-permission android:name="android.permission.READ_CALL_LOG" />
-    <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
-    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.SEND_SMS" />
-    <uses-permission android:name="android.permission.STOP_APP_SWITCHES" />
-    <uses-permission android:name="android.permission.VIBRATE" />
-    <uses-permission android:name="android.permission.WRITE_CALL_LOG" />
-    <uses-permission android:name="android.permission.WAKE_LOCK" />
-    <uses-permission android:name="android.permission.READ_BLOCKED_NUMBERS" />
-    <uses-permission android:name="android.permission.WRITE_BLOCKED_NUMBERS" />
-    <uses-permission android:name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME" />
+    <uses-permission android:name="android.permission.MODIFY_AUDIO_ROUTING"/>
+    <uses-permission android:name="android.permission.MODIFY_PHONE_STATE"/>
+    <uses-permission android:name="android.permission.READ_CALL_LOG"/>
+    <uses-permission android:name="android.permission.READ_DEVICE_CONFIG"/>
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.READ_PROJECTION_STATE"/>
+    <uses-permission android:name="android.permission.SEND_SMS"/>
+    <uses-permission android:name="android.permission.STOP_APP_SWITCHES"/>
+    <uses-permission android:name="android.permission.VIBRATE"/>
+    <uses-permission android:name="android.permission.WRITE_CALL_LOG"/>
+    <uses-permission android:name="android.permission.WAKE_LOCK"/>
+    <uses-permission android:name="android.permission.READ_BLOCKED_NUMBERS"/>
+    <uses-permission android:name="android.permission.WRITE_BLOCKED_NUMBERS"/>
+    <uses-permission android:name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"/>
 
-    <permission
-            android:name="android.permission.BROADCAST_CALLLOG_INFO"
-            android:label="Broadcast the call type/duration information"
-            android:protectionLevel="signature|system"/>
+    <permission android:name="android.permission.BROADCAST_CALLLOG_INFO"
+         android:label="Broadcast the call type/duration information"
+         android:protectionLevel="signature|system"/>
 
-    <permission
-            android:name="android.permission.PROCESS_CALLLOG_INFO"
-            android:label="Register to handle the broadcasted call type/duration information"
-            android:protectionLevel="signature|system"/>
+    <permission android:name="android.permission.PROCESS_CALLLOG_INFO"
+         android:label="Register to handle the broadcasted call type/duration information"
+         android:protectionLevel="signature|system"/>
 
-    <permission
-            android:name="android.permission.BROADCAST_PHONE_ACCOUNT_REGISTRATION"
-            android:label="Broadcast phone account registration"
-            android:protectionLevel="signature|system"/>
+    <permission android:name="android.permission.BROADCAST_PHONE_ACCOUNT_REGISTRATION"
+         android:label="Broadcast phone account registration"
+         android:protectionLevel="signature|system"/>
 
-    <permission
-            android:name="android.permission.PROCESS_PHONE_ACCOUNT_REGISTRATION"
-            android:label="Process phone account registration"
-            android:protectionLevel="signature|system"/>
+    <permission android:name="android.permission.PROCESS_PHONE_ACCOUNT_REGISTRATION"
+         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"/>
+    <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"
-            android:supportsRtl="true"
-            android:process="system"
-            android:usesCleartextTraffic="false"
-            android:defaultToDeviceProtectedStorage="true"
-            android:directBootAware="true">
+         android:icon="@mipmap/ic_launcher_phone"
+         android:allowBackup="false"
+         android:supportsRtl="true"
+         android:process="system"
+         android:usesCleartextTraffic="false"
+         android:defaultToDeviceProtectedStorage="true"
+         android:directBootAware="true">
 
         <!-- CALL vs CALL_PRIVILEGED vs CALL_EMERGENCY
              We have three different intents through which a call can be initiated each with its
@@ -104,60 +100,61 @@
 
         <!-- Activity that displays UI for managing blocked numbers. -->
         <activity android:name=".settings.BlockedNumbersActivity"
-                  android:label="@string/blocked_numbers"
-                  android:configChanges="orientation|screenSize|keyboardHidden"
-                  android:theme="@style/Theme.Telecom.BlockedNumbers"
-                  android:process=":ui"
-                  android:exported="true">
+             android:label="@string/blocked_numbers"
+             android:configChanges="orientation|screenSize|keyboardHidden"
+             android:theme="@style/Theme.Telecom.BlockedNumbers"
+             android:process=":ui"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.telecom.action.MANAGE_BLOCKED_NUMBERS" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.telecom.action.MANAGE_BLOCKED_NUMBERS"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
 
         <activity android:name=".settings.CallBlockDisabledActivity"
-                android:configChanges="keyboardHidden|orientation|screenSize"
-                android:excludeFromRecents="true"
-                android:launchMode="singleInstance"
-                android:theme="@style/Theme.Telecomm.Transparent"
-                android:process=":ui">
+             android:configChanges="keyboardHidden|orientation|screenSize"
+             android:excludeFromRecents="true"
+             android:launchMode="singleInstance"
+             android:theme="@style/Theme.Telecomm.Transparent"
+             android:process=":ui">
         </activity>
 
         <!-- Activity that starts the outgoing call process by listening to CALL intent which
-             contain contact information in the intent's data. CallActivity handles any data
-             URL with the schemes "tel", "sip", and "voicemail". It also handles URLs linked to
-             contacts provider entries. Any data not fitting the schema described is ignored. -->
+                         contain contact information in the intent's data. CallActivity handles any data
+                         URL with the schemes "tel", "sip", and "voicemail". It also handles URLs linked to
+                         contacts provider entries. Any data not fitting the schema described is ignored. -->
         <activity android:name=".components.UserCallActivity"
-                android:label="@string/userCallActivityLabel"
-                android:theme="@style/Theme.Telecomm.Transparent"
-                android:permission="android.permission.CALL_PHONE"
-                android:excludeFromRecents="true"
-                android:process=":ui">
+             android:label="@string/userCallActivityLabel"
+             android:theme="@style/Theme.Telecomm.Transparent"
+             android:permission="android.permission.CALL_PHONE"
+             android:excludeFromRecents="true"
+             android:process=":ui"
+             android:exported="true">
             <!-- CALL action intent filters for the various ways of initiating an outgoing call. -->
             <intent-filter>
-                <action android:name="android.intent.action.CALL" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:scheme="tel" />
+                <action android:name="android.intent.action.CALL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:scheme="tel"/>
             </intent-filter>
             <!-- Specify an icon for SIP calls so that quick contacts widget shows a special SIP
-                 icon for calls to SIP addresses. -->
+                                 icon for calls to SIP addresses. -->
             <intent-filter android:icon="@drawable/ic_launcher_sip_call">
-                <action android:name="android.intent.action.CALL" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:scheme="sip" />
+                <action android:name="android.intent.action.CALL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:scheme="sip"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.CALL" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:scheme="voicemail" />
+                <action android:name="android.intent.action.CALL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:scheme="voicemail"/>
             </intent-filter>
             <!-- Omit default category below so that all Intents sent to this filter must be
-                 explicit. -->
+                                 explicit. -->
             <intent-filter>
-                <action android:name="android.intent.action.CALL" />
-                <data android:mimeType="vnd.android.cursor.item/phone" />
-                <data android:mimeType="vnd.android.cursor.item/phone_v2" />
-                <data android:mimeType="vnd.android.cursor.item/person" />
+                <action android:name="android.intent.action.CALL"/>
+                <data android:mimeType="vnd.android.cursor.item/phone"/>
+                <data android:mimeType="vnd.android.cursor.item/phone_v2"/>
+                <data android:mimeType="vnd.android.cursor.item/person"/>
             </intent-filter>
         </activity>
 
@@ -167,30 +164,31 @@
              processed. High priority of 1000 is used in all intent filters to prevent anything but
              the system from processing this intent (b/8871505). -->
         <activity-alias android:name="PrivilegedCallActivity"
-                android:targetActivity=".components.UserCallActivity"
-                android:permission="android.permission.CALL_PRIVILEGED"
-                android:process=":ui">
+             android:targetActivity=".components.UserCallActivity"
+             android:permission="android.permission.CALL_PRIVILEGED"
+             android:exported="true"
+             android:process=":ui">
             <intent-filter android:priority="1000">
-                <action android:name="android.intent.action.CALL_PRIVILEGED" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:scheme="tel" />
+                <action android:name="android.intent.action.CALL_PRIVILEGED"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:scheme="tel"/>
             </intent-filter>
             <intent-filter android:priority="1000"
-                    android:icon="@drawable/ic_launcher_sip_call">
-                <action android:name="android.intent.action.CALL_PRIVILEGED" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:scheme="sip" />
+                 android:icon="@drawable/ic_launcher_sip_call">
+                <action android:name="android.intent.action.CALL_PRIVILEGED"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:scheme="sip"/>
             </intent-filter>
             <intent-filter android:priority="1000">
-                <action android:name="android.intent.action.CALL_PRIVILEGED" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:scheme="voicemail" />
+                <action android:name="android.intent.action.CALL_PRIVILEGED"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:scheme="voicemail"/>
             </intent-filter>
             <intent-filter android:priority="1000">
-                <action android:name="android.intent.action.CALL_PRIVILEGED" />
-                <data android:mimeType="vnd.android.cursor.item/phone" />
-                <data android:mimeType="vnd.android.cursor.item/phone_v2" />
-                <data android:mimeType="vnd.android.cursor.item/person" />
+                <action android:name="android.intent.action.CALL_PRIVILEGED"/>
+                <data android:mimeType="vnd.android.cursor.item/phone"/>
+                <data android:mimeType="vnd.android.cursor.item/phone_v2"/>
+                <data android:mimeType="vnd.android.cursor.item/person"/>
             </intent-filter>
         </activity-alias>
 
@@ -200,123 +198,130 @@
              1000 is used in all intent filters to prevent anything but the system from processing
              this intent (b/8871505). -->
         <!-- TODO: Is there really a notion of an emergency SIP number? If not, can
-             that scheme be removed from this activity? -->
+                         that scheme be removed from this activity? -->
         <activity-alias android:name="EmergencyCallActivity"
-                android:targetActivity=".components.UserCallActivity"
-                android:permission="android.permission.CALL_PRIVILEGED"
-                android:process=":ui">
+             android:targetActivity=".components.UserCallActivity"
+             android:permission="android.permission.CALL_PRIVILEGED"
+             android:exported="true"
+             android:process=":ui">
             <intent-filter android:priority="1000">
-                <action android:name="android.intent.action.CALL_EMERGENCY" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:scheme="tel" />
+                <action android:name="android.intent.action.CALL_EMERGENCY"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:scheme="tel"/>
             </intent-filter>
             <intent-filter android:priority="1000"
-                    android:icon="@drawable/ic_launcher_sip_call">
-                <action android:name="android.intent.action.CALL_EMERGENCY" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:scheme="sip" />
+                 android:icon="@drawable/ic_launcher_sip_call">
+                <action android:name="android.intent.action.CALL_EMERGENCY"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:scheme="sip"/>
             </intent-filter>
             <intent-filter android:priority="1000">
-                <action android:name="android.intent.action.CALL_EMERGENCY" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:scheme="voicemail" />
+                <action android:name="android.intent.action.CALL_EMERGENCY"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:scheme="voicemail"/>
             </intent-filter>
             <intent-filter android:priority="1000">
-                <action android:name="android.intent.action.CALL_EMERGENCY" />
-                <data android:mimeType="vnd.android.cursor.item/phone" />
-                <data android:mimeType="vnd.android.cursor.item/phone_v2" />
-                <data android:mimeType="vnd.android.cursor.item/person" />
+                <action android:name="android.intent.action.CALL_EMERGENCY"/>
+                <data android:mimeType="vnd.android.cursor.item/phone"/>
+                <data android:mimeType="vnd.android.cursor.item/phone_v2"/>
+                <data android:mimeType="vnd.android.cursor.item/person"/>
             </intent-filter>
         </activity-alias>
 
-        <receiver android:name=".components.TelecomBroadcastReceiver" android:exported="false"
-                android:process="system">
+        <receiver android:name=".components.TelecomBroadcastReceiver"
+             android:exported="false"
+             android:process="system">
             <intent-filter>
-                <action android:name="com.android.server.telecom.ACTION_CLEAR_MISSED_CALLS" />
-                <action android:name="com.android.server.telecom.ACTION_CALL_BACK_FROM_NOTIFICATION" />
-                <action android:name="com.android.server.telecom.ACTION_SEND_SMS_FROM_NOTIFICATION" />
-                <action android:name="com.android.server.telecom.ACTION_ANSWER_FROM_NOTIFICATION" />
-                <action android:name="com.android.server.telecom.ACTION_REJECT_FROM_NOTIFICATION" />
-                <action android:name="com.android.server.telecom.PROCEED_WITH_CALL" />
-                <action android:name="com.android.server.telecom.CANCEL_CALL" />
-                <action android:name="com.android.server.telecom.PROCEED_WITH_REDIRECTED_CALL" />
-                <action android:name="com.android.server.telecom.CANCEL_REDIRECTED_CALL" />
+                <action android:name="com.android.server.telecom.ACTION_CLEAR_MISSED_CALLS"/>
+                <action android:name="com.android.server.telecom.ACTION_CALL_BACK_FROM_NOTIFICATION"/>
+                <action android:name="com.android.server.telecom.ACTION_SEND_SMS_FROM_NOTIFICATION"/>
+                <action android:name="com.android.server.telecom.ACTION_ANSWER_FROM_NOTIFICATION"/>
+                <action android:name="com.android.server.telecom.ACTION_REJECT_FROM_NOTIFICATION"/>
+                <action android:name="com.android.server.telecom.PROCEED_WITH_CALL"/>
+                <action android:name="com.android.server.telecom.CANCEL_CALL"/>
+                <action android:name="com.android.server.telecom.PROCEED_WITH_REDIRECTED_CALL"/>
+                <action android:name="com.android.server.telecom.CANCEL_REDIRECTED_CALL"/>
             </intent-filter>
         </receiver>
 
         <receiver android:name=".components.AppUninstallBroadcastReceiver"
-                android:process="system">
+             android:process="system"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.PACKAGE_FULLY_REMOVED" />
-                <data android:scheme="package" />
+                <action android:name="android.intent.action.PACKAGE_FULLY_REMOVED"/>
+                <data android:scheme="package"/>
             </intent-filter>
         </receiver>
 
         <activity android:name=".RespondViaSmsSettings"
-                  android:label="@string/respond_via_sms_setting_title"
-                  android:configChanges="orientation|screenSize|keyboardHidden"
-                  android:theme="@style/Theme.Telecom.DialerSettings"
-                  android:process=":ui">
+             android:label="@string/respond_via_sms_setting_title"
+             android:configChanges="orientation|screenSize|keyboardHidden"
+             android:theme="@style/Theme.Telecom.DialerSettings"
+             android:process=":ui"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <action android:name="android.telecom.action.SHOW_RESPOND_VIA_SMS_SETTINGS" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.intent.action.MAIN"/>
+                <action android:name="android.telecom.action.SHOW_RESPOND_VIA_SMS_SETTINGS"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
 
         <activity android:name=".settings.EnableAccountPreferenceActivity"
-                  android:label="@string/enable_account_preference_title"
-                  android:configChanges="orientation|screenSize|keyboardHidden"
-                  android:theme="@style/Theme.Telecom.DialerSettings"
-                  android:process=":ui">
+             android:label="@string/enable_account_preference_title"
+             android:configChanges="orientation|screenSize|keyboardHidden"
+             android:theme="@style/Theme.Telecom.DialerSettings"
+             android:process=":ui"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
 
         <activity android:name=".components.ErrorDialogActivity"
-                android:configChanges="orientation|screenSize|keyboardHidden"
-                android:excludeFromRecents="true"
-                android:launchMode="singleInstance"
-                android:theme="@style/Theme.Telecomm.Transparent"
-                android:process=":ui">
+             android:configChanges="orientation|screenSize|keyboardHidden"
+             android:excludeFromRecents="true"
+             android:launchMode="singleInstance"
+             android:theme="@style/Theme.Telecomm.Transparent"
+             android:process=":ui">
         </activity>
 
         <activity android:name=".ui.ConfirmCallDialogActivity"
-                android:configChanges="orientation|screenSize|keyboardHidden"
-                android:excludeFromRecents="true"
-                android:launchMode="singleInstance"
-                android:theme="@style/Theme.Telecomm.Transparent"
-                android:process=":ui">
+             android:configChanges="orientation|screenSize|keyboardHidden"
+             android:excludeFromRecents="true"
+             android:launchMode="singleInstance"
+             android:theme="@style/Theme.Telecomm.Transparent"
+             android:process=":ui">
         </activity>
 
         <activity android:name=".ui.CallRedirectionTimeoutDialogActivity"
-                  android:configChanges="orientation|screenSize|keyboardHidden"
-                  android:excludeFromRecents="true"
-                  android:launchMode="singleInstance"
-                  android:theme="@style/Theme.Telecomm.Transparent"
-                  android:process=":ui">
+             android:configChanges="orientation|screenSize|keyboardHidden"
+             android:excludeFromRecents="true"
+             android:launchMode="singleInstance"
+             android:theme="@style/Theme.Telecomm.Transparent"
+             android:process=":ui">
         </activity>
 
         <activity android:name=".ui.TelecomDeveloperMenu"
-                  android:label="@string/developer_title"
-                  android:exported="false"
-                  android:process=":ui" />
+             android:label="@string/developer_title"
+             android:exported="false"
+             android:process=":ui"/>
 
         <service android:name=".components.BluetoothPhoneService"
-                android:singleUser="true"
-                android:process="system">
+             android:singleUser="true"
+             android:process="system"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.bluetooth.IBluetoothHeadsetPhone" />
+                <action android:name="android.bluetooth.IBluetoothHeadsetPhone"/>
             </intent-filter>
         </service>
 
         <service android:name=".components.TelecomService"
-                android:singleUser="true"
-                android:process="system">
+             android:singleUser="true"
+             android:process="system"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.telecom.ITelecomService" />
+                <action android:name="android.telecom.ITelecomService"/>
             </intent-filter>
         </service>
 
diff --git a/OWNERS b/OWNERS
index 94409ef..39be2c1 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,4 +1,6 @@
 breadley@google.com
 hallliu@google.com
 tgunn@google.com
-paulye@google.com
+xiaotonj@google.com
+shuoq@google.com
+rgreenwalt@google.com
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 75feeb6..2e14650 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -16,5 +16,15 @@
         }
       ]
     }
+  ],
+  "presubmit-large": [
+    {
+      "name": "CtsTelecomTestCases",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
+    }
   ]
 }
diff --git a/res/raw/InCallQualityNotification.ogg b/res/raw/InCallQualityNotification.ogg
new file mode 100644
index 0000000..84c029a
--- /dev/null
+++ b/res/raw/InCallQualityNotification.ogg
Binary files differ
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index 2857be3..408021f 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"Oproepbestuur"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"Foonoproepe"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"Bel"</string>
     <string name="unknown" msgid="6993977514360123431">"Onbekend"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"Gemiste oproep"</string>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index df8215c..a5fe872 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"የጥሪ አስተዳደር"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"የስልክ ጥሪዎች"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"ስልክ"</string>
     <string name="unknown" msgid="6993977514360123431">"ያልታወቀ"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"ያመለጠጥሪ"</string>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index 7e204d0..8dc37e7 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"إدارة المكالمات"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"مكالمات هاتفية"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"الهاتف"</string>
     <string name="unknown" msgid="6993977514360123431">"غير معروف"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"مكالمة فائتة"</string>
diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml
index 2f5de68..0808bbf 100644
--- a/res/values-as/strings.xml
+++ b/res/values-as/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"কল পৰিচালনা"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"ফ’ন কল"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"ফ’ন"</string>
     <string name="unknown" msgid="6993977514360123431">"অজ্ঞাত"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"মিছ্ড কল"</string>
diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml
index 53817b3..e38e653 100644
--- a/res/values-az/strings.xml
+++ b/res/values-az/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"Zənglərin idarəolunması"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"Telefon Zəngləri"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"Telefon"</string>
     <string name="unknown" msgid="6993977514360123431">"Naməlum"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"Buraxılmış zəng"</string>
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
index eba8c5c..5646cb4 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"Upravljanje pozivima"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"Telefonski pozivi"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"Telefon"</string>
     <string name="unknown" msgid="6993977514360123431">"Nepoznato"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"Propušten poziv"</string>
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
index b6dd707..b92f69a 100644
--- a/res/values-be/strings.xml
+++ b/res/values-be/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"Кіраванне выклікамі"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"Тэлефонныя выклікі"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"Тэлефон"</string>
     <string name="unknown" msgid="6993977514360123431">"Невядомы"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"Прапушчаны выклік"</string>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index 7afddc2..84e1a3d 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"Управление на обажданията"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"Телефонни обаждания"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"Телефон"</string>
     <string name="unknown" msgid="6993977514360123431">"Неизвестен номер"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"Пропуснато обаждане"</string>
diff --git a/res/values-bn/strings.xml b/res/values-bn/strings.xml
index cf04d40..3da181c 100644
--- a/res/values-bn/strings.xml
+++ b/res/values-bn/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"কল ব্যবস্থাপনা"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"ফোন কল"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"ফোন"</string>
     <string name="unknown" msgid="6993977514360123431">"অজানা"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"মিসড কল"</string>
diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml
index 1968357..d56feeb 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"Upravljanje pozivima"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"Telefonski pozivi"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"Telefon"</string>
     <string name="unknown" msgid="6993977514360123431">"Nepoznato"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"Propušteni poziv"</string>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index 69a7b6e..7e2c81e 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"Gestió de trucades"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"Trucades telefòniques"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"Telèfon"</string>
     <string name="unknown" msgid="6993977514360123431">"Desconegut"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"Trucada perduda"</string>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index 1730a30..ec6125b 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"Správa hovorů"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"Telefonní hovory"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"Telefon"</string>
     <string name="unknown" msgid="6993977514360123431">"Neznámý volající"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"Zmeškaný hovor"</string>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index 6a208c4..d4bc20e 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"Opkaldsstyring"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"Telefonopkald"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"Opkald"</string>
     <string name="unknown" msgid="6993977514360123431">"Ukendt"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"Ubesvaret opkald"</string>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index f60d207..bcf62ed 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"Anrufverwaltung"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"Telefonanrufe"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"Telefon"</string>
     <string name="unknown" msgid="6993977514360123431">"Unbekannt"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"Entgangener Anruf"</string>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index ab523c1..a4498ce 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"Διαχείριση κλήσεων"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"Τηλεφωνικές κλήσεις"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"Τηλέφωνο"</string>
     <string name="unknown" msgid="6993977514360123431">"Άγνωστος"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"Αναπάντητη κλήση"</string>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index 7ce0e53..d422e58 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"Call Management"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"Phone calls"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"Phone"</string>
     <string name="unknown" msgid="6993977514360123431">"Unknown"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"Missed call"</string>
diff --git a/res/values-en-rCA/strings.xml b/res/values-en-rCA/strings.xml
index 7ce0e53..d422e58 100644
--- a/res/values-en-rCA/strings.xml
+++ b/res/values-en-rCA/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"Call Management"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"Phone calls"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"Phone"</string>
     <string name="unknown" msgid="6993977514360123431">"Unknown"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"Missed call"</string>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index 7ce0e53..d422e58 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"Call Management"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"Phone calls"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"Phone"</string>
     <string name="unknown" msgid="6993977514360123431">"Unknown"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"Missed call"</string>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index 7ce0e53..d422e58 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"Call Management"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"Phone calls"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"Phone"</string>
     <string name="unknown" msgid="6993977514360123431">"Unknown"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"Missed call"</string>
diff --git a/res/values-en-rXC/strings.xml b/res/values-en-rXC/strings.xml
index 3b3a983..f533e0a 100644
--- a/res/values-en-rXC/strings.xml
+++ b/res/values-en-rXC/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‎‏‎‏‎‏‎‏‎‏‏‎‏‎‎‏‎‎‎‎‎‎‎‎‏‏‏‏‎‏‏‏‎‎‏‏‏‎‏‏‏‎‎‏‏‏‎‏‎‎‎‎‎‎‏‎‏‏‎‎‏‏‏‎Call Management‎‏‎‎‏‎"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‎‏‏‏‎‎‏‎‏‎‏‎‏‎‏‏‏‎‏‎‏‎‎‎‎‏‎‏‏‎‎‏‏‏‎‏‏‏‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‎‏‎‏‎‎‎‏‏‎Phone Calls‎‏‎‎‏‎"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‎‏‏‎‎‏‎‎‎‎‎‏‎‎‎‏‏‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‎‏‏‎‎‏‎‎‏‎‏‎‏‎‏‎‎‎‎Phone‎‏‎‎‏‎"</string>
     <string name="unknown" msgid="6993977514360123431">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‎‎‏‎‏‏‏‏‏‏‎‎‏‎‏‏‏‏‎‏‎‎‎‎‎‎‏‎‎‏‏‎‎‎‎‎‎‏‎‎‏‏‏‎Unknown‎‏‎‎‏‎"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‏‏‎‎‎‏‏‎‎‎‏‏‏‎‏‎‎‎‎‏‏‎‏‏‎‎‏‏‎‏‎‏‎‏‎‏‎‏‏‎‏‎‏‎‏‏‎‎‎‏‏‏‎‏‎‏‎‎‏‎‏‏‏‎‎Missed call‎‏‎‎‏‎"</string>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index 826b877..9da169d 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"Administración de llamadas"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"Llamadas telefónicas"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"Teléfono"</string>
     <string name="unknown" msgid="6993977514360123431">"Desconocida"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"Llamada perdida"</string>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index b7b9cff..68499c2 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"Gestión de llamadas"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"Llamadas"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"Teléfono"</string>
     <string name="unknown" msgid="6993977514360123431">"Desconocido"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"Llamada perdida"</string>
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
index bb291ad..34bd333 100644
--- a/res/values-et/strings.xml
+++ b/res/values-et/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"Kõnehaldus"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"Telefonikõned"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"Telefon"</string>
     <string name="unknown" msgid="6993977514360123431">"Tundmatu"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"Vastamata kõne"</string>
diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml
index 1191f52..79b9524 100644
--- a/res/values-eu/strings.xml
+++ b/res/values-eu/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"Deien kudeaketa"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"Telefono-deiak"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"Telefonoa"</string>
     <string name="unknown" msgid="6993977514360123431">"Ezezaguna"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"Dei galdua"</string>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index c873159..991ee5b 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"مدیریت تماس"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"تماس‌های تلفنی"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"تلفن"</string>
     <string name="unknown" msgid="6993977514360123431">"ناشناس"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"تماس بی پاسخ"</string>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index 74cdf7e..3ad0c23 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"Puhelujen hallinta"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"Puhelut"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"Puhelin"</string>
     <string name="unknown" msgid="6993977514360123431">"Tuntematon"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"Vastaamatta jäänyt puhelu"</string>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index 5061eda..46bc45a 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"Gestion des appels"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"Appels téléphoniques"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"Téléphone"</string>
     <string name="unknown" msgid="6993977514360123431">"Inconnu"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"Appel manqué"</string>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index c2a95a2..bee3695 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"Gestion des appels"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"Appels téléphoniques"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"Téléphone"</string>
     <string name="unknown" msgid="6993977514360123431">"Inconnu"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"Appel manqué"</string>
diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml
index c62fffc..cc5efc2 100644
--- a/res/values-gl/strings.xml
+++ b/res/values-gl/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"Xestión de chamadas"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"Chamadas telefónicas"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"Teléfono"</string>
     <string name="unknown" msgid="6993977514360123431">"Descoñecido"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"Chamada perdida"</string>
diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml
index 731618a..e04f38c 100644
--- a/res/values-gu/strings.xml
+++ b/res/values-gu/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"કૉલ મેનેજમેન્ટ"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"Phone Calls"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"ફોન"</string>
     <string name="unknown" msgid="6993977514360123431">"અજાણ્યું"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"ચૂકી ગયેલો કૉલ"</string>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index 4562a5f..94fcdb1 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"कॉल मैनेजमेंट"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"फ़ोन कॉल"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"फ़ोन"</string>
     <string name="unknown" msgid="6993977514360123431">"अज्ञात"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"छूटी कॉल"</string>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index d341565..7928978 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"Upravljanje pozivima"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"Telefonski pozivi"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"Telefon"</string>
     <string name="unknown" msgid="6993977514360123431">"Nepoznato"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"Propušteni poziv"</string>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index 55e5535..7bbff68 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"Híváskezelés"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"Telefonhívások"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"Telefon"</string>
     <string name="unknown" msgid="6993977514360123431">"Ismeretlen"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"Nem fogadott hívás"</string>
diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml
index 98a3bc1..851e4e9 100644
--- a/res/values-hy/strings.xml
+++ b/res/values-hy/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"Զանգերի կառավարում"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"Հեռախոսազանգեր"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"Հեռախոս"</string>
     <string name="unknown" msgid="6993977514360123431">"Անհայտ"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"Բաց թողնված զանգ"</string>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index f479340..d71cd15 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"Pengelolaan Panggilan Telepon"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"Panggilan Telepon"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"Telepon"</string>
     <string name="unknown" msgid="6993977514360123431">"Tidak diketahui"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"Panggilan tak terjawab"</string>
diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml
index 442b9d5..2c5ef10 100644
--- a/res/values-is/strings.xml
+++ b/res/values-is/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"Símtalastjórnun"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"Símtöl"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"Sími"</string>
     <string name="unknown" msgid="6993977514360123431">"Óþekkt"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"Ósvarað símtal"</string>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index 60f32d7..97a23d0 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"Gestione chiamate"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"Telefonate"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"Telefono"</string>
     <string name="unknown" msgid="6993977514360123431">"Sconosciuto"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"Chiamata persa"</string>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index 17e4a1b..8811efb 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"ניהול השיחות"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"שיחות טלפון"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"טלפון"</string>
     <string name="unknown" msgid="6993977514360123431">"לא ידוע"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"שיחה שלא נענתה"</string>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index a11d8d2..d78bce9 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"通話管理"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"電話"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"電話"</string>
     <string name="unknown" msgid="6993977514360123431">"通知不可能"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"不在着信"</string>
diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml
index be27e8b..f3ac3d6 100644
--- a/res/values-ka/strings.xml
+++ b/res/values-ka/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"ზარების მართვა"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"სატელეფონო ზარები"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"ტელეფონი"</string>
     <string name="unknown" msgid="6993977514360123431">"უცნობი"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"გამოტოვებული ზარი"</string>
diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml
index ab1ae40..22f7d28 100644
--- a/res/values-kk/strings.xml
+++ b/res/values-kk/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"Қоңырауды басқару"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"Телефон қоңыраулары"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"Телефон"</string>
     <string name="unknown" msgid="6993977514360123431">"Белгісіз"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"Қабылданбаған қоңырау"</string>
diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml
index 58b2abf..7186180 100644
--- a/res/values-km/strings.xml
+++ b/res/values-km/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"គ្រប់គ្រងការហៅ"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"Phone Calls"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"ទូរសព្ទ"</string>
     <string name="unknown" msgid="6993977514360123431">"មិន​ស្គាល់"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"ខកខាន​ទទួល"</string>
diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml
index dff4d0a..acee7a9 100644
--- a/res/values-kn/strings.xml
+++ b/res/values-kn/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"ಕರೆ ನಿರ್ವಹಣೆ"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"ಫೋನ್ ಕರೆಗಳು"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"ಫೋನ್"</string>
     <string name="unknown" msgid="6993977514360123431">"ಅಪರಿಚಿತ"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"ಮಿಸ್ಡ್‌ ಕಾಲ್‌"</string>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index 1a29b2e..0134017 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"통화 관리"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"전화 통화"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"전화"</string>
     <string name="unknown" msgid="6993977514360123431">"알 수 없음"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"부재중 전화"</string>
diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml
index 83e498e..ad7a017 100644
--- a/res/values-ky/strings.xml
+++ b/res/values-ky/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"Чалууларды башкаруу"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"Телефон чалуулар"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"Телефон"</string>
     <string name="unknown" msgid="6993977514360123431">"Белгисиз"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"Кабыл алынбаган чалуу"</string>
diff --git a/res/values-lo/strings.xml b/res/values-lo/strings.xml
index 64142ca..8118fa8 100644
--- a/res/values-lo/strings.xml
+++ b/res/values-lo/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"ການຈັດການການໂທ"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"ການໂທລະສັບ"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"ໂທລະສັບ"</string>
     <string name="unknown" msgid="6993977514360123431">"ບໍ່ຮູ້ຈັກ"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"ສາຍທີ່ບໍ່ໄດ້ຮັບ"</string>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index 29090d1..34c2997 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"Skambučių tvarkymas"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"Telefono skambučiai"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"Telefonas"</string>
     <string name="unknown" msgid="6993977514360123431">"Nežinomas"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"Praleistas skambutis"</string>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index 782777d..65fe942 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"Zvanu pārvaldība"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"Tālruņa zvani"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"Tālrunis"</string>
     <string name="unknown" msgid="6993977514360123431">"Nezināms"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"Neatbildēts zvans"</string>
diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml
index a9b3cc3..87de64f 100644
--- a/res/values-mk/strings.xml
+++ b/res/values-mk/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"Управување со повици"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"Телефонски повици"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"Телефон"</string>
     <string name="unknown" msgid="6993977514360123431">"Непознато"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"Пропуштен повик"</string>
diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml
index 5c64034..36bb494 100644
--- a/res/values-ml/strings.xml
+++ b/res/values-ml/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"കോൾ മാനേജ്മെന്റ്"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"ഫോൺ കോളുകൾ"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"ഫോണ്‍"</string>
     <string name="unknown" msgid="6993977514360123431">"അജ്ഞാതം"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"മിസ്‌ഡ് കോൾ"</string>
diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml
index cdef26b..d1f5273 100644
--- a/res/values-mn/strings.xml
+++ b/res/values-mn/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"Дуудлагын удирдлага"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"Утасны дуудлага"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"Утас"</string>
     <string name="unknown" msgid="6993977514360123431">"Тодорхойгүй"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"Аваагүй дуудлага"</string>
diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml
index c235aab..6fbdc03 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"कॉल व्यवस्थापन"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"फोन कॉल"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"फोन"</string>
     <string name="unknown" msgid="6993977514360123431">"अज्ञात"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"सुटलेला कॉल"</string>
diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml
index b43b554..7571b9d 100644
--- a/res/values-ms/strings.xml
+++ b/res/values-ms/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"Pengurusan Panggilan"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"Panggilan Telefon"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"Telefon"</string>
     <string name="unknown" msgid="6993977514360123431">"Tidak diketahui"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"Panggilan tidak dijawab"</string>
diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml
index 4d94c1a..ac6965f 100644
--- a/res/values-my/strings.xml
+++ b/res/values-my/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"ခေါ်ဆိုမှုစီမံခန့်ခွဲရေး"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"ဖုန်းခေါ်ဆိုမှုများ"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"ဖုန်း"</string>
     <string name="unknown" msgid="6993977514360123431">"မသိပါ"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"လွဲသွားသော ဖုန်းခေါ်မှု"</string>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index 3f65c62..df39be6 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"Administrering av samtaler"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"Telefonanrop"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"Telefon"</string>
     <string name="unknown" msgid="6993977514360123431">"Ukjent"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"Tapt anrop"</string>
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
index 5ef8bc0..e38ed9d 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"कल व्यवस्थापन"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"फोन कलहरू"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"फोन"</string>
     <string name="unknown" msgid="6993977514360123431">"अज्ञात"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"छुटेका कल"</string>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index 166e95d..4051118 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"Oproepbeheer"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"Telefoongesprekken"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"Telefoon"</string>
     <string name="unknown" msgid="6993977514360123431">"Onbekend"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"Gemist gesprek"</string>
diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml
index e3ca712..758beda 100644
--- a/res/values-or/strings.xml
+++ b/res/values-or/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"କଲ୍ ପରିଚାଳନା"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"ଫୋନ୍ କଲଗୁଡ଼ିକ"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"ଫୋନ୍ କରନ୍ତୁ"</string>
     <string name="unknown" msgid="6993977514360123431">"ଅଜଣା"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"ମିସଡ୍ କଲ୍‌"</string>
diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml
index 2b94316..e08b5e9 100644
--- a/res/values-pa/strings.xml
+++ b/res/values-pa/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"ਕਾਲ ਪ੍ਰਬੰਧਨ"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"ਫ਼ੋਨ ਕਾਲਾਂ"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"ਫ਼ੋਨ"</string>
     <string name="unknown" msgid="6993977514360123431">"ਅਗਿਆਤ"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"ਮਿਸਡ ਕਾਲ"</string>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index 9cb1980..f341da7 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"Zarządzanie połączeniami"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"Połączenia telefoniczne"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"Telefon"</string>
     <string name="unknown" msgid="6993977514360123431">"Nieznany"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"Nieodebrane połączenie"</string>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index bb5213e..42b435e 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"Gestão de chamadas"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"Chamadas telefónicas"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"Telefone"</string>
     <string name="unknown" msgid="6993977514360123431">"Desconhecido"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"Chamada não atendida"</string>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index 5d88619..68bd2c5 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"Gerenciamento de chamadas"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"Chamadas telefônicas"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"Telefone"</string>
     <string name="unknown" msgid="6993977514360123431">"Desconhecido"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"Chamada perdida"</string>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index a95d4a4..adb1ff8 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"Gestionarea apelurilor"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"Apeluri telefonice"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"Telefon"</string>
     <string name="unknown" msgid="6993977514360123431">"Necunoscut"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"Apel nepreluat"</string>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index a50907a..49ab1db 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"Управление звонками"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"Телефонные звонки"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"Телефон"</string>
     <string name="unknown" msgid="6993977514360123431">"Неизвестный абонент"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"Пропущенный вызов"</string>
diff --git a/res/values-si/strings.xml b/res/values-si/strings.xml
index b73272b..315a0dc 100644
--- a/res/values-si/strings.xml
+++ b/res/values-si/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"ඇමතුම් කළමනාකරණය"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"දුරකථන ඇමතුම්"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"දුරකථනය"</string>
     <string name="unknown" msgid="6993977514360123431">"නොදනී"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"මඟ හැරුණු ඇමතුම"</string>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index 0d79f1a..5209880 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"Správa hovorov"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"Telefonické hovory"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"Telefón"</string>
     <string name="unknown" msgid="6993977514360123431">"Neznáme"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"Zmeškaný hovor"</string>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index e16de63..845e88b 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"Upravljanje klicev"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"Telefonski klici"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"Telefon"</string>
     <string name="unknown" msgid="6993977514360123431">"Neznano"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"Neodgovorjeni klic"</string>
diff --git a/res/values-sq/strings.xml b/res/values-sq/strings.xml
index 53f4efc..6ae9e26 100644
--- a/res/values-sq/strings.xml
+++ b/res/values-sq/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"Menaxhimi i thirrjes"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"Telefonatat"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"Telefoni"</string>
     <string name="unknown" msgid="6993977514360123431">"I panjohur"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"Telefonatë e humbur"</string>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index adfece0..6d83483 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"Управљање позивима"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"Телефонски позиви"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"Телефон"</string>
     <string name="unknown" msgid="6993977514360123431">"Непознато"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"Пропуштен позив"</string>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index b646cbb..22e526b 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"Samtalshantering"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"Telefonsamtal"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"Telefon"</string>
     <string name="unknown" msgid="6993977514360123431">"Okänd"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"Missat samtal"</string>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index d1142f7..cc17765 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"Kudhibiti Simu"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"Simu"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"Simu"</string>
     <string name="unknown" msgid="6993977514360123431">"Haijulikani"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"Simu uliyokosa"</string>
diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml
index ee48214..0608652 100644
--- a/res/values-ta/strings.xml
+++ b/res/values-ta/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"அழைப்பு நிர்வாகம்"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"ஃபோன் அழைப்புகள்"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"ஃபோன்"</string>
     <string name="unknown" msgid="6993977514360123431">"தெரியாதவர்"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"தவறிய அழைப்பு"</string>
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
index cdf805d..22c6d2a 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"కాల్ మేనేజ్‌మెంట్"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"ఫోన్ కాల్స్"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"ఫోన్"</string>
     <string name="unknown" msgid="6993977514360123431">"తెలియదు"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"సమాధానం ఇవ్వని కాల్"</string>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index 615abb9..0def6f6 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"การจัดการการโทร"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"โทรศัพท์"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"โทรศัพท์"</string>
     <string name="unknown" msgid="6993977514360123431">"ไม่ทราบ"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"สายที่ไม่ได้รับ"</string>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index 998abef..2889d15 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"Pamamahala sa Tawag"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"Mga Tawag sa Telepono"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"Telepono"</string>
     <string name="unknown" msgid="6993977514360123431">"Di-kilala"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"Hindi nasagot na tawag"</string>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index 2e02f8c..2b3cfc5 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"Çağrı Yönetimi"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"Telefon Aramaları"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"Telefon"</string>
     <string name="unknown" msgid="6993977514360123431">"Bilinmiyor"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"Cevapsız çağrı"</string>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index d86b3eb..762341c 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"Керування дзвінками"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"Телефонні виклики"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"Телефон"</string>
     <string name="unknown" msgid="6993977514360123431">"Невідомий"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"Пропущений виклик"</string>
diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml
index 20cca1b..bc81512 100644
--- a/res/values-ur/strings.xml
+++ b/res/values-ur/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"کال مینجمنٹ"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"فون کالز"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"فون"</string>
     <string name="unknown" msgid="6993977514360123431">"نامعلوم"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"چھوٹی ہوئی کال"</string>
diff --git a/res/values-uz/strings.xml b/res/values-uz/strings.xml
index 3271245..cda0a1b 100644
--- a/res/values-uz/strings.xml
+++ b/res/values-uz/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"Chaqiruvlar boshqaruvi"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"Telefon chaqiruvlari"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"Telefon"</string>
     <string name="unknown" msgid="6993977514360123431">"Noma’lum"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"Javobsiz chaqiruv"</string>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index c6fe44a..d4195b6 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"Quản lý cuộc gọi"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"Cuộc gọi điện thoại"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"Điện thoại"</string>
     <string name="unknown" msgid="6993977514360123431">"Không xác định"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"Cuộc gọi nhỡ"</string>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index 6080b06..1c9ed1b 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"通话管理"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"电话"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"电话"</string>
     <string name="unknown" msgid="6993977514360123431">"未知"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"未接电话"</string>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index 0f26c9d..98f1fa1 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"通話管理"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"電話通話"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"電話"</string>
     <string name="unknown" msgid="6993977514360123431">"未知"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"未接來電"</string>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index 7c98928..3c87562 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"通話管理"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"電話"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"電話"</string>
     <string name="unknown" msgid="6993977514360123431">"不明"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"未接來電"</string>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index 30c147d..9f5ef44 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -16,7 +16,7 @@
 
 <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="3077225713817780583">"Ukuphathwa kwekholi"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"Amakholi Wefoni"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"Ifoni"</string>
     <string name="unknown" msgid="6993977514360123431">"Akwaziwa"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"Ikholi ekulahlekele"</string>
diff --git a/res/values/config.xml b/res/values/config.xml
index 9cbbf46..b0e50b0 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -69,4 +69,8 @@
     <!-- When true, the options in the call blocking settings to block restricted and unknown
          callers are combined into a single toggle. -->
     <bool name="combine_options_to_block_restricted_and_unknown_callers">true</bool>
+
+    <!-- When set, Telecom will attempt to bind to the {@link CallDiagnosticService} implementation
+         defined by the app with this package name. -->
+    <string name="call_diagnostic_service_package_name"></string>
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index df08d7c..01bb88c 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -17,8 +17,10 @@
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <!-- Official label of the Telecomm/Phone app, as seen in "Manage Applications"
          and other settings UIs. This is the "app name" used in notification, recents,
-         and app info screens. -->
-    <string name="telecommAppLabel" product="default">Call Management</string>
+         and app info screens. The term "phone calls" is used since an end user would more commonly
+         think of the stuff the Telecom framework deals with as having to do with "phone calls",
+         not "call management" (even though that is technically more accurate). -->
+    <string name="telecommAppLabel" product="default">Phone Calls</string>
 
     <!-- Title used for the activity for placing a call. This name appears
          in activity disambig dialogs -->
diff --git a/res/xml/activity_blocked_numbers.xml b/res/xml/activity_blocked_numbers.xml
index f884ec9..df1a759 100644
--- a/res/xml/activity_blocked_numbers.xml
+++ b/res/xml/activity_blocked_numbers.xml
@@ -75,7 +75,7 @@
                     <TextView
                             android:id="@+id/add_blocked"
                             android:layout_width="wrap_content"
-                            android:layout_height="wrap_content"
+                            android:layout_height="48dp"
                             android:text="@string/block_number"
                             android:layout_marginBottom="@dimen/blocked_numbers_button_bottom_margin"
                             android:paddingTop="@dimen/blocked_numbers_button_large_padding"
diff --git a/res/xml/enhanced_call_blocking_settings.xml b/res/xml/enhanced_call_blocking_settings.xml
index 32c4c5d..73ed2af 100644
--- a/res/xml/enhanced_call_blocking_settings.xml
+++ b/res/xml/enhanced_call_blocking_settings.xml
@@ -41,8 +41,4 @@
         android:persistent="false"
         android:defaultValue="false"/>
     <!--Add divider to separate this enhanced call blocking settings from other settings-->
-    <Preference
-        android:key="enhanced_call_blocking_divider"
-        android:persistent="false"
-        android:layout="@xml/layout_divider"/>
 </PreferenceScreen>
\ No newline at end of file
diff --git a/res/xml/layout_blocked_number.xml b/res/xml/layout_blocked_number.xml
index 3cdd771..9dbfc2f 100644
--- a/res/xml/layout_blocked_number.xml
+++ b/res/xml/layout_blocked_number.xml
@@ -28,9 +28,8 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_alignParentStart="true"
+        android:layout_toStartOf="@+id/delete_blocked_number"
         android:paddingTop="@dimen/blocked_numbers_delete_icon_padding"
-        android:layout_alignParentLeft="true"
-        android:layout_toLeftOf="@+id/delete_blocked_number"
         android:textDirection="ltr" />
 
     <ImageView
diff --git a/scripts/aosp_tag_preupload.py b/scripts/aosp_tag_preupload.py
index 77a9714..bfcdbd6 100755
--- a/scripts/aosp_tag_preupload.py
+++ b/scripts/aosp_tag_preupload.py
@@ -41,15 +41,18 @@
   commit_msg = subprocess.check_output(["git", "show",
                                         sys.argv[1], "--no-notes"])
   for commit_line in commit_msg.splitlines():
-    if re.search(AOSP_COMMIT_TAG_REGEX, commit_line, re.IGNORECASE):
-      _check_aosp_message(commit_line)
+    # Some lines in the commit message will be given to us as bytes
+    commit_line_str = str(commit_line)
+    if re.search(AOSP_COMMIT_TAG_REGEX, str(commit_line_str), re.IGNORECASE):
+      _check_aosp_message(commit_line_str)
 
   print(ERROR_MESSAGE)
-  sys.exit(0)
+  # Print the warning, but do not fail the presubmit check.
+  sys.exit(77)
 
 def _is_in_aosp():
   branch_info = subprocess.check_output(["git", "branch", "-vv"])
-  return re.search(AOSP_BRANCH_REGEX, branch_info) is not None
+  return re.search(AOSP_BRANCH_REGEX, str(branch_info)) is not None
 
 def _check_aosp_message(aosp_line):
   if re.search(AOSP_COMMIT_LINK_REGEX, aosp_line):
@@ -59,7 +62,8 @@
     sys.exit(0)
 
   print(ERROR_MESSAGE)
-  sys.exit(0)
+  # Print the warning, but do not fail the presubmit check.
+  sys.exit(77)
 
 if __name__ == '__main__':
   main()
diff --git a/src/com/android/server/telecom/Analytics.java b/src/com/android/server/telecom/Analytics.java
index 9c227a6..d6780ed 100644
--- a/src/com/android/server/telecom/Analytics.java
+++ b/src/com/android/server/telecom/Analytics.java
@@ -208,6 +208,9 @@
 
         public void setCallSource(int callSource) {
         }
+
+        public void setMissedReason(long missedReason) {
+        }
     }
 
     /**
@@ -242,6 +245,7 @@
         public List<TelecomLogClass.InCallServiceInfo> inCallServiceInfos;
         public int callProperties = 0;
         public int callSource = CALL_SOURCE_UNSPECIFIED;
+        public long missedReason;
 
         private long mTimeOfLastVideoEvent = -1;
 
@@ -254,6 +258,7 @@
             connectionService = "";
             videoEvents = new LinkedList<>();
             inCallServiceInfos = new LinkedList<>();
+            missedReason = 0;
         }
 
         CallInfoImpl(CallInfoImpl other) {
@@ -272,6 +277,7 @@
             this.videoEvents = other.videoEvents;
             this.callProperties = other.callProperties;
             this.callSource = other.callSource;
+            this.missedReason = other.missedReason;
 
             if (other.callTerminationReason != null) {
                 this.callTerminationReason = new DisconnectCause(
@@ -342,6 +348,13 @@
         }
 
         @Override
+        public void setMissedReason(long missedReason) {
+            Log.d(TAG, "setting missedReason for call " + callId + ": "
+                    + missedReason);
+            this.missedReason = missedReason;
+        }
+
+        @Override
         public void setCallEvents(EventManager.EventRecord records) {
             this.callEvents = records;
         }
@@ -399,6 +412,7 @@
                     + "    isEmergency: " + isEmergency + '\n'
                     + "    callTechnologies: " + getCallTechnologiesAsString() + '\n'
                     + "    callTerminationReason: " + getCallDisconnectReasonString() + '\n'
+                    + "    missedReason: " + getMissedReasonString() + '\n'
                     + "    connectionService: " + connectionService + '\n'
                     + "    isVideoCall: " + isVideo + '\n'
                     + "    inCallServices: " + getInCallServicesString() + '\n'
@@ -526,20 +540,27 @@
             }
         }
 
+        private String getMissedReasonString() {
+            //TODO: Implement this
+            return null;
+        }
+
         private String getInCallServicesString() {
             StringBuilder s = new StringBuilder();
             s.append("[\n");
-            for (TelecomLogClass.InCallServiceInfo service : inCallServiceInfos) {
-                s.append("    ");
-                s.append("name: ");
-                s.append(service.getInCallServiceName());
-                s.append(" type: ");
-                s.append(service.getInCallServiceType());
-                s.append(" is crashed: ");
-                s.append(service.getIsNullBinding());
-                s.append(" service last time in ms: ");
-                s.append(service.getBoundDurationMillis());
-                s.append("\n");
+            if (inCallServiceInfos != null) {
+                for (TelecomLogClass.InCallServiceInfo service : inCallServiceInfos) {
+                    s.append("    ");
+                    s.append("name: ");
+                    s.append(service.getInCallServiceName());
+                    s.append(" type: ");
+                    s.append(service.getInCallServiceType());
+                    s.append(" is crashed: ");
+                    s.append(service.getIsNullBinding());
+                    s.append(" service last time in ms: ");
+                    s.append(service.getBoundDurationMillis());
+                    s.append("\n");
+                }
             }
             s.append("]");
             return s.toString();
@@ -612,7 +633,7 @@
     }
 
     public static CallInfo initiateCallAnalytics(String callId, int direction) {
-        Log.d(TAG, "Starting analytics for call " + callId);
+        Log.i(TAG, "Starting analytics for call " + callId);
         CallInfoImpl callInfo = new CallInfoImpl(callId, direction);
         synchronized (sLock) {
             while (sActiveCallIds.size() >= MAX_NUM_CALLS_TO_STORE) {
diff --git a/src/com/android/server/telecom/BluetoothHeadsetProxy.java b/src/com/android/server/telecom/BluetoothHeadsetProxy.java
index a43b3cd..e4eed87 100644
--- a/src/com/android/server/telecom/BluetoothHeadsetProxy.java
+++ b/src/com/android/server/telecom/BluetoothHeadsetProxy.java
@@ -36,19 +36,6 @@
         mBluetoothHeadset = headset;
     }
 
-    public void clccResponse(int index, int direction, int status, int mode, boolean mpty,
-            String number, int type) {
-
-        mBluetoothHeadset.clccResponse(index, direction, status, mode, mpty, number, type);
-    }
-
-    public void phoneStateChanged(int numActive, int numHeld, int callState, String number,
-            int type, String name) {
-
-        mBluetoothHeadset.phoneStateChanged(numActive, numHeld, callState, number, type,
-            name);
-    }
-
     public List<BluetoothDevice> getConnectedDevices() {
         return mBluetoothHeadset.getConnectedDevices();
     }
diff --git a/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java b/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java
deleted file mode 100644
index f2ea950..0000000
--- a/src/com/android/server/telecom/BluetoothPhoneServiceImpl.java
+++ /dev/null
@@ -1,932 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.telecom;
-
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothHeadset;
-import android.bluetooth.BluetoothProfile;
-import android.bluetooth.IBluetoothHeadsetPhone;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.net.Uri;
-import android.os.Binder;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.telecom.Connection;
-import android.telecom.Log;
-import android.telecom.PhoneAccount;
-import android.telecom.VideoProfile;
-import android.telephony.PhoneNumberUtils;
-import android.telephony.TelephonyManager;
-import android.text.TextUtils;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.telecom.CallsManager.CallsManagerListener;
-
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Bluetooth headset manager for Telecom. This class shares the call state with the bluetooth device
- * and accepts call-related commands to perform on behalf of the BT device.
- */
-public class BluetoothPhoneServiceImpl {
-
-    public interface BluetoothPhoneServiceImplFactory {
-        BluetoothPhoneServiceImpl makeBluetoothPhoneServiceImpl(Context context,
-                TelecomSystem.SyncRoot lock, CallsManager callsManager,
-                PhoneAccountRegistrar phoneAccountRegistrar);
-    }
-
-    private static final String TAG = "BluetoothPhoneService";
-
-    // match up with bthf_call_state_t of bt_hf.h
-    private static final int CALL_STATE_ACTIVE = 0;
-    private static final int CALL_STATE_HELD = 1;
-    private static final int CALL_STATE_DIALING = 2;
-    private static final int CALL_STATE_ALERTING = 3;
-    private static final int CALL_STATE_INCOMING = 4;
-    private static final int CALL_STATE_WAITING = 5;
-    private static final int CALL_STATE_IDLE = 6;
-    private static final int CALL_STATE_DISCONNECTED = 7;
-
-    // match up with bthf_call_state_t of bt_hf.h
-    // Terminate all held or set UDUB("busy") to a waiting call
-    private static final int CHLD_TYPE_RELEASEHELD = 0;
-    // Terminate all active calls and accepts a waiting/held call
-    private static final int CHLD_TYPE_RELEASEACTIVE_ACCEPTHELD = 1;
-    // Hold all active calls and accepts a waiting/held call
-    private static final int CHLD_TYPE_HOLDACTIVE_ACCEPTHELD = 2;
-    // Add all held calls to a conference
-    private static final int CHLD_TYPE_ADDHELDTOCONF = 3;
-
-    // Indicates that no call is ringing
-    private static final int DEFAULT_RINGING_ADDRESS_TYPE = 128;
-
-    private int mNumActiveCalls = 0;
-    private int mNumHeldCalls = 0;
-    private int mNumChildrenOfActiveCall = 0;
-    private int mBluetoothCallState = CALL_STATE_IDLE;
-    private String mRingingAddress = "";
-    private int mRingingAddressType = DEFAULT_RINGING_ADDRESS_TYPE;
-    private Call mOldHeldCall = null;
-    private boolean mIsDisconnectedTonePlaying = false;
-
-    /**
-     * Binder implementation of IBluetoothHeadsetPhone. Implements the command interface that the
-     * bluetooth headset code uses to control call.
-     */
-    @VisibleForTesting
-    public final IBluetoothHeadsetPhone.Stub mBinder = new IBluetoothHeadsetPhone.Stub() {
-        @Override
-        public boolean answerCall() throws RemoteException {
-            synchronized (mLock) {
-                enforceModifyPermission();
-                Log.startSession("BPSI.aC");
-                long token = Binder.clearCallingIdentity();
-                try {
-                    Log.i(TAG, "BT - answering call");
-                    Call call = mCallsManager.getRingingOrSimulatedRingingCall();
-                    if (call != null) {
-                        mCallsManager.answerCall(call, VideoProfile.STATE_AUDIO_ONLY);
-                        return true;
-                    }
-                    return false;
-                } finally {
-                    Binder.restoreCallingIdentity(token);
-                    Log.endSession();
-                }
-
-            }
-        }
-
-        @Override
-        public boolean hangupCall() throws RemoteException {
-            synchronized (mLock) {
-                enforceModifyPermission();
-                Log.startSession("BPSI.hC");
-                long token = Binder.clearCallingIdentity();
-                try {
-                    Log.i(TAG, "BT - hanging up call");
-                    Call call = mCallsManager.getForegroundCall();
-                    if (call != null) {
-                        mCallsManager.disconnectCall(call);
-                        return true;
-                    }
-                    return false;
-                } finally {
-                    Binder.restoreCallingIdentity(token);
-                    Log.endSession();
-                }
-            }
-        }
-
-        @Override
-        public boolean sendDtmf(int dtmf) throws RemoteException {
-            synchronized (mLock) {
-                enforceModifyPermission();
-                Log.startSession("BPSI.sD");
-                long token = Binder.clearCallingIdentity();
-                try {
-                    Log.i(TAG, "BT - sendDtmf %c", Log.DEBUG ? dtmf : '.');
-                    Call call = mCallsManager.getForegroundCall();
-                    if (call != null) {
-                        // TODO: Consider making this a queue instead of starting/stopping
-                        // in quick succession.
-                        mCallsManager.playDtmfTone(call, (char) dtmf);
-                        mCallsManager.stopDtmfTone(call);
-                        return true;
-                    }
-                    return false;
-                } finally {
-                    Binder.restoreCallingIdentity(token);
-                    Log.endSession();
-                }
-            }
-        }
-
-        @Override
-        public String getNetworkOperator() throws RemoteException {
-            synchronized (mLock) {
-                enforceModifyPermission();
-                Log.startSession("BPSI.gNO");
-                long token = Binder.clearCallingIdentity();
-                try {
-                    Log.i(TAG, "getNetworkOperator");
-                    PhoneAccount account = getBestPhoneAccount();
-                    if (account != null && account.getLabel() != null) {
-                        return account.getLabel().toString();
-                    } else {
-                        // Finally, just get the network name from telephony.
-                        return mContext.getSystemService(TelephonyManager.class)
-                                .getNetworkOperatorName();
-                    }
-                } finally {
-                    Binder.restoreCallingIdentity(token);
-                    Log.endSession();
-                }
-            }
-        }
-
-        @Override
-        public String getSubscriberNumber() throws RemoteException {
-            synchronized (mLock) {
-                enforceModifyPermission();
-                Log.startSession("BPSI.gSN");
-                long token = Binder.clearCallingIdentity();
-                try {
-                    Log.i(TAG, "getSubscriberNumber");
-                    String address = null;
-                    PhoneAccount account = getBestPhoneAccount();
-                    if (account != null) {
-                        Uri addressUri = account.getAddress();
-                        if (addressUri != null) {
-                            address = addressUri.getSchemeSpecificPart();
-                        }
-                    }
-                    if (TextUtils.isEmpty(address)) {
-                        address = mContext.getSystemService(TelephonyManager.class)
-                                .getLine1Number();
-                        if (address == null) address = "";
-                    }
-                    return address;
-                } finally {
-                    Binder.restoreCallingIdentity(token);
-                    Log.endSession();
-                }
-            }
-        }
-
-        @Override
-        public boolean listCurrentCalls() throws RemoteException {
-            synchronized (mLock) {
-                enforceModifyPermission();
-                Log.startSession("BPSI.lCC");
-                long token = Binder.clearCallingIdentity();
-                try {
-                    // only log if it is after we recently updated the headset state or else it can
-                    // clog the android log since this can be queried every second.
-                    boolean logQuery = mHeadsetUpdatedRecently;
-                    mHeadsetUpdatedRecently = false;
-
-                    if (logQuery) {
-                        Log.i(TAG, "listcurrentCalls");
-                    }
-
-                    sendListOfCalls(logQuery);
-                    return true;
-                } finally {
-                    Binder.restoreCallingIdentity(token);
-                    Log.endSession();
-                }
-            }
-        }
-
-        @Override
-        public boolean queryPhoneState() throws RemoteException {
-            synchronized (mLock) {
-                enforceModifyPermission();
-                Log.startSession("BPSI.qPS");
-                long token = Binder.clearCallingIdentity();
-                try {
-                    Log.i(TAG, "queryPhoneState");
-                    updateHeadsetWithCallState(true /* force */);
-                    return true;
-                } finally {
-                    Binder.restoreCallingIdentity(token);
-                    Log.endSession();
-                }
-            }
-        }
-
-        @Override
-        public boolean processChld(int chld) throws RemoteException {
-            synchronized (mLock) {
-                enforceModifyPermission();
-                Log.startSession("BPSI.pC");
-                long token = Binder.clearCallingIdentity();
-                try {
-                    Log.i(TAG, "processChld %d", chld);
-                    return BluetoothPhoneServiceImpl.this.processChld(chld);
-                } finally {
-                    Binder.restoreCallingIdentity(token);
-                    Log.endSession();
-                }
-            }
-        }
-
-        @Override
-        public void updateBtHandsfreeAfterRadioTechnologyChange() throws RemoteException {
-            Log.d(TAG, "RAT change - deprecated");
-            // deprecated
-        }
-
-        @Override
-        public void cdmaSetSecondCallState(boolean state) throws RemoteException {
-            Log.d(TAG, "cdma 1 - deprecated");
-            // deprecated
-        }
-
-        @Override
-        public void cdmaSwapSecondCallState() throws RemoteException {
-            Log.d(TAG, "cdma 2 - deprecated");
-            // deprecated
-        }
-    };
-
-    /**
-     * Listens to call changes from the CallsManager and calls into methods to update the bluetooth
-     * headset with the new states.
-     */
-    @VisibleForTesting
-    public CallsManagerListener mCallsManagerListener = new CallsManagerListenerBase() {
-        @Override
-        public void onCallAdded(Call call) {
-            if (call.isExternalCall()) {
-                return;
-            }
-            updateHeadsetWithCallState(false /* force */);
-        }
-
-        @Override
-        public void onCallRemoved(Call call) {
-            if (call.isExternalCall()) {
-                return;
-            }
-            mClccIndexMap.remove(call);
-            updateHeadsetWithCallState(false /* force */);
-        }
-
-        /**
-         * Where a call which was external becomes a regular call, or a regular call becomes
-         * external, treat as an add or remove, respectively.
-         *
-         * @param call The call.
-         * @param isExternalCall {@code True} if the call became external, {@code false} otherwise.
-         */
-        @Override
-        public void onExternalCallChanged(Call call, boolean isExternalCall) {
-            if (isExternalCall) {
-                onCallRemoved(call);
-            } else {
-                onCallAdded(call);
-            }
-        }
-
-        @Override
-        public void onCallStateChanged(Call call, int oldState, int newState) {
-            if (call.isExternalCall()) {
-                return;
-            }
-            // If a call is being put on hold because of a new connecting call, ignore the
-            // CONNECTING since the BT state update needs to send out the numHeld = 1 + dialing
-            // state atomically.
-            // When the call later transitions to DIALING/DISCONNECTED we will then send out the
-            // aggregated update.
-            if (oldState == CallState.ACTIVE && newState == CallState.ON_HOLD) {
-                for (Call otherCall : mCallsManager.getCalls()) {
-                    if (otherCall.getState() == CallState.CONNECTING) {
-                        return;
-                    }
-                }
-            }
-
-            // To have an active call and another dialing at the same time is an invalid BT
-            // state. We can assume that the active call will be automatically held which will
-            // send another update at which point we will be in the right state.
-            if (mCallsManager.getActiveCall() != null
-                    && oldState == CallState.CONNECTING &&
-                    (newState == CallState.DIALING || newState == CallState.PULLING)) {
-                return;
-            }
-            updateHeadsetWithCallState(false /* force */);
-        }
-
-        @Override
-        public void onIsConferencedChanged(Call call) {
-            if (call.isExternalCall()) {
-                return;
-            }
-            /*
-             * Filter certain onIsConferencedChanged callbacks. Unfortunately this needs to be done
-             * because conference change events are not atomic and multiple callbacks get fired
-             * when two calls are conferenced together. This confuses updateHeadsetWithCallState
-             * if it runs in the middle of two calls being conferenced and can cause spurious and
-             * incorrect headset state updates. One of the scenarios is described below for CDMA
-             * conference calls.
-             *
-             * 1) Call 1 and Call 2 are being merged into conference Call 3.
-             * 2) Call 1 has its parent set to Call 3, but Call 2 does not have a parent yet.
-             * 3) updateHeadsetWithCallState now thinks that there are two active calls (Call 2 and
-             * Call 3) when there is actually only one active call (Call 3).
-             */
-            if (call.getParentCall() != null) {
-                // If this call is newly conferenced, ignore the callback. We only care about the
-                // one sent for the parent conference call.
-                Log.d(this, "Ignoring onIsConferenceChanged from child call with new parent");
-                return;
-            }
-            if (call.getChildCalls().size() == 1) {
-                // If this is a parent call with only one child, ignore the callback as well since
-                // the minimum number of child calls to start a conference call is 2. We expect
-                // this to be called again when the parent call has another child call added.
-                Log.d(this, "Ignoring onIsConferenceChanged from parent with only one child call");
-                return;
-            }
-            updateHeadsetWithCallState(false /* force */);
-        }
-
-        @Override
-        public void onDisconnectedTonePlaying(boolean isTonePlaying) {
-            mIsDisconnectedTonePlaying = isTonePlaying;
-            updateHeadsetWithCallState(false /* force */);
-        }
-    };
-
-    /**
-     * Listens to connections and disconnections of bluetooth headsets.  We need to save the current
-     * bluetooth headset so that we know where to send call updates.
-     */
-    @VisibleForTesting
-    public BluetoothProfile.ServiceListener mProfileListener =
-            new BluetoothProfile.ServiceListener() {
-                @Override
-                public void onServiceConnected(int profile, BluetoothProfile proxy) {
-                    synchronized (mLock) {
-                        setBluetoothHeadset(new BluetoothHeadsetProxy((BluetoothHeadset) proxy));
-                        updateHeadsetWithCallState(true /* force */);
-                    }
-                }
-
-                @Override
-                public void onServiceDisconnected(int profile) {
-                    synchronized (mLock) {
-                        mBluetoothHeadset = null;
-                    }
-                }
-            };
-
-    /**
-     * Receives events for global state changes of the bluetooth adapter.
-     */
-    @VisibleForTesting
-    public final BroadcastReceiver mBluetoothAdapterReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            synchronized (mLock) {
-                int state = intent
-                        .getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
-                Log.d(TAG, "Bluetooth Adapter state: %d", state);
-                if (state == BluetoothAdapter.STATE_ON) {
-                    try {
-                        mBinder.queryPhoneState();
-                    } catch (RemoteException e) {
-                        // Remote exception not expected
-                    }
-                }
-            }
-        }
-    };
-
-    private BluetoothAdapterProxy mBluetoothAdapter;
-    private BluetoothHeadsetProxy mBluetoothHeadset;
-
-    // A map from Calls to indexes used to identify calls for CLCC (C* List Current Calls).
-    private Map<Call, Integer> mClccIndexMap = new HashMap<>();
-
-    private boolean mHeadsetUpdatedRecently = false;
-
-    private final Context mContext;
-    private final TelecomSystem.SyncRoot mLock;
-    private final CallsManager mCallsManager;
-    private final PhoneAccountRegistrar mPhoneAccountRegistrar;
-
-    public IBinder getBinder() {
-        return mBinder;
-    }
-
-    public BluetoothPhoneServiceImpl(
-            Context context,
-            TelecomSystem.SyncRoot lock,
-            CallsManager callsManager,
-            BluetoothAdapterProxy bluetoothAdapter,
-            PhoneAccountRegistrar phoneAccountRegistrar) {
-        Log.d(this, "onCreate");
-
-        mContext = context;
-        mLock = lock;
-        mCallsManager = callsManager;
-        mPhoneAccountRegistrar = phoneAccountRegistrar;
-
-        mBluetoothAdapter = bluetoothAdapter;
-        if (mBluetoothAdapter == null) {
-            Log.d(this, "BluetoothPhoneService shutting down, no BT Adapter found.");
-            return;
-        }
-        mBluetoothAdapter.getProfileProxy(context, mProfileListener, BluetoothProfile.HEADSET);
-
-        IntentFilter intentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
-        context.registerReceiver(mBluetoothAdapterReceiver, intentFilter);
-
-        mCallsManager.addListener(mCallsManagerListener);
-        updateHeadsetWithCallState(false /* force */);
-    }
-
-    @VisibleForTesting
-    public void setBluetoothHeadset(BluetoothHeadsetProxy bluetoothHeadset) {
-        mBluetoothHeadset = bluetoothHeadset;
-    }
-
-    private boolean processChld(int chld) {
-        Call activeCall = mCallsManager.getActiveCall();
-        Call ringingCall = mCallsManager.getRingingOrSimulatedRingingCall();
-        Call heldCall = mCallsManager.getHeldCall();
-
-        // TODO: Keeping as Log.i for now.  Move to Log.d after L release if BT proves stable.
-        Log.i(TAG, "Active: %s\nRinging: %s\nHeld: %s", activeCall, ringingCall, heldCall);
-
-        if (chld == CHLD_TYPE_RELEASEHELD) {
-            if (ringingCall != null) {
-                mCallsManager.rejectCall(ringingCall, false, null);
-                return true;
-            } else if (heldCall != null) {
-                mCallsManager.disconnectCall(heldCall);
-                return true;
-            }
-        } else if (chld == CHLD_TYPE_RELEASEACTIVE_ACCEPTHELD) {
-            if (activeCall == null && ringingCall == null && heldCall == null)
-                return false;
-            if (activeCall != null) {
-                mCallsManager.disconnectCall(activeCall);
-                if (ringingCall != null) {
-                    mCallsManager.answerCall(ringingCall, VideoProfile.STATE_AUDIO_ONLY);
-                }
-                return true;
-            }
-            if (ringingCall != null) {
-                mCallsManager.answerCall(ringingCall, ringingCall.getVideoState());
-            } else if (heldCall != null) {
-                mCallsManager.unholdCall(heldCall);
-            }
-            return true;
-        } else if (chld == CHLD_TYPE_HOLDACTIVE_ACCEPTHELD) {
-            if (activeCall != null && activeCall.can(Connection.CAPABILITY_SWAP_CONFERENCE)) {
-                activeCall.swapConference();
-                Log.i(TAG, "CDMA calls in conference swapped, updating headset");
-                updateHeadsetWithCallState(true /* force */);
-                return true;
-            } else if (ringingCall != null) {
-                mCallsManager.answerCall(ringingCall, VideoProfile.STATE_AUDIO_ONLY);
-                return true;
-            } else if (heldCall != null) {
-                // CallsManager will hold any active calls when unhold() is called on a
-                // currently-held call.
-                mCallsManager.unholdCall(heldCall);
-                return true;
-            } else if (activeCall != null && activeCall.can(Connection.CAPABILITY_HOLD)) {
-                mCallsManager.holdCall(activeCall);
-                return true;
-            }
-        } else if (chld == CHLD_TYPE_ADDHELDTOCONF) {
-            if (activeCall != null) {
-                if (activeCall.can(Connection.CAPABILITY_MERGE_CONFERENCE)) {
-                    activeCall.mergeConference();
-                    return true;
-                } else {
-                    List<Call> conferenceable = activeCall.getConferenceableCalls();
-                    if (!conferenceable.isEmpty()) {
-                        mCallsManager.conference(activeCall, conferenceable.get(0));
-                        return true;
-                    }
-                }
-            }
-        }
-        return false;
-    }
-
-    private void enforceModifyPermission() {
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.MODIFY_PHONE_STATE, null);
-    }
-
-    private void sendListOfCalls(boolean shouldLog) {
-        Collection<Call> mCalls = mCallsManager.getCalls();
-        for (Call call : mCalls) {
-            // We don't send the parent conference call to the bluetooth device.
-            // We do, however want to send conferences that have no children to the bluetooth
-            // device (e.g. IMS Conference).
-            if (!call.isConference() ||
-                    (call.isConference() && call
-                            .can(Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN))) {
-                sendClccForCall(call, shouldLog);
-            }
-        }
-        sendClccEndMarker();
-    }
-
-    /**
-     * Sends a single clcc (C* List Current Calls) event for the specified call.
-     */
-    private void sendClccForCall(Call call, boolean shouldLog) {
-        boolean isForeground = mCallsManager.getForegroundCall() == call;
-        int state = getBtCallState(call, isForeground);
-        boolean isPartOfConference = false;
-        boolean isConferenceWithNoChildren = call.isConference() && call
-                .can(Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN);
-
-        if (state == CALL_STATE_IDLE) {
-            return;
-        }
-
-        Call conferenceCall = call.getParentCall();
-        if (conferenceCall != null) {
-            isPartOfConference = true;
-
-            // Run some alternative states for Conference-level merge/swap support.
-            // Basically, if call supports swapping or merging at the conference-level, then we need
-            // to expose the calls as having distinct states (ACTIVE vs CAPABILITY_HOLD) or the
-            // functionality won't show up on the bluetooth device.
-
-            // Before doing any special logic, ensure that we are dealing with an ACTIVE call and
-            // that the conference itself has a notion of the current "active" child call.
-            Call activeChild = conferenceCall.getConferenceLevelActiveCall();
-            if (state == CALL_STATE_ACTIVE && activeChild != null) {
-                // Reevaluate state if we can MERGE or if we can SWAP without previously having
-                // MERGED.
-                boolean shouldReevaluateState =
-                        conferenceCall.can(Connection.CAPABILITY_MERGE_CONFERENCE) ||
-                        (conferenceCall.can(Connection.CAPABILITY_SWAP_CONFERENCE) &&
-                        !conferenceCall.wasConferencePreviouslyMerged());
-
-                if (shouldReevaluateState) {
-                    isPartOfConference = false;
-                    if (call == activeChild) {
-                        state = CALL_STATE_ACTIVE;
-                    } else {
-                        // At this point we know there is an "active" child and we know that it is
-                        // not this call, so set it to HELD instead.
-                        state = CALL_STATE_HELD;
-                    }
-                }
-            }
-            if (conferenceCall.getState() == CallState.ON_HOLD &&
-                    conferenceCall.can(Connection.CAPABILITY_MANAGE_CONFERENCE)) {
-                // If the parent IMS CEP conference call is on hold, we should mark this call as
-                // being on hold regardless of what the other children are doing.
-                state = CALL_STATE_HELD;
-            }
-        } else if (isConferenceWithNoChildren) {
-            // Handle the special case of an IMS conference call without conference event package
-            // support.  The call will be marked as a conference, but the conference will not have
-            // child calls where conference event packages are not used by the carrier.
-            isPartOfConference = true;
-        }
-
-        int index = getIndexForCall(call);
-        int direction = call.isIncoming() ? 1 : 0;
-        final Uri addressUri;
-        if (call.getGatewayInfo() != null) {
-            addressUri = call.getGatewayInfo().getOriginalAddress();
-        } else {
-            addressUri = call.getHandle();
-        }
-
-        String address = addressUri == null ? null : addressUri.getSchemeSpecificPart();
-        if (address != null) {
-            address = PhoneNumberUtils.stripSeparators(address);
-        }
-
-        int addressType = address == null ? -1 : PhoneNumberUtils.toaFromString(address);
-
-        if (shouldLog) {
-            Log.i(this, "sending clcc for call %d, %d, %d, %b, %s, %d",
-                    index, direction, state, isPartOfConference, Log.piiHandle(address),
-                    addressType);
-        }
-
-        if (mBluetoothHeadset != null) {
-            mBluetoothHeadset.clccResponse(
-                    index, direction, state, 0, isPartOfConference, address, addressType);
-        }
-    }
-
-    private void sendClccEndMarker() {
-        // End marker is recognized with an index value of 0. All other parameters are ignored.
-        if (mBluetoothHeadset != null) {
-            mBluetoothHeadset.clccResponse(0 /* index */, 0, 0, 0, false, null, 0);
-        }
-    }
-
-    /**
-     * Returns the caches index for the specified call.  If no such index exists, then an index is
-     * given (smallest number starting from 1 that isn't already taken).
-     */
-    private int getIndexForCall(Call call) {
-        if (mClccIndexMap.containsKey(call)) {
-            return mClccIndexMap.get(call);
-        }
-
-        int i = 1;  // Indexes for bluetooth clcc are 1-based.
-        while (mClccIndexMap.containsValue(i)) {
-            i++;
-        }
-
-        // NOTE: Indexes are removed in {@link #onCallRemoved}.
-        mClccIndexMap.put(call, i);
-        return i;
-    }
-
-    /**
-     * Sends an update of the current call state to the current Headset.
-     *
-     * @param force {@code true} if the headset state should be sent regardless if no changes to the
-     *      state have occurred, {@code false} if the state should only be sent if the state has
-     *      changed.
-     */
-    private void updateHeadsetWithCallState(boolean force) {
-        Call activeCall = mCallsManager.getActiveCall();
-        Call ringingCall = mCallsManager.getRingingOrSimulatedRingingCall();
-        Call heldCall = mCallsManager.getHeldCall();
-
-        int bluetoothCallState = getBluetoothCallStateForUpdate();
-
-        String ringingAddress = null;
-        int ringingAddressType = DEFAULT_RINGING_ADDRESS_TYPE;
-        String ringingName = null;
-        if (ringingCall != null && ringingCall.getHandle() != null
-            && !ringingCall.isSilentRingingRequested()) {
-            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 = "";
-        }
-
-        int numActiveCalls = activeCall == null ? 0 : 1;
-        int numHeldCalls = mCallsManager.getNumHeldCalls();
-        int numChildrenOfActiveCall = activeCall == null ? 0 : activeCall.getChildCalls().size();
-
-        // Intermediate state for GSM calls which are in the process of being swapped.
-        // TODO: Should we be hardcoding this value to 2 or should we check if all top level calls
-        //       are held?
-        boolean callsPendingSwitch = (numHeldCalls == 2);
-
-        // For conference calls which support swapping the active call within the conference
-        // (namely CDMA calls) we need to expose that as a held call in order for the BT device
-        // to show "swap" and "merge" functionality.
-        boolean ignoreHeldCallChange = false;
-        if (activeCall != null && activeCall.isConference() &&
-                !activeCall.can(Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN)) {
-            if (activeCall.can(Connection.CAPABILITY_SWAP_CONFERENCE)) {
-                // Indicate that BT device should show SWAP command by indicating that there is a
-                // call on hold, but only if the conference wasn't previously merged.
-                numHeldCalls = activeCall.wasConferencePreviouslyMerged() ? 0 : 1;
-            } else if (activeCall.can(Connection.CAPABILITY_MERGE_CONFERENCE)) {
-                numHeldCalls = 1;  // Merge is available, so expose via numHeldCalls.
-            }
-
-            for (Call childCall : activeCall.getChildCalls()) {
-                // Held call has changed due to it being combined into a CDMA conference. Keep
-                // track of this and ignore any future update since it doesn't really count as
-                // a call change.
-                if (mOldHeldCall == childCall) {
-                    ignoreHeldCallChange = true;
-                    break;
-                }
-            }
-        }
-
-        if (mBluetoothHeadset != null &&
-                (force ||
-                        (!callsPendingSwitch &&
-                                (numActiveCalls != mNumActiveCalls ||
-                                        numChildrenOfActiveCall != mNumChildrenOfActiveCall ||
-                                        numHeldCalls != mNumHeldCalls ||
-                                        bluetoothCallState != mBluetoothCallState ||
-                                        !TextUtils.equals(ringingAddress, mRingingAddress) ||
-                                        ringingAddressType != mRingingAddressType ||
-                                (heldCall != mOldHeldCall && !ignoreHeldCallChange))))) {
-
-            // If the call is transitioning into the alerting state, send DIALING first.
-            // Some devices expect to see a DIALING state prior to seeing an ALERTING state
-            // so we need to send it first.
-            boolean sendDialingFirst = mBluetoothCallState != bluetoothCallState &&
-                    bluetoothCallState == CALL_STATE_ALERTING;
-
-            mOldHeldCall = heldCall;
-            mNumActiveCalls = numActiveCalls;
-            mNumChildrenOfActiveCall = numChildrenOfActiveCall;
-            mNumHeldCalls = numHeldCalls;
-            mBluetoothCallState = bluetoothCallState;
-            mRingingAddress = ringingAddress;
-            mRingingAddressType = ringingAddressType;
-
-            if (sendDialingFirst) {
-                // Log in full to make logs easier to debug.
-                Log.i(TAG, "updateHeadsetWithCallState " +
-                        "numActive %s, " +
-                        "numHeld %s, " +
-                        "callState %s, " +
-                        "ringing number %s, " +
-                        "ringing type %s, " +
-                        "ringing name %s",
-                        mNumActiveCalls,
-                        mNumHeldCalls,
-                        CALL_STATE_DIALING,
-                        Log.pii(mRingingAddress),
-                        mRingingAddressType,
-                        Log.pii(ringingName));
-                mBluetoothHeadset.phoneStateChanged(
-                        mNumActiveCalls,
-                        mNumHeldCalls,
-                        CALL_STATE_DIALING,
-                        mRingingAddress,
-                        mRingingAddressType,
-                        ringingName);
-            }
-
-            Log.i(TAG, "updateHeadsetWithCallState " +
-                    "numActive %s, " +
-                    "numHeld %s, " +
-                    "callState %s, " +
-                    "ringing number %s, " +
-                    "ringing type %s, " +
-                    "ringing name %s",
-                    mNumActiveCalls,
-                    mNumHeldCalls,
-                    mBluetoothCallState,
-                    Log.pii(mRingingAddress),
-                    mRingingAddressType,
-                    Log.pii(ringingName));
-
-            mBluetoothHeadset.phoneStateChanged(
-                    mNumActiveCalls,
-                    mNumHeldCalls,
-                    mBluetoothCallState,
-                    mRingingAddress,
-                    mRingingAddressType,
-                    ringingName);
-
-            mHeadsetUpdatedRecently = true;
-        }
-    }
-
-    private int getBluetoothCallStateForUpdate() {
-        Call ringingCall = mCallsManager.getRingingOrSimulatedRingingCall();
-        Call dialingCall = mCallsManager.getOutgoingCall();
-        boolean hasOnlyDisconnectedCalls = mCallsManager.hasOnlyDisconnectedCalls();
-
-        //
-        // !! WARNING !!
-        // You will note that CALL_STATE_WAITING, CALL_STATE_HELD, and CALL_STATE_ACTIVE are not
-        // used in this version of the call state mappings.  This is on purpose.
-        // phone_state_change() in btif_hf.c is not written to handle these states. Only with the
-        // listCalls*() method are WAITING and ACTIVE used.
-        // Using the unsupported states here caused problems with inconsistent state in some
-        // bluetooth devices (like not getting out of ringing state after answering a call).
-        //
-        int bluetoothCallState = CALL_STATE_IDLE;
-        if (ringingCall != null && !ringingCall.isSilentRingingRequested()) {
-            bluetoothCallState = CALL_STATE_INCOMING;
-        } else if (dialingCall != null) {
-            bluetoothCallState = CALL_STATE_ALERTING;
-        } else if (hasOnlyDisconnectedCalls || mIsDisconnectedTonePlaying) {
-            // Keep the DISCONNECTED state until the disconnect tone's playback is done
-            bluetoothCallState = CALL_STATE_DISCONNECTED;
-        }
-        return bluetoothCallState;
-    }
-
-    private int getBtCallState(Call call, boolean isForeground) {
-        switch (call.getState()) {
-            case CallState.NEW:
-            case CallState.ABORTED:
-            case CallState.DISCONNECTED:
-            case CallState.AUDIO_PROCESSING:
-                return CALL_STATE_IDLE;
-
-            case CallState.ACTIVE:
-                return CALL_STATE_ACTIVE;
-
-            case CallState.CONNECTING:
-            case CallState.SELECT_PHONE_ACCOUNT:
-            case CallState.DIALING:
-            case CallState.PULLING:
-                // Yes, this is correctly returning ALERTING.
-                // "Dialing" for BT means that we have sent information to the service provider
-                // to place the call but there is no confirmation that the call is going through.
-                // When there finally is confirmation, the ringback is played which is referred to
-                // as an "alert" tone, thus, ALERTING.
-                // TODO: We should consider using the ALERTING terms in Telecom because that
-                // seems to be more industry-standard.
-                return CALL_STATE_ALERTING;
-
-            case CallState.ON_HOLD:
-                return CALL_STATE_HELD;
-
-            case CallState.RINGING:
-            case CallState.ANSWERED:
-            case CallState.SIMULATED_RINGING:
-                if (call.isSilentRingingRequested()) {
-                    return CALL_STATE_IDLE;
-                } else if (isForeground) {
-                    return CALL_STATE_INCOMING;
-                } else {
-                    return CALL_STATE_WAITING;
-                }
-        }
-        return CALL_STATE_IDLE;
-    }
-
-    /**
-     * Returns the best phone account to use for the given state of all calls.
-     * First, tries to return the phone account for the foreground call, second the default
-     * phone account for PhoneAccount.SCHEME_TEL.
-     */
-    private PhoneAccount getBestPhoneAccount() {
-        if (mPhoneAccountRegistrar == null) {
-            return null;
-        }
-
-        Call call = mCallsManager.getForegroundCall();
-
-        PhoneAccount account = null;
-        if (call != null) {
-            // First try to get the network name of the foreground call.
-            account = mPhoneAccountRegistrar.getPhoneAccountOfCurrentUser(
-                    call.getTargetPhoneAccount());
-        }
-
-        if (account == null) {
-            // Second, Try to get the label for the default Phone Account.
-            account = mPhoneAccountRegistrar.getPhoneAccountUnchecked(
-                    mPhoneAccountRegistrar.getOutgoingPhoneAccountForSchemeOfCurrentUser(
-                            PhoneAccount.SCHEME_TEL));
-        }
-        return account;
-    }
-}
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
old mode 100755
new mode 100644
index cd236d2..0218124
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -16,6 +16,8 @@
 
 package com.android.server.telecom;
 
+import static android.provider.CallLog.Calls.MISSED_REASON_NOT_MISSED;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
@@ -33,8 +35,12 @@
 import android.os.SystemClock;
 import android.os.Trace;
 import android.os.UserHandle;
+import android.provider.CallLog;
 import android.provider.ContactsContract.Contacts;
+import android.telecom.BluetoothCallQualityReport;
 import android.telecom.CallAudioState;
+import android.telecom.CallDiagnosticService;
+import android.telecom.CallDiagnostics;
 import android.telecom.CallerInfo;
 import android.telecom.Conference;
 import android.telecom.Connection;
@@ -51,9 +57,11 @@
 import android.telecom.StatusHints;
 import android.telecom.TelecomManager;
 import android.telecom.VideoProfile;
+import android.telephony.CallQuality;
 import android.telephony.PhoneNumberUtils;
 import android.telephony.TelephonyManager;
 import android.telephony.emergency.EmergencyNumber;
+import android.telephony.ims.ImsReasonInfo;
 import android.text.TextUtils;
 import android.widget.Toast;
 
@@ -74,7 +82,10 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
 
 /**
  *  Encapsulates all aspects of a given phone call throughout its lifecycle, starting
@@ -151,6 +162,10 @@
                                  Bundle extras, boolean isLegacy);
         void onHandoverFailed(Call call, int error);
         void onHandoverComplete(Call call);
+        void onBluetoothCallQualityReport(Call call, BluetoothCallQualityReport report);
+        void onReceivedDeviceToDeviceMessage(Call call, int messageType, int messageValue);
+        void onReceivedCallQualityReport(Call call, CallQuality callQuality);
+        void onCallerNumberVerificationStatusChanged(Call call, int callerNumberVerificationStatus);
     }
 
     public abstract static class ListenerBase implements Listener {
@@ -239,6 +254,15 @@
         public void onHandoverFailed(Call call, int error) {}
         @Override
         public void onHandoverComplete(Call call) {}
+        @Override
+        public void onBluetoothCallQualityReport(Call call, BluetoothCallQualityReport report) {}
+        @Override
+        public void onReceivedDeviceToDeviceMessage(Call call, int messageType, int messageValue) {}
+        @Override
+        public void onReceivedCallQualityReport(Call call, CallQuality callQuality) {}
+        @Override
+        public void onCallerNumberVerificationStatusChanged(Call call,
+                int callerNumberVerificationStatus) {}
     }
 
     private final CallerInfoLookupHelper.OnQueryCompleteListener mCallerInfoQueryListener =
@@ -507,6 +531,15 @@
     private boolean mIsSelfManaged = false;
 
     /**
+     * Indicates whether the {@link PhoneAccount} associated with an self-managed call want to
+     * expose the call to an {@link android.telecom.InCallService} which declares the metadata
+     * {@link TelecomManager#METADATA_INCLUDE_SELF_MANAGED_CALLS},
+     * For calls that {@link #mIsSelfManaged} is {@code false}, this value should be {@code false}
+     * as well.
+     */
+    private boolean mVisibleToInCallService = false;
+
+    /**
      * Indicates whether the {@link PhoneAccount} associated with this call supports video calling.
      * {@code True} if the phone account supports video calling, {@code false} otherwise.
      */
@@ -545,7 +578,7 @@
     private ParcelFileDescriptor[] mConnectionServiceToInCallStreams;
 
     /**
-     * True if we're supposed to start this call with RTT, either due to the master switch or due
+     * True if we're supposed to start this call with RTT, either due to the settings switch or due
      * to an extra.
      */
     private boolean mDidRequestToStartWithRtt = false;
@@ -588,7 +621,7 @@
 
     /**
      * Indicates whether this call is using one of the
-     * {@link com.android.server.telecom.callfiltering.IncomingCallFilter.CallFilter} modules.
+     * {@link com.android.server.telecom.callfiltering.CallFilter} modules.
      */
     private boolean mIsUsingCallFiltering = false;
 
@@ -611,6 +644,51 @@
     private String mPostCallPackageName;
 
     /**
+     * Call missed information code.
+     */
+    @CallLog.Calls.MissedReason private long mMissedReason;
+
+    /**
+     * Time that this call start ringing or simulated ringing.
+     */
+    private long mStartRingTime;
+
+    /**
+     * The package name of the call screening service that silence this call. If the call is not
+     * silenced, this field will be null.
+     */
+    private CharSequence mCallScreeningAppName;
+
+    /**
+     * The component name of the call screening service that silence this call. If the call is not
+     * silenced, this field will be null.
+     */
+    private String mCallScreeningComponentName;
+
+    /**
+     * When {@code true} indicates this call originated from a SIM-based {@link PhoneAccount}.
+     * A sim-based {@link PhoneAccount} is one with {@link PhoneAccount#CAPABILITY_SIM_SUBSCRIPTION}
+     * set.
+     */
+    private boolean mIsSimCall;
+
+    /**
+     * Set to {@code true} if we received a valid response ({@code null} or otherwise) from
+     * the {@link CallDiagnostics#onCallDisconnected(ImsReasonInfo)} or
+     * {@link CallDiagnostics#onCallDisconnected(int, int)} calls.  This is used to detect a timeout
+     * when awaiting a response from the call diagnostic service.
+     */
+    private boolean mReceivedCallDiagnosticPostCallResponse = false;
+
+    /**
+     * {@link CompletableFuture} used to delay posting disconnection and removal to a call until
+     * after a {@link CallDiagnosticService} is able to handle the disconnection and provide a
+     * disconnect message via {@link CallDiagnostics#onCallDisconnected(ImsReasonInfo)} or
+     * {@link CallDiagnostics#onCallDisconnected(int, int)}.
+     */
+    private CompletableFuture<Boolean> mDisconnectFuture;
+
+    /**
      * Persists the specified parameters and initializes the new instance.
      * @param context The context.
      * @param repository The connection service repository.
@@ -692,6 +770,8 @@
         mClockProxy = clockProxy;
         mToastFactory = toastFactory;
         mCreationTimeMillis = mClockProxy.currentTimeMillis();
+        mMissedReason = MISSED_REASON_NOT_MISSED;
+        mStartRingTime = 0;
     }
 
     /**
@@ -1040,6 +1120,31 @@
     }
 
     /**
+     * Handles an incoming overridden disconnect message for this call.
+     *
+     * We only care if the disconnect is handled via a future.
+     * @param message the overridden disconnect message.
+     */
+    public void handleOverrideDisconnectMessage(@Nullable CharSequence message) {
+        Log.i(this, "handleOverrideDisconnectMessage; callid=%s, msg=%s", getId(), message);
+
+        if (isDisconnectHandledViaFuture()) {
+            mReceivedCallDiagnosticPostCallResponse = true;
+            if (message != null) {
+                Log.addEvent(this, LogUtils.Events.OVERRIDE_DISCONNECT_MESSAGE, message);
+                // Replace the existing disconnect cause in this call
+                setOverrideDisconnectCauseCode(new DisconnectCause(DisconnectCause.ERROR, message,
+                        message, null));
+            }
+
+            mDisconnectFuture.complete(true);
+        } else {
+            Log.w(this, "handleOverrideDisconnectMessage; callid=%s - got override when unbound",
+                    getId());
+        }
+    }
+
+    /**
      * Sets the call state. Although there exists the notion of appropriate state transitions
      * (see {@link CallState}), in practice those expectations break down when cellular systems
      * misbehave and they do this very often. The result is that we do not enforce state transitions
@@ -1124,6 +1229,12 @@
                 case CallState.ANSWERED:
                     event = LogUtils.Events.SET_ANSWERED;
                     break;
+                case CallState.AUDIO_PROCESSING:
+                    event = LogUtils.Events.SET_AUDIO_PROCESSING;
+                    break;
+                case CallState.SIMULATED_RINGING:
+                    event = LogUtils.Events.SET_SIMULATED_RINGING;
+                    break;
             }
             if (event != null) {
                 // The string data should be just the tag.
@@ -1217,6 +1328,8 @@
     public void setCallerNumberVerificationStatus(
             @Connection.VerificationStatus int callerNumberVerificationStatus) {
         mCallerNumberVerificationStatus = callerNumberVerificationStatus;
+        mListeners.forEach(l -> l.onCallerNumberVerificationStatusChanged(this,
+                callerNumberVerificationStatus));
     }
 
     public @Connection.VerificationStatus int getCallerNumberVerificationStatus() {
@@ -1253,6 +1366,9 @@
                 } catch (IllegalStateException ise) {
                     Log.e(this, ise, "setHandle: can't determine if number is emergency");
                     mIsEmergencyCall = false;
+                } catch (RuntimeException r) {
+                    Log.e(this, r, "setHandle: can't determine if number is emergency");
+                    mIsEmergencyCall = false;
                 }
                 mAnalytics.setCallIsEmergency(mIsEmergencyCall);
             }
@@ -1277,6 +1393,8 @@
                                     number.equals(eNumber.getNumber()));
         } catch (IllegalStateException ise) {
             return false;
+        } catch (RuntimeException r) {
+            return false;
         }
     }
 
@@ -1595,6 +1713,14 @@
         setConnectionProperties(getConnectionProperties());
     }
 
+    public boolean visibleToInCallService() {
+        return mVisibleToInCallService;
+    }
+
+    public void setVisibleToInCallService(boolean visibleToInCallService) {
+        mVisibleToInCallService = visibleToInCallService;
+    }
+
     public void markFinishedHandoverStateAndCleanup(int handoverState) {
         if (mHandoverSourceCall != null) {
             mHandoverSourceCall.setHandoverState(handoverState);
@@ -1651,6 +1777,7 @@
         PhoneAccountRegistrar phoneAccountRegistrar = mCallsManager.getPhoneAccountRegistrar();
         boolean isWorkCall = false;
         boolean isCallRecordingToneSupported = false;
+        boolean isSimCall = false;
         PhoneAccount phoneAccount =
                 phoneAccountRegistrar.getPhoneAccountUnchecked(mTargetPhoneAccountHandle);
         if (phoneAccount != null) {
@@ -1668,9 +1795,11 @@
                     PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION) && phoneAccount.getExtras() != null
                     && phoneAccount.getExtras().getBoolean(
                     PhoneAccount.EXTRA_PLAY_CALL_RECORDING_TONE, false));
+            isSimCall = phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION);
         }
         mIsWorkCall = isWorkCall;
         mUseCallRecordingTone = isCallRecordingToneSupported;
+        mIsSimCall = isSimCall;
     }
 
     /**
@@ -1851,6 +1980,13 @@
             if (didRttChange) {
                 if ((mConnectionProperties & Connection.PROPERTY_IS_RTT) ==
                         Connection.PROPERTY_IS_RTT) {
+                    // If we already had RTT streams up, that means that either the call started
+                    // with RTT or the user previously requested to start RTT. Either way, don't
+                    // play the alert tone.
+                    if (!areRttStreamsInitialized()) {
+                        mCallsManager.playRttUpgradeToneForCall(this);
+                    }
+
                     createRttStreams();
                     // Call startRtt to pass the RTT pipes down to the connection service.
                     // They already turned on the RTT property so no request should be sent.
@@ -1892,6 +2028,16 @@
                 }
             }
 
+            boolean wasDowngradedConference =
+                    (previousProperties & Connection.PROPERTY_IS_DOWNGRADED_CONFERENCE) != 0;
+            boolean isDowngradedConference =
+                    (connectionProperties & Connection.PROPERTY_IS_DOWNGRADED_CONFERENCE) != 0;
+            if (wasDowngradedConference && !isDowngradedConference) {
+                Log.i(this, "DOWNGRADED_CONFERENCE property removed; setting"
+                        + " conference state to false");
+                setConferenceState(false);
+            }
+
             mAnalytics.addCallProperties(mConnectionProperties);
 
             int xorProps = previousProperties ^ mConnectionProperties;
@@ -2429,7 +2575,7 @@
                         "reject call failed due to null CS callId=%s", getId());
             }
             Log.addEvent(this, LogUtils.Events.REQUEST_REJECT, reason);
-        } else if (isRinging("reject")) {
+        } else if (isRinging("reject") || isAnswered("reject")) {
             // Ensure video state history tracks video state at time of rejection.
             mVideoStateHistory |= mVideoState;
 
@@ -2461,7 +2607,7 @@
                         "reject call failed due to null CS callId=%s", getId());
             }
             Log.addEvent(this, LogUtils.Events.REQUEST_REJECT);
-        } else if (isRinging("reject")) {
+        } else if (isRinging("reject") || isAnswered("reject")) {
             // Ensure video state history tracks video state at time of rejection.
             mVideoStateHistory |= mVideoState;
 
@@ -2574,7 +2720,8 @@
         return mState == CallState.ACTIVE;
     }
 
-    Bundle getExtras() {
+    @VisibleForTesting
+    public Bundle getExtras() {
         return mExtras;
     }
 
@@ -2614,6 +2761,16 @@
             setOriginalConnectionId(extras.getString(Connection.EXTRA_ORIGINAL_CONNECTION_ID));
         }
 
+        if (extras.containsKey(Connection.EXTRA_CALLER_NUMBER_VERIFICATION_STATUS)
+                && source == SOURCE_CONNECTION_SERVICE) {
+            int callerNumberVerificationStatus =
+                    extras.getInt(Connection.EXTRA_CALLER_NUMBER_VERIFICATION_STATUS);
+            if (mCallerNumberVerificationStatus != callerNumberVerificationStatus) {
+                Log.addEvent(this, LogUtils.Events.VERSTAT_CHANGED, callerNumberVerificationStatus);
+                setCallerNumberVerificationStatus(callerNumberVerificationStatus);
+            }
+        }
+
         // The remote connection service API can track the phone account which was originally
         // requested to create a connection via the remote connection service API; we store that so
         // we have some visibility into how a call was actually placed.
@@ -2900,6 +3057,14 @@
                 }
                 requestHandover(phoneAccountHandle, videoState, handoverExtrasBundle, true);
             } else {
+                // Relay bluetooth call quality reports to the call diagnostic service.
+                if (BluetoothCallQualityReport.EVENT_BLUETOOTH_CALL_QUALITY_REPORT.equals(event)
+                        && extras.containsKey(
+                        BluetoothCallQualityReport.EXTRA_BLUETOOTH_CALL_QUALITY_REPORT)) {
+                    notifyBluetoothCallQualityReport(extras.getParcelable(
+                            BluetoothCallQualityReport.EXTRA_BLUETOOTH_CALL_QUALITY_REPORT
+                    ));
+                }
                 Log.addEvent(this, LogUtils.Events.CALL_EVENT, event);
                 mConnectionService.sendCallEvent(this, event, extras);
             }
@@ -2910,6 +3075,17 @@
     }
 
     /**
+     * Notifies listeners when a bluetooth quality report is received.
+     * @param report The bluetooth quality report.
+     */
+    void notifyBluetoothCallQualityReport(@NonNull BluetoothCallQualityReport report) {
+        Log.addEvent(this, LogUtils.Events.BT_QUALITY_REPORT, "choppy=" + report.isChoppyVoice());
+        for (Listener l : mListeners) {
+            l.onBluetoothCallQualityReport(this, report);
+        }
+    }
+
+    /**
      * Initiates a handover of this Call to the {@link ConnectionService} identified
      * by destAcct.
      * @param destAcct ConnectionService to which the call should be handed over.
@@ -2985,6 +3161,13 @@
     void setConferenceableCalls(List<Call> conferenceableCalls) {
         mConferenceableCalls.clear();
         mConferenceableCalls.addAll(conferenceableCalls);
+        String confCallIds = "";
+        if (!conferenceableCalls.isEmpty()) {
+            confCallIds = conferenceableCalls.stream()
+                    .map(c -> c.getId())
+                    .collect(Collectors.joining(","));
+        }
+        Log.addEvent(this, LogUtils.Events.CONF_CALLS_CHANGED, confCallIds);
 
         for (Listener l : mListeners) {
             l.onConferenceableCallsChanged(this);
@@ -3142,6 +3325,18 @@
         return false;
     }
 
+    /**
+     * @return True if the call is answered, else logs the action name.
+     */
+    private boolean isAnswered(String actionName) {
+        if (mState == CallState.ANSWERED) {
+            return true;
+        }
+
+        Log.i(this, "Request to %s a non-answered call %s", actionName, this);
+        return false;
+    }
+
     @SuppressWarnings("rawtypes")
     private void decrementAssociatedCallCount(ServiceBinder binder) {
         if (binder != null) {
@@ -3604,7 +3799,10 @@
      * @param extras The extras.
      */
     public void onConnectionEvent(String event, Bundle extras) {
-        Log.addEvent(this, LogUtils.Events.CONNECTION_EVENT, event);
+        // Don't log call quality reports; they're quite frequent and will clog the log.
+        if (!Connection.EVENT_CALL_QUALITY_REPORT.equals(event)) {
+            Log.addEvent(this, LogUtils.Events.CONNECTION_EVENT, event);
+        }
         if (Connection.EVENT_ON_HOLD_TONE_START.equals(event)) {
             mIsRemotelyHeld = true;
             Log.addEvent(this, LogUtils.Events.REMOTELY_HELD);
@@ -3627,6 +3825,23 @@
             for (Listener l : mListeners) {
                 l.onCallSwitchFailed(this);
             }
+        } else if (Connection.EVENT_DEVICE_TO_DEVICE_MESSAGE.equals(event)
+                && extras != null && extras.containsKey(
+                Connection.EXTRA_DEVICE_TO_DEVICE_MESSAGE_TYPE)
+                && extras.containsKey(Connection.EXTRA_DEVICE_TO_DEVICE_MESSAGE_VALUE)) {
+            // Relay an incoming D2D message to interested listeners; most notably the
+            // CallDiagnosticService.
+            int messageType = extras.getInt(Connection.EXTRA_DEVICE_TO_DEVICE_MESSAGE_TYPE);
+            int messageValue = extras.getInt(Connection.EXTRA_DEVICE_TO_DEVICE_MESSAGE_VALUE);
+            for (Listener l : mListeners) {
+                l.onReceivedDeviceToDeviceMessage(this, messageType, messageValue);
+            }
+        } else if (Connection.EVENT_CALL_QUALITY_REPORT.equals(event)
+                && extras != null && extras.containsKey(Connection.EXTRA_CALL_QUALITY_REPORT)) {
+            CallQuality callQuality = extras.getParcelable(Connection.EXTRA_CALL_QUALITY_REPORT);
+            for (Listener l : mListeners) {
+                l.onReceivedCallQualityReport(this, callQuality);
+            }
         } else {
             for (Listener l : mListeners) {
                 l.onConnectionEvent(this, event, extras);
@@ -3792,6 +4007,10 @@
         mIsUsingCallFiltering = isUsingCallFiltering;
     }
 
+    public boolean isUsingCallFiltering() {
+        return mIsUsingCallFiltering;
+    }
+
     /**
      * Returns whether or not Volte call was used.
      *
@@ -3828,6 +4047,44 @@
     }
 
     /**
+     * Sends a device to device message to the other part of the call.
+     * @param message the message type to send.
+     * @param value the value for the message.
+     */
+    public void sendDeviceToDeviceMessage(@CallDiagnostics.MessageType int message, int value) {
+        Log.i(this, "sendDeviceToDeviceMessage; callId=%s, msg=%d/%d", getId(), message, value);
+        Bundle extras = new Bundle();
+        extras.putInt(Connection.EXTRA_DEVICE_TO_DEVICE_MESSAGE_TYPE, message);
+        extras.putInt(Connection.EXTRA_DEVICE_TO_DEVICE_MESSAGE_VALUE, value);
+        // Send to the connection service.
+        sendCallEvent(Connection.EVENT_DEVICE_TO_DEVICE_MESSAGE, extras);
+    }
+
+    /**
+     * Signals to the Dialer app to start displaying a diagnostic message.
+     * @param messageId a unique ID for the message to display.
+     * @param message the message to display.
+     */
+    public void displayDiagnosticMessage(int messageId, @NonNull CharSequence message) {
+        Bundle extras = new Bundle();
+        extras.putInt(android.telecom.Call.EXTRA_DIAGNOSTIC_MESSAGE_ID, messageId);
+        extras.putCharSequence(android.telecom.Call.EXTRA_DIAGNOSTIC_MESSAGE, message);
+        // Send to the dialer.
+        onConnectionEvent(android.telecom.Call.EVENT_DISPLAY_DIAGNOSTIC_MESSAGE, extras);
+    }
+
+    /**
+     * Signals to the Dialer app to stop displaying a diagnostic message.
+     * @param messageId a unique ID for the message to clear.
+     */
+    public void clearDiagnosticMessage(int messageId) {
+        Bundle extras = new Bundle();
+        extras.putInt(android.telecom.Call.EXTRA_DIAGNOSTIC_MESSAGE_ID, messageId);
+        // Send to the dialer.
+        onConnectionEvent(android.telecom.Call.EVENT_CLEAR_DIAGNOSTIC_MESSAGE, extras);
+    }
+
+    /**
      * Remaps the call direction as indicated by an {@link android.telecom.Call.Details} direction
      * constant to the constants (e.g. {@link #CALL_DIRECTION_INCOMING}) used in this call class.
      * @param direction The android.telecom.Call direction.
@@ -3864,4 +4121,127 @@
     public String getPostCallPackageName() {
         return mPostCallPackageName;
     }
+
+    public long getMissedReason() {
+        return mMissedReason;
+    }
+
+    public void setMissedReason(long missedReason) {
+        mMissedReason = missedReason;
+    }
+
+    public void setUserMissed(long code) {
+        mMissedReason |= code;
+    }
+
+    public long getStartRingTime() {
+        return mStartRingTime;
+    }
+
+    public void setStartRingTime(long startRingTime) {
+        mStartRingTime = startRingTime;
+    }
+
+    public CharSequence getCallScreeningAppName() {
+        return mCallScreeningAppName;
+    }
+
+    public void setCallScreeningAppName(CharSequence callScreeningAppName) {
+        mCallScreeningAppName = callScreeningAppName;
+    }
+
+    public String getCallScreeningComponentName() {
+        return mCallScreeningComponentName;
+    }
+
+    public void setCallScreeningComponentName(String callScreeningComponentName) {
+        mCallScreeningComponentName = callScreeningComponentName;
+    }
+
+    public void maybeOnInCallServiceTrackingChanged(boolean isTracking, boolean hasUi) {
+        if (mConnectionService == null) {
+            Log.w(this, "maybeOnInCallServiceTrackingChanged() request on a call"
+                    + " without a connection service.");
+        } else {
+            if (hasUi) {
+                mConnectionService.onUsingAlternativeUi(this, isTracking);
+            } else if (isTracking) {
+                mConnectionService.onTrackedByNonUiService(this, isTracking);
+            }
+        }
+    }
+
+    /**
+     * @return {@code true} when this call originated from a SIM-based {@link PhoneAccount}.
+     * A sim-based {@link PhoneAccount} is one with {@link PhoneAccount#CAPABILITY_SIM_SUBSCRIPTION}
+     * set.
+     */
+    public boolean isSimCall() {
+        return mIsSimCall;
+    }
+
+    /**
+     * Sets whether this is a sim call or not.
+     * @param isSimCall {@code true} if this is a SIM call, {@code false} otherwise.
+     */
+    public void setIsSimCall(boolean isSimCall) {
+        mIsSimCall = isSimCall;
+    }
+
+    /**
+     * Initializes a disconnect future which is used to chain up pending operations which take
+     * place when the {@link CallDiagnosticService} returns the result of the
+     * {@link CallDiagnostics#onCallDisconnected(int, int)} or
+     * {@link CallDiagnostics#onCallDisconnected(ImsReasonInfo)} invocation via
+     * {@link CallDiagnosticServiceAdapter}.  If no {@link CallDiagnosticService} is in use, we
+     * would not try to make a disconnect future.
+     * @param timeoutMillis Timeout we use for waiting for the response.
+     * @return the {@link CompletableFuture}.
+     */
+    public CompletableFuture<Boolean> initializeDisconnectFuture(long timeoutMillis) {
+        if (mDisconnectFuture == null) {
+            mDisconnectFuture = new CompletableFuture<Boolean>()
+                    .completeOnTimeout(false, timeoutMillis, TimeUnit.MILLISECONDS);
+            // After all the chained stuff we will report where the CDS timed out.
+            mDisconnectFuture.thenRunAsync(() -> {
+                if (!mReceivedCallDiagnosticPostCallResponse) {
+                    Log.addEvent(this, LogUtils.Events.CALL_DIAGNOSTIC_SERVICE_TIMEOUT);
+                }
+                // Clear the future as a final step.
+                mDisconnectFuture = null;
+                },
+                new LoggedHandlerExecutor(mHandler, "C.iDF", mLock))
+                    .exceptionally((throwable) -> {
+                        Log.e(this, throwable, "Error while executing disconnect future");
+                        return null;
+                    });
+        }
+        return mDisconnectFuture;
+    }
+
+    /**
+     * @return the disconnect future, if initialized.  Used for chaining operations after creation.
+     */
+    public CompletableFuture<Boolean> getDisconnectFuture() {
+        return mDisconnectFuture;
+    }
+
+    /**
+     * @return {@code true} if disconnection and removal is handled via a future, or {@code false}
+     * if this is handled immediately.
+     */
+    public boolean isDisconnectHandledViaFuture() {
+        return mDisconnectFuture != null;
+    }
+
+    /**
+     * Perform any cleanup on this call as a result of a {@link TelecomServiceImpl}
+     * {@code cleanupStuckCalls} request.
+     */
+    public void cleanup() {
+        if (mDisconnectFuture != null) {
+            mDisconnectFuture.complete(false);
+            mDisconnectFuture = null;
+        }
+    }
 }
diff --git a/src/com/android/server/telecom/CallAudioManager.java b/src/com/android/server/telecom/CallAudioManager.java
index a6509b4..6a7261e 100644
--- a/src/com/android/server/telecom/CallAudioManager.java
+++ b/src/com/android/server/telecom/CallAudioManager.java
@@ -264,6 +264,14 @@
         }
     }
 
+    public void playRttUpgradeTone(Call call) {
+        if (call != mForegroundCall) {
+            // We only play tones for foreground calls.
+            return;
+        }
+        mPlayerFactory.createPlayer(InCallTonePlayer.TONE_RTT_REQUEST).startTone();
+    }
+
     /**
      * Play or stop a call hold tone for a call.  Triggered via
      * {@link Connection#sendConnectionEvent(String)} when the
diff --git a/src/com/android/server/telecom/CallAudioRouteStateMachine.java b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
index 28f9df9..e5a6ecc 100644
--- a/src/com/android/server/telecom/CallAudioRouteStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
@@ -413,6 +413,8 @@
                     return HANDLED;
                 case SWITCH_SPEAKER:
                 case USER_SWITCH_SPEAKER:
+                    setSpeakerphoneOn(true);
+                    // fall through
                 case SPEAKER_ON:
                     transitionTo(mActiveSpeakerRoute);
                     return HANDLED;
@@ -491,6 +493,8 @@
                 case SWITCH_FOCUS:
                     if (msg.arg1 == ACTIVE_FOCUS || msg.arg1 == RINGING_FOCUS) {
                         transitionTo(mActiveEarpieceRoute);
+                    } else {
+                        mCallAudioManager.notifyAudioOperationsComplete();
                     }
                     return HANDLED;
                 default:
@@ -534,6 +538,7 @@
                     // This may be sent as a confirmation by the BT stack after switch off BT.
                     return HANDLED;
                 case CONNECT_DOCK:
+                    setSpeakerphoneOn(true);
                     sendInternalMessage(SWITCH_SPEAKER);
                     return HANDLED;
                 case DISCONNECT_DOCK:
@@ -612,6 +617,8 @@
                     return HANDLED;
                 case SWITCH_SPEAKER:
                 case USER_SWITCH_SPEAKER:
+                    setSpeakerphoneOn(true);
+                    // fall through
                 case SPEAKER_ON:
                     transitionTo(mActiveSpeakerRoute);
                     return HANDLED;
@@ -690,6 +697,8 @@
                 case SWITCH_FOCUS:
                     if (msg.arg1 == ACTIVE_FOCUS || msg.arg1 == RINGING_FOCUS) {
                         transitionTo(mActiveHeadsetRoute);
+                    } else {
+                        mCallAudioManager.notifyAudioOperationsComplete();
                     }
                     return HANDLED;
                 default:
@@ -727,6 +736,7 @@
                     return HANDLED;
                 case DISCONNECT_WIRED_HEADSET:
                     if (mWasOnSpeaker) {
+                        setSpeakerphoneOn(true);
                         sendInternalMessage(SWITCH_SPEAKER);
                     } else {
                         sendInternalMessage(SWITCH_BASELINE_ROUTE, INCLUDE_BLUETOOTH_IN_BASELINE);
@@ -798,6 +808,7 @@
                     transitionTo(mActiveHeadsetRoute);
                     break;
                 case SWITCH_SPEAKER:
+                    setSpeakerphoneOn(true);
                     transitionTo(mActiveSpeakerRoute);
                     break;
                 default:
@@ -854,6 +865,8 @@
                     mHasUserExplicitlyLeftBluetooth = true;
                     // fall through
                 case SWITCH_SPEAKER:
+                    setSpeakerphoneOn(true);
+                    // fall through
                 case SPEAKER_ON:
                     setBluetoothOff();
                     transitionTo(mActiveSpeakerRoute);
@@ -949,6 +962,8 @@
                     mHasUserExplicitlyLeftBluetooth = true;
                     // fall through
                 case SWITCH_SPEAKER:
+                    setSpeakerphoneOn(true);
+                    // fall through
                 case SPEAKER_ON:
                     transitionTo(mActiveSpeakerRoute);
                     return HANDLED;
@@ -1040,6 +1055,8 @@
                         } else {
                             transitionTo(mRingingBluetoothRoute);
                         }
+                    } else {
+                        mCallAudioManager.notifyAudioOperationsComplete();
                     }
                     return HANDLED;
                 case BT_AUDIO_DISCONNECTED:
@@ -1108,8 +1125,9 @@
         @Override
         public void enter() {
             super.enter();
+            // Don't set speakerphone on here -- we might end up in this state by following
+            // the speaker state that some other app commanded.
             mWasOnSpeaker = true;
-            setSpeakerphoneOn(true);
             CallAudioState newState = new CallAudioState(mIsMuted, ROUTE_SPEAKER,
                     mAvailableRoutes, null, mBluetoothRouteManager.getConnectedDevices());
             setSystemAudioState(newState, true);
@@ -1260,7 +1278,10 @@
                     return HANDLED;
                 case SWITCH_FOCUS:
                     if (msg.arg1 == ACTIVE_FOCUS || msg.arg1 == RINGING_FOCUS) {
+                        setSpeakerphoneOn(true);
                         transitionTo(mActiveSpeakerRoute);
+                    } else {
+                        mCallAudioManager.notifyAudioOperationsComplete();
                     }
                     return HANDLED;
                 default:
@@ -1326,6 +1347,15 @@
                     } else {
                         sendInternalMessage(MUTE_EXTERNALLY_CHANGED);
                     }
+                } else if (AudioManager.STREAM_MUTE_CHANGED_ACTION.equals(intent.getAction())) {
+                    int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
+                    boolean isStreamMuted = intent.getBooleanExtra(
+                            AudioManager.EXTRA_STREAM_VOLUME_MUTED, false);
+
+                    if (streamType == AudioManager.STREAM_RING && !isStreamMuted) {
+                        Log.i(this, "Ring stream was un-muted.");
+                        mCallAudioManager.onRingerModeChange();
+                    }
                 } else {
                     Log.w(this, "Received non-mute-change intent");
                 }
@@ -1509,6 +1539,8 @@
         mWasOnSpeaker = false;
         mContext.registerReceiver(mMuteChangeReceiver,
                 new IntentFilter(AudioManager.ACTION_MICROPHONE_MUTE_CHANGED));
+        mContext.registerReceiver(mMuteChangeReceiver,
+                new IntentFilter(AudioManager.STREAM_MUTE_CHANGED_ACTION));
         mContext.registerReceiver(mSpeakerPhoneChangeReceiver,
                 new IntentFilter(AudioManager.ACTION_SPEAKERPHONE_STATE_CHANGED));
 
@@ -1596,12 +1628,8 @@
     }
 
     private void setSpeakerphoneOn(boolean on) {
-        if (mAudioManager.isSpeakerphoneOn() != on) {
-            Log.i(this, "turning speaker phone %s", on);
-            mAudioManager.setSpeakerphoneOn(on);
-        } else {
-            Log.i(this, "Ignoring speakerphone request -- already %s", on);
-        }
+        Log.i(this, "turning speaker phone %s", on);
+        mAudioManager.setSpeakerphoneOn(on);
         mStatusBarNotifier.notifySpeakerphone(on);
     }
 
diff --git a/src/com/android/server/telecom/CallDiagnosticServiceAdapter.java b/src/com/android/server/telecom/CallDiagnosticServiceAdapter.java
new file mode 100644
index 0000000..8623866
--- /dev/null
+++ b/src/com/android/server/telecom/CallDiagnosticServiceAdapter.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2021 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.os.Binder;
+import android.os.RemoteException;
+import android.telecom.CallDiagnosticService;
+import android.telecom.CallDiagnostics;
+import android.telecom.Log;
+
+import com.android.internal.telecom.ICallDiagnosticServiceAdapter;
+
+/**
+ * Adapter class used to provide a path for messages FROM a {@link CallDiagnosticService} back to
+ * the telecom stack.
+ */
+public class CallDiagnosticServiceAdapter extends ICallDiagnosticServiceAdapter.Stub {
+    public interface TelecomAdapter {
+        void displayDiagnosticMessage(String callId, int messageId, CharSequence message);
+        void clearDiagnosticMessage(String callId, int messageId);
+        void sendDeviceToDeviceMessage(String callId, @CallDiagnostics.MessageType int message,
+                int value);
+        void overrideDisconnectMessage(String callId, CharSequence message);
+    }
+
+    private final TelecomAdapter mTelecomAdapter;
+    private final String mOwnerPackageName;
+    private final String mOwnerPackageAbbreviation;
+    private final TelecomSystem.SyncRoot mLock;
+
+    CallDiagnosticServiceAdapter(@NonNull TelecomAdapter telecomAdapter,
+            @NonNull String ownerPackageName, @NonNull TelecomSystem.SyncRoot lock) {
+        mTelecomAdapter = telecomAdapter;
+        mOwnerPackageName = ownerPackageName;
+        mOwnerPackageAbbreviation = Log.getPackageAbbreviation(ownerPackageName);
+        mLock = lock;
+    }
+
+    @Override
+    public void displayDiagnosticMessage(String callId, int messageId, CharSequence message)
+            throws RemoteException {
+        try {
+            Log.startSession("CDSA.dDM", mOwnerPackageAbbreviation);
+            long token = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    Log.i(this, "displayDiagnosticMessage; callId=%s, msg=%d/%s", callId, messageId,
+                            message);
+                    mTelecomAdapter.displayDiagnosticMessage(callId, messageId, message);
+                }
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        } finally {
+            Log.endSession();
+        }
+    }
+
+    @Override
+    public void clearDiagnosticMessage(String callId, int messageId) throws RemoteException {
+        try {
+            Log.startSession("CDSA.cDM", mOwnerPackageAbbreviation);
+            long token = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    Log.i(this, "clearDiagnosticMessage; callId=%s, msg=%d", callId, messageId);
+                    mTelecomAdapter.clearDiagnosticMessage(callId, messageId);
+                }
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        } finally {
+            Log.endSession();
+        }
+    }
+
+    @Override
+    public void sendDeviceToDeviceMessage(String callId, @CallDiagnostics.MessageType int message,
+            int value)
+            throws RemoteException {
+        try {
+            Log.startSession("CDSA.sDTDM", mOwnerPackageAbbreviation);
+            long token = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    Log.i(this, "sendDeviceToDeviceMessage; callId=%s, msg=%d/%d", callId, message,
+                            value);
+                    mTelecomAdapter.sendDeviceToDeviceMessage(callId, message, value);
+                }
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        } finally {
+            Log.endSession();
+        }
+    }
+
+    @Override
+    public void overrideDisconnectMessage(String callId, CharSequence message)
+            throws RemoteException {
+        try {
+            Log.startSession("CDSA.oDM", mOwnerPackageAbbreviation);
+            long token = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    Log.i(this, "overrideDisconnectMessage; callId=%s, msg=%s", callId, message);
+                    mTelecomAdapter.overrideDisconnectMessage(callId, message);
+                }
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        } finally {
+            Log.endSession();
+        }
+    }
+}
diff --git a/src/com/android/server/telecom/CallDiagnosticServiceController.java b/src/com/android/server/telecom/CallDiagnosticServiceController.java
new file mode 100644
index 0000000..d8ee475
--- /dev/null
+++ b/src/com/android/server/telecom/CallDiagnosticServiceController.java
@@ -0,0 +1,722 @@
+/*
+ * Copyright (C) 2021 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.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.UserIdInt;
+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.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.telecom.BluetoothCallQualityReport;
+import android.telecom.CallAudioState;
+import android.telecom.CallDiagnosticService;
+import android.telecom.ConnectionService;
+import android.telecom.CallDiagnostics;
+import android.telecom.DisconnectCause;
+import android.telecom.InCallService;
+import android.telecom.Log;
+import android.telecom.ParcelableCall;
+import android.telephony.CallQuality;
+import android.telephony.ims.ImsReasonInfo;
+import android.text.TextUtils;
+
+import com.android.internal.telecom.ICallDiagnosticService;
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.util.List;
+
+/**
+ * Responsible for maintaining binding to the {@link CallDiagnosticService} defined by the
+ * {@code call_diagnostic_service_package_name} key in the
+ * {@code packages/services/Telecomm/res/values/config.xml} file.
+ */
+public class CallDiagnosticServiceController extends CallsManagerListenerBase {
+    /**
+     * Context dependencies for the {@link CallDiagnosticServiceController}.
+     */
+    public interface ContextProxy {
+        List<ResolveInfo> queryIntentServicesAsUser(@NonNull Intent intent,
+                @PackageManager.ResolveInfoFlags int flags, @UserIdInt int userId);
+        boolean bindServiceAsUser(@NonNull @RequiresPermission Intent service,
+                @NonNull ServiceConnection conn, int flags, @NonNull UserHandle user);
+        void unbindService(@NonNull ServiceConnection conn);
+        UserHandle getCurrentUserHandle();
+    }
+
+    /**
+     * Listener for {@link Call} events; used to propagate these changes to the
+     * {@link CallDiagnosticService}.
+     */
+    private final Call.Listener mCallListener = new Call.ListenerBase() {
+        @Override
+        public void onConnectionCapabilitiesChanged(Call call) {
+            updateCall(call);
+        }
+
+        @Override
+        public void onConnectionPropertiesChanged(Call call, boolean didRttChange) {
+            updateCall(call);
+        }
+
+        /**
+         * Listens for changes to extras reported by a Telecom {@link Call}.
+         *
+         * Extras changes can originate from a {@link ConnectionService} or an {@link InCallService}
+         * so we will only trigger an update of the call information if the source of the extras
+         * change was a {@link ConnectionService}.
+         *
+         * @param call The call.
+         * @param source The source of the extras change ({@link Call#SOURCE_CONNECTION_SERVICE} or
+         *               {@link Call#SOURCE_INCALL_SERVICE}).
+         * @param extras The extras.
+         */
+        @Override
+        public void onExtrasChanged(Call call, int source, Bundle extras) {
+            // Do not inform InCallServices of changes which originated there.
+            if (source == Call.SOURCE_INCALL_SERVICE) {
+                return;
+            }
+            updateCall(call);
+        }
+
+        /**
+         * Listens for changes to extras reported by a Telecom {@link Call}.
+         *
+         * Extras changes can originate from a {@link ConnectionService} or an {@link InCallService}
+         * so we will only trigger an update of the call information if the source of the extras
+         * change was a {@link ConnectionService}.
+         *  @param call The call.
+         * @param source The source of the extras change ({@link Call#SOURCE_CONNECTION_SERVICE} or
+         *               {@link Call#SOURCE_INCALL_SERVICE}).
+         * @param keys The extra key removed
+         */
+        @Override
+        public void onExtrasRemoved(Call call, int source, List<String> keys) {
+            // Do not inform InCallServices of changes which originated there.
+            if (source == Call.SOURCE_INCALL_SERVICE) {
+                return;
+            }
+            updateCall(call);
+        }
+
+        /**
+         * Handles changes to the video state of a call.
+         * @param call
+         * @param previousVideoState
+         * @param newVideoState
+         */
+        @Override
+        public void onVideoStateChanged(Call call, int previousVideoState, int newVideoState) {
+            updateCall(call);
+        }
+
+        /**
+         * Relays a bluetooth call quality report received from the Bluetooth stack to the
+         * CallDiagnosticService.
+         * @param call The call.
+         * @param report The received report.
+         */
+        @Override
+        public void onBluetoothCallQualityReport(Call call, BluetoothCallQualityReport report) {
+            handleBluetoothCallQualityReport(call, report);
+        }
+
+        /**
+         * Relays a device to device message received from Telephony to the CallDiagnosticService.
+         * @param call
+         * @param messageType
+         * @param messageValue
+         */
+        @Override
+        public void onReceivedDeviceToDeviceMessage(Call call, int messageType, int messageValue) {
+            handleReceivedDeviceToDeviceMessage(call, messageType, messageValue);
+        }
+
+        /**
+         * Handles an incoming {@link CallQuality} report from a {@link android.telecom.Connection}.
+         * @param call The call.
+         * @param callQualityReport The call quality report.
+         */
+        @Override
+        public void onReceivedCallQualityReport(Call call, CallQuality callQualityReport) {
+            handleCallQualityReport(call, callQualityReport);
+        }
+    };
+
+    /**
+     * {@link ServiceConnection} handling changes to binding of the {@link CallDiagnosticService}.
+     */
+    private class CallDiagnosticServiceConnection implements ServiceConnection {
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            Log.startSession("CDSC.oSC", Log.getPackageAbbreviation(name));
+            try {
+                synchronized (mLock) {
+                    mCallDiagnosticService = ICallDiagnosticService.Stub.asInterface(service);
+
+                    handleConnectionComplete(mCallDiagnosticService);
+                }
+                Log.i(CallDiagnosticServiceController.this, "onServiceConnected: cmp=%s", name);
+            } finally {
+                Log.endSession();
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            Log.startSession("CDSC.oSD", Log.getPackageAbbreviation(name));
+            try {
+                synchronized (mLock) {
+                    mCallDiagnosticService = null;
+                    mConnection = null;
+                }
+                Log.i(CallDiagnosticServiceController.this, "onServiceDisconnected: cmp=%s", name);
+            } finally {
+                Log.endSession();
+            }
+        }
+
+        @Override
+        public void onBindingDied(ComponentName name) {
+            Log.startSession("CDSC.oBD", Log.getPackageAbbreviation(name));
+            try {
+                synchronized (mLock) {
+                    mCallDiagnosticService = null;
+                    mConnection = null;
+                }
+                Log.w(CallDiagnosticServiceController.this, "onBindingDied: cmp=%s", name);
+            } finally {
+                Log.endSession();
+            }
+        }
+
+        @Override
+        public void onNullBinding(ComponentName name) {
+            Log.startSession("CDSC.oNB", Log.getPackageAbbreviation(name));
+            try {
+                synchronized (mLock) {
+                    maybeUnbindCallScreeningService();
+                }
+            } finally {
+                Log.endSession();
+            }
+        }
+    }
+
+    private final String mPackageName;
+    private final ContextProxy mContextProxy;
+    private InCallTonePlayer.Factory mPlayerFactory;
+    private String mTestPackageName;
+    private CallDiagnosticServiceConnection mConnection;
+    private CallDiagnosticServiceAdapter mAdapter;
+    private final TelecomSystem.SyncRoot mLock;
+    private ICallDiagnosticService mCallDiagnosticService;
+    private final CallIdMapper mCallIdMapper = new CallIdMapper(Call::getId);
+
+    public CallDiagnosticServiceController(@NonNull ContextProxy contextProxy,
+            @Nullable String packageName, @NonNull TelecomSystem.SyncRoot lock) {
+        mContextProxy = contextProxy;
+        mPackageName = packageName;
+        mLock = lock;
+    }
+
+    /**
+     * Sets the current {@link InCallTonePlayer.Factory} for this instance.
+     * @param factory the factory.
+     */
+    public void setInCallTonePlayerFactory(InCallTonePlayer.Factory factory) {
+        mPlayerFactory = factory;
+    }
+
+    /**
+     * Handles Telecom adding new calls.  Will bind to the call diagnostic service if needed and
+     * send the calls, or send to an already bound service.
+     * @param call The call to add.
+     */
+    @Override
+    public void onCallAdded(@NonNull Call call) {
+        if (!call.isSimCall() || call.isExternalCall()) {
+            Log.i(this, "onCallAdded: skipping call %s as non-sim or external.", call.getId());
+            return;
+        }
+        if (mCallIdMapper.getCallId(call) == null) {
+            mCallIdMapper.addCall(call);
+            call.addListener(mCallListener);
+        }
+        if (isConnected()) {
+            sendCallToBoundService(call, mCallDiagnosticService);
+        } else {
+            maybeBindCallDiagnosticService();
+        }
+    }
+
+    /**
+     * Handles a newly disconnected call signalled from {@link CallsManager}.
+     * @param call The call
+     * @param disconnectCause The disconnect cause
+     * @return {@code true} if the {@link CallDiagnosticService} was sent the call, {@code false}
+     * if the call was not applicable to the CDS or if there was an issue sending it.
+     */
+    public boolean onCallDisconnected(@NonNull Call call,
+            @NonNull DisconnectCause disconnectCause) {
+        if (!call.isSimCall() || call.isExternalCall()) {
+            Log.i(this, "onCallDisconnected: skipping call %s as non-sim or external.",
+                    call.getId());
+            return false;
+        }
+        String callId = mCallIdMapper.getCallId(call);
+        try {
+            if (isConnected()) {
+                mCallDiagnosticService.notifyCallDisconnected(callId, disconnectCause);
+                return true;
+            }
+        } catch (RemoteException e) {
+            Log.w(this, "onCallDisconnected: callId=%s, exception=%s", call.getId(), e);
+        }
+        return false;
+    }
+
+    /**
+     * Handles Telecom removal of calls; will remove the call from the bound service and if the
+     * number of tracked calls falls to zero, unbind from the service.
+     * @param call The call to remove from the bound CDS.
+     */
+    @Override
+    public void onCallRemoved(@NonNull Call call) {
+        if (!call.isSimCall() || call.isExternalCall()) {
+            Log.i(this, "onCallRemoved: skipping call %s as non-sim or external.", call.getId());
+            return;
+        }
+        mCallIdMapper.removeCall(call);
+        call.removeListener(mCallListener);
+        removeCallFromBoundService(call, mCallDiagnosticService);
+
+        if (mCallIdMapper.getCalls().size() == 0) {
+            maybeUnbindCallScreeningService();
+        }
+    }
+
+    @Override
+    public void onCallStateChanged(Call call, int oldState, int newState) {
+        updateCall(call);
+    }
+
+    @Override
+    public void onCallAudioStateChanged(CallAudioState oldCallAudioState,
+            CallAudioState newCallAudioState) {
+        if (mCallDiagnosticService != null) {
+            try {
+                mCallDiagnosticService.updateCallAudioState(newCallAudioState);
+            } catch (RemoteException e) {
+                Log.w(this, "onCallAudioStateChanged: failed %s", e);
+            }
+        }
+    }
+
+    /**
+     * Sets the test call diagnostic service; used by the telecom command line command to override
+     * the {@link CallDiagnosticService} to bind to for CTS test purposes.
+     * @param packageName The package name to set to.
+     */
+    public void setTestCallDiagnosticService(@Nullable String packageName) {
+        if (TextUtils.isEmpty(packageName)) {
+            mTestPackageName = null;
+        } else {
+            mTestPackageName = packageName;
+        }
+
+        Log.i(this, "setTestCallDiagnosticService: packageName=%s", packageName);
+    }
+
+    /**
+     * Determines the active call diagnostic service, taking into account the test override.
+     * @return The package name of the active call diagnostic service.
+     */
+    private @Nullable String getActiveCallDiagnosticService() {
+        if (mTestPackageName != null) {
+            return mTestPackageName;
+        }
+
+        return mPackageName;
+    }
+
+    /**
+     * If we are not already bound to the {@link CallDiagnosticService}, attempts to initiate a
+     * binding tho that service.
+     * @return {@code true} if we bound, {@code false} otherwise.
+     */
+    private boolean maybeBindCallDiagnosticService() {
+        if (mConnection != null) {
+            return false;
+        }
+
+        mConnection = new CallDiagnosticServiceConnection();
+        boolean bound = bindCallDiagnosticService(mContextProxy.getCurrentUserHandle(),
+                getActiveCallDiagnosticService(), mConnection);
+        if (!bound) {
+            mConnection = null;
+        }
+        return bound;
+    }
+
+    /**
+     * Performs binding to the {@link CallDiagnosticService}.
+     * @param userHandle user name to bind via.
+     * @param packageName package name of the CDS.
+     * @param serviceConnection The service connection to be notified of bind events.
+     * @return
+     */
+    private boolean bindCallDiagnosticService(UserHandle userHandle,
+            String packageName, CallDiagnosticServiceConnection serviceConnection) {
+
+        if (TextUtils.isEmpty(packageName)) {
+            Log.i(this, "bindCallDiagnosticService: no package; skip binding.");
+            return false;
+        }
+
+        Intent intent = new Intent(CallDiagnosticService.SERVICE_INTERFACE)
+                .setPackage(packageName);
+        Log.i(this, "bindCallDiagnosticService: user %d.", userHandle.getIdentifier());
+        List<ResolveInfo> entries = mContextProxy.queryIntentServicesAsUser(intent, 0,
+                userHandle.getIdentifier());
+        if (entries.isEmpty()) {
+            Log.i(this, "bindCallDiagnosticService: %s has no service.", packageName);
+            return false;
+        }
+
+        ResolveInfo entry = entries.get(0);
+        if (entry.serviceInfo == null) {
+            Log.i(this, "bindCallDiagnosticService: %s has no service info.", packageName);
+            return false;
+        }
+
+        if (entry.serviceInfo.permission == null || !entry.serviceInfo.permission.equals(
+                Manifest.permission.BIND_CALL_DIAGNOSTIC_SERVICE)) {
+            Log.i(this, "bindCallDiagnosticService: %s doesn't require "
+                    + "BIND_CALL_DIAGNOSTIC_SERVICE.", packageName);
+            return false;
+        }
+
+        ComponentName componentName =
+                new ComponentName(entry.serviceInfo.packageName, entry.serviceInfo.name);
+        intent.setComponent(componentName);
+        if (mContextProxy.bindServiceAsUser(
+                intent,
+                serviceConnection,
+                Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
+                UserHandle.CURRENT)) {
+            Log.d(this, "bindCallDiagnosticService, found service, waiting for it to connect");
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * If we are bound to a {@link CallDiagnosticService}, unbind from it.
+     */
+    public void maybeUnbindCallScreeningService() {
+        if (mConnection != null) {
+            Log.i(this, "maybeUnbindCallScreeningService - unbinding from %s",
+                    getActiveCallDiagnosticService());
+            try {
+                mContextProxy.unbindService(mConnection);
+                mCallDiagnosticService = null;
+                mConnection = null;
+            } catch (IllegalArgumentException e) {
+                Log.i(this, "maybeUnbindCallScreeningService: Exception when unbind %s : %s",
+                        getActiveCallDiagnosticService(), e.getMessage());
+            }
+        } else {
+            Log.w(this, "maybeUnbindCallScreeningService - already unbound");
+        }
+    }
+
+    /**
+     * Implements the abstracted Telecom functionality the {@link CallDiagnosticServiceAdapter}
+     * depends on.
+     */
+    private CallDiagnosticServiceAdapter.TelecomAdapter mTelecomAdapter =
+            new CallDiagnosticServiceAdapter.TelecomAdapter() {
+
+        @Override
+        public void displayDiagnosticMessage(String callId, int messageId, CharSequence message) {
+            handleDisplayDiagnosticMessage(callId, messageId, message);
+        }
+
+        @Override
+        public void clearDiagnosticMessage(String callId, int messageId) {
+            handleClearDiagnosticMessage(callId, messageId);
+        }
+
+        @Override
+        public void sendDeviceToDeviceMessage(String callId,
+                        @CallDiagnostics.MessageType int message, int value) {
+            handleSendD2DMessage(callId, message, value);
+        }
+
+        @Override
+        public void overrideDisconnectMessage(String callId, CharSequence message) {
+            handleOverrideDisconnectMessage(callId, message);
+        }
+    };
+
+    /**
+     * Sends all calls to the specified {@link CallDiagnosticService}.
+     * @param callDiagnosticService the CDS to send calls to.
+     */
+    private void handleConnectionComplete(@NonNull ICallDiagnosticService callDiagnosticService) {
+        mAdapter = new CallDiagnosticServiceAdapter(mTelecomAdapter,
+                getActiveCallDiagnosticService(), mLock);
+        try {
+            // Add adapter for communication back from the call diagnostic service to Telecom.
+            callDiagnosticService.setAdapter(mAdapter);
+
+            // Loop through all the calls we've got ready to send since binding.
+            for (Call call : mCallIdMapper.getCalls()) {
+                sendCallToBoundService(call, callDiagnosticService);
+            }
+        } catch (RemoteException e) {
+            Log.w(this, "handleConnectionComplete: error=%s", e);
+        }
+    }
+
+    /**
+     * Handles a request from a {@link CallDiagnosticService} to display a diagnostic message.
+     * @param callId the ID of the call to display the message for.
+     * @param message the message.
+     */
+    private void handleDisplayDiagnosticMessage(@NonNull String callId, int messageId,
+            @Nullable CharSequence message) {
+        Call call = mCallIdMapper.getCall(callId);
+        if (call == null) {
+            Log.w(this, "handleDisplayDiagnosticMessage: callId=%s; msg=%d/%s; invalid call",
+                    callId, messageId, message);
+            return;
+        }
+        Log.i(this, "handleDisplayDiagnosticMessage: callId=%s; msg=%d/%s",
+                callId, messageId, message);
+        if (mPlayerFactory != null) {
+            // Play that tone!
+            mPlayerFactory.createPlayer(InCallTonePlayer.TONE_IN_CALL_QUALITY_NOTIFICATION)
+                    .startTone();
+        }
+        call.displayDiagnosticMessage(messageId, message);
+    }
+
+    /**
+     * Handles a request from a {@link CallDiagnosticService} to clear a previously displayed
+     * diagnostic message.
+     * @param callId the ID of the call to display the message for.
+     * @param messageId the message ID which was previous posted.
+     */
+    private void handleClearDiagnosticMessage(@NonNull String callId, int messageId) {
+        Call call = mCallIdMapper.getCall(callId);
+        if (call == null) {
+            Log.w(this, "handleClearDiagnosticMessage: callId=%s; msg=%d; invalid call",
+                    callId, messageId);
+            return;
+        }
+        Log.i(this, "handleClearDiagnosticMessage: callId=%s; msg=%d; invalid call",
+                callId, messageId);
+        call.clearDiagnosticMessage(messageId);
+    }
+
+    /**
+     * Handles a request from a {@link CallDiagnosticService} to send a device to device message.
+     * @param callId The ID of the call to send the D2D message for.
+     * @param message The message type.
+     * @param value The message value.
+     */
+    private void handleSendD2DMessage(@NonNull String callId,
+            @CallDiagnostics.MessageType int message, int value) {
+        Call call = mCallIdMapper.getCall(callId);
+        if (call == null) {
+            Log.w(this, "handleSendD2DMessage: callId=%s; msg=%d/%d; invalid call", callId,
+                    message, value);
+            return;
+        }
+        Log.i(this, "handleSendD2DMessage: callId=%s; msg=%d/%d", callId, message, value);
+        call.sendDeviceToDeviceMessage(message, value);
+    }
+
+    /**
+     * Handles a request from a {@link CallDiagnosticService} to override the disconnect message
+     * for a call.  This is the response path from a previous call into the
+     * {@link CallDiagnosticService} via {@link CallDiagnostics#onCallDisconnected(ImsReasonInfo)}.
+     * @param callId The telecom call ID the disconnect override is pending for.
+     * @param message The new disconnect message, or {@code null} if no override.
+     */
+    private void handleOverrideDisconnectMessage(@NonNull String callId,
+            @Nullable CharSequence message) {
+        Call call = mCallIdMapper.getCall(callId);
+        if (call == null) {
+            Log.w(this, "handleOverrideDisconnectMessage: callId=%s; msg=%s; invalid call", callId,
+                    message);
+            return;
+        }
+        Log.i(this, "handleOverrideDisconnectMessage: callId=%s; msg=%s", callId, message);
+        call.handleOverrideDisconnectMessage(message);
+    }
+
+    /**
+     * Sends a single call to the bound {@link CallDiagnosticService}.
+     * @param call The call to send.
+     * @param callDiagnosticService The CDS to send it to.
+     */
+    private void sendCallToBoundService(@NonNull Call call,
+            @NonNull ICallDiagnosticService callDiagnosticService) {
+        try {
+            if (isConnected()) {
+                Log.w(this, "sendCallToBoundService: initializing %s", call.getId());
+                callDiagnosticService.initializeDiagnosticCall(getParceledCall(call));
+            } else {
+                Log.w(this, "sendCallToBoundService: not bound, skipping %s", call.getId());
+            }
+        } catch (RemoteException e) {
+            Log.w(this, "sendCallToBoundService: callId=%s, exception=%s", call.getId(), e);
+        }
+    }
+
+    /**
+     * Removes a call from a bound {@link CallDiagnosticService}.
+     * @param call The call to remove.
+     * @param callDiagnosticService The CDS to remove it from.
+     */
+    private void removeCallFromBoundService(@NonNull Call call,
+            @NonNull ICallDiagnosticService callDiagnosticService) {
+        try {
+            if (isConnected()) {
+                callDiagnosticService.removeDiagnosticCall(call.getId());
+            }
+        } catch (RemoteException e) {
+            Log.w(this, "removeCallFromBoundService: callId=%s, exception=%s", call.getId(), e);
+        }
+    }
+
+    /**
+     * @return {@code true} if the call diagnostic service is bound/connected.
+     */
+    public boolean isConnected() {
+        return mCallDiagnosticService != null;
+    }
+
+    /**
+     * Updates the Call diagnostic service with changes to a call.
+     * @param call The updated call.
+     */
+    private void updateCall(@NonNull Call call) {
+        try {
+            if (isConnected()) {
+                mCallDiagnosticService.updateCall(getParceledCall(call));
+            }
+        } catch (RemoteException e) {
+            Log.w(this, "updateCall: callId=%s, exception=%s", call.getId(), e);
+        }
+    }
+
+    /**
+     * Updates the call diagnostic service with a received bluetooth quality report.
+     * @param call The call.
+     * @param report The bluetooth call quality report.
+     */
+    private void handleBluetoothCallQualityReport(@NonNull Call call,
+            @NonNull BluetoothCallQualityReport report) {
+        try {
+            if (isConnected()) {
+                mCallDiagnosticService.receiveBluetoothCallQualityReport(report);
+            }
+        } catch (RemoteException e) {
+            Log.w(this, "handleBluetoothCallQualityReport: callId=%s, exception=%s", call.getId(),
+                    e);
+        }
+    }
+
+    /**
+     * Informs a CallDiagnosticService of an incoming device to device message which was received
+     * via the carrier network.
+     * @param call the call the message was received via.
+     * @param messageType The message type.
+     * @param messageValue The message value.
+     */
+    private void handleReceivedDeviceToDeviceMessage(@NonNull Call call, int messageType,
+            int messageValue) {
+        try {
+            if (isConnected()) {
+                mCallDiagnosticService.receiveDeviceToDeviceMessage(call.getId(), messageType,
+                        messageValue);
+            }
+        } catch (RemoteException e) {
+            Log.w(this, "handleReceivedDeviceToDeviceMessage: callId=%s, exception=%s",
+                    call.getId(), e);
+        }
+    }
+
+    /**
+     * Handles a reported {@link CallQuality} report from a {@link android.telecom.Connection}.
+     * @param call The call the report originated from.
+     * @param callQualityReport The {@link CallQuality} report.
+     */
+    private void handleCallQualityReport(@NonNull Call call,
+            @NonNull CallQuality callQualityReport) {
+        try {
+            if (isConnected()) {
+                mCallDiagnosticService.callQualityChanged(call.getId(), callQualityReport);
+            }
+        } catch (RemoteException e) {
+            Log.w(this, "handleCallQualityReport: callId=%s, exception=%s",
+                    call.getId(), e);
+        }
+    }
+
+    /**
+     * Get a parcelled representation of a call for transport to the service.
+     * @param call The call.
+     * @return The parcelled call.
+     */
+    private @NonNull ParcelableCall getParceledCall(@NonNull Call call) {
+        return ParcelableCallUtils.toParcelableCall(
+                call,
+                false /* includeVideoProvider */,
+                null /* phoneAcctRegistrar */,
+                false /* supportsExternalCalls */,
+                false /* includeRttCall */,
+                false /* isForSystemDialer */
+        );
+    }
+
+    /**
+     * Dumps the state of the {@link CallDiagnosticServiceController}.
+     *
+     * @param pw The {@code IndentingPrintWriter} to write the state to.
+     */
+    public void dump(IndentingPrintWriter pw) {
+        pw.print("activeCallDiagnosticService: ");
+        pw.println(getActiveCallDiagnosticService());
+        pw.print("isConnected: ");
+        pw.println(isConnected());
+    }
+}
diff --git a/src/com/android/server/telecom/CallIdMapper.java b/src/com/android/server/telecom/CallIdMapper.java
index 72a51f5..2cd5c79 100644
--- a/src/com/android/server/telecom/CallIdMapper.java
+++ b/src/com/android/server/telecom/CallIdMapper.java
@@ -72,6 +72,10 @@
             return mSecondaryMap.get(value);
         }
 
+        public Collection<V> getValues() {
+            return mPrimaryMap.values();
+        }
+
         public void clear() {
             mPrimaryMap.clear();
             mSecondaryMap.clear();
@@ -134,7 +138,7 @@
     }
 
     Collection<Call> getCalls() {
-        return mCalls.mPrimaryMap.values();
+        return mCalls.getValues();
     }
 
     void clear() {
diff --git a/src/com/android/server/telecom/CallIntentProcessor.java b/src/com/android/server/telecom/CallIntentProcessor.java
index 7305ab9..7f864b8 100644
--- a/src/com/android/server/telecom/CallIntentProcessor.java
+++ b/src/com/android/server/telecom/CallIntentProcessor.java
@@ -141,6 +141,23 @@
             clientExtras.putString(TelecomManager.EXTRA_CALL_SUBJECT, callsubject);
         }
 
+        if (intent.hasExtra(android.telecom.TelecomManager.EXTRA_PRIORITY)) {
+            clientExtras.putInt(android.telecom.TelecomManager.EXTRA_PRIORITY, intent.getIntExtra(
+                    android.telecom.TelecomManager.EXTRA_PRIORITY,
+                            android.telecom.TelecomManager.PRIORITY_NORMAL));
+        }
+
+        if (intent.hasExtra(android.telecom.TelecomManager.EXTRA_LOCATION)) {
+            clientExtras.putParcelable(android.telecom.TelecomManager.EXTRA_LOCATION,
+                    intent.getParcelableExtra(android.telecom.TelecomManager.EXTRA_LOCATION));
+        }
+
+        if (intent.hasExtra(android.telecom.TelecomManager.EXTRA_OUTGOING_PICTURE)) {
+            clientExtras.putParcelable(android.telecom.TelecomManager.EXTRA_OUTGOING_PICTURE,
+                    intent.getParcelableExtra(
+                            android.telecom.TelecomManager.EXTRA_OUTGOING_PICTURE));
+        }
+
         final int videoState = intent.getIntExtra( TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
                 VideoProfile.STATE_AUDIO_ONLY);
         clientExtras.putInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, videoState);
diff --git a/src/com/android/server/telecom/CallLogManager.java b/src/com/android/server/telecom/CallLogManager.java
index 7a5af14..0ec2362 100755
--- a/src/com/android/server/telecom/CallLogManager.java
+++ b/src/com/android/server/telecom/CallLogManager.java
@@ -16,6 +16,7 @@
 
 package com.android.server.telecom;
 
+import static android.provider.CallLog.Calls.BLOCK_REASON_NOT_BLOCKED;
 import static android.telephony.CarrierConfigManager.KEY_SUPPORT_IMS_CONFERENCE_EVENT_PACKAGE_BOOL;
 
 import android.annotation.Nullable;
@@ -23,25 +24,26 @@
 import android.content.Intent;
 import android.location.Country;
 import android.location.CountryDetector;
+import android.location.Location;
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Looper;
 import android.os.UserHandle;
 import android.os.PersistableBundle;
+import android.provider.CallLog;
 import android.provider.CallLog.Calls;
 import android.telecom.Connection;
 import android.telecom.DisconnectCause;
 import android.telecom.Log;
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
+import android.telecom.TelecomManager;
 import android.telecom.VideoProfile;
 import android.telephony.CarrierConfigManager;
 import android.telephony.PhoneNumberUtils;
 import android.telephony.SubscriptionManager;
 
-// TODO: Needed for move to system service: import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
-import android.telecom.CallerInfo;
 import com.android.server.telecom.callfiltering.CallFilteringResult;
 
 import java.util.Arrays;
@@ -65,68 +67,19 @@
      * Parameter object to hold the arguments to add a call in the call log DB.
      */
     private static class AddCallArgs {
-        /**
-         * @param callerInfo Caller details.
-         * @param number The phone number to be logged.
-         * @param presentation Number presentation of the phone number to be logged.
-         * @param callType The type of call (e.g INCOMING_TYPE). @see
-         *     {@link android.provider.CallLog} for the list of values.
-         * @param features The features of the call (e.g. FEATURES_VIDEO). @see
-         *     {@link android.provider.CallLog} for the list of values.
-         * @param creationDate Time when the call was created (milliseconds since epoch).
-         * @param durationInMillis Duration of the call (milliseconds).
-         * @param dataUsage Data usage in bytes, or null if not applicable.
-         * @param isRead Indicates if the entry has been read or not.
-         * @param logCallCompletedListener optional callback called after the call is logged.
-         */
-        public AddCallArgs(Context context, CallerInfo callerInfo, String number,
-                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, int callBlockReason,
-                CharSequence callScreeningAppName, String callScreeningComponentName) {
+        public AddCallArgs(Context context, CallLog.AddCallParams params,
+                @Nullable LogCallCompletedListener logCallCompletedListener) {
             this.context = context;
-            this.callerInfo = callerInfo;
-            this.number = number;
-            this.postDialDigits = postDialDigits;
-            this.viaNumber = viaNumber;
-            this.presentation = presentation;
-            this.callType = callType;
-            this.features = features;
-            this.accountHandle = accountHandle;
-            this.timestamp = creationDate;
-            this.durationInSec = (int)(durationInMillis / 1000);
-            this.dataUsage = dataUsage;
-            this.initiatingUser = initiatingUser;
-            this.isRead = isRead;
+            this.params = params;
             this.logCallCompletedListener = logCallCompletedListener;
-            this.callBockReason = callBlockReason;
-            this.callScreeningAppName = callScreeningAppName;
-            this.callScreeningComponentName = callScreeningComponentName;
+
         }
         // Since the members are accessed directly, we don't use the
         // mXxxx notation.
         public final Context context;
-        public final CallerInfo callerInfo;
-        public final String number;
-        public final String postDialDigits;
-        public final String viaNumber;
-        public final int presentation;
-        public final int callType;
-        public final int features;
-        public final PhoneAccountHandle accountHandle;
-        public final long timestamp;
-        public final int durationInSec;
-        public final Long dataUsage;
-        public final UserHandle initiatingUser;
-        public final boolean isRead;
-
+        public final CallLog.AddCallParams params;
         @Nullable
         public final LogCallCompletedListener logCallCompletedListener;
-
-        public final int callBockReason;
-        public final CharSequence callScreeningAppName;
-        public final String callScreeningComponentName;
     }
 
     private static final String TAG = CallLogManager.class.getSimpleName();
@@ -311,106 +264,133 @@
      */
     void logCall(Call call, int callLogType,
         @Nullable LogCallCompletedListener logCallCompletedListener, CallFilteringResult result) {
-        long creationTime;
 
+        CallLog.AddCallParams.AddCallParametersBuilder paramBuilder =
+                new CallLog.AddCallParams.AddCallParametersBuilder();
         if (call.getConnectTimeMillis() != 0
                 && call.getConnectTimeMillis() < call.getCreationTimeMillis()) {
             // If connected time is available, use connected time. The connected time might be
             // earlier than created time since it might come from carrier sent special SMS to
             // notifier user earlier missed call.
-            creationTime = call.getConnectTimeMillis();
+            paramBuilder.setStart(call.getConnectTimeMillis());
         } else {
-            creationTime = call.getCreationTimeMillis();
+            paramBuilder.setStart(call.getCreationTimeMillis());
         }
 
-        final long age = call.getAgeMillis();
+        paramBuilder.setDuration((int) (call.getAgeMillis() / 1000));
 
-        final String logNumber = getLogNumber(call);
+        String logNumber = getLogNumber(call);
+        paramBuilder.setNumber(logNumber);
 
         Log.d(TAG, "logNumber set to: %s", Log.pii(logNumber));
 
-        final PhoneAccountHandle emergencyAccountHandle =
-                TelephonyUtil.getDefaultEmergencyPhoneAccount().getAccountHandle();
-
         String formattedViaNumber = PhoneNumberUtils.formatNumber(call.getViaNumber(),
                 getCountryIso());
         formattedViaNumber = (formattedViaNumber != null) ?
                 formattedViaNumber : call.getViaNumber();
+        paramBuilder.setViaNumber(formattedViaNumber);
 
+        final PhoneAccountHandle emergencyAccountHandle =
+                TelephonyUtil.getDefaultEmergencyPhoneAccount().getAccountHandle();
         PhoneAccountHandle accountHandle = call.getTargetPhoneAccount();
         if (emergencyAccountHandle.equals(accountHandle)) {
             accountHandle = null;
         }
+        paramBuilder.setAccountHandle(accountHandle);
 
-        Long callDataUsage = call.getCallDataUsage() == Call.DATA_USAGE_NOT_SET ? null :
-                call.getCallDataUsage();
+        paramBuilder.setDataUsage(call.getCallDataUsage() == Call.DATA_USAGE_NOT_SET
+                ? Long.MIN_VALUE : call.getCallDataUsage());
 
-        int callFeatures = getCallFeatures(call.getVideoStateHistory(),
+        paramBuilder.setFeatures(getCallFeatures(call.getVideoStateHistory(),
                 call.getDisconnectCause().getCode() == DisconnectCause.CALL_PULLED,
                 call.wasHighDefAudio(), call.wasWifi(),
                 (call.getConnectionProperties() & Connection.PROPERTY_ASSISTED_DIALING) ==
                         Connection.PROPERTY_ASSISTED_DIALING,
                 call.wasEverRttCall(),
-                call.wasVolte());
+                call.wasVolte()));
 
-        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);
+        if (result == null) {
+            result = new CallFilteringResult.Builder()
+                    .setCallScreeningAppName(call.getCallScreeningAppName())
+                    .setCallScreeningComponentName(call.getCallScreeningComponentName())
+                    .build();
+        }
+        if (callLogType == Calls.BLOCKED_TYPE || callLogType == Calls.MISSED_TYPE) {
+            paramBuilder.setCallBlockReason(result.mCallBlockReason);
+            paramBuilder.setCallScreeningComponentName(result.mCallScreeningComponentName);
+            paramBuilder.setCallScreeningAppName(result.mCallScreeningAppName);
         } 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*/);
+            paramBuilder.setCallBlockReason(BLOCK_REASON_NOT_BLOCKED);
+        }
+
+        PhoneAccount phoneAccount = mPhoneAccountRegistrar.getPhoneAccountUnchecked(accountHandle);
+        UserHandle initiatingUser = call.getInitiatingUser();
+        if (phoneAccount != null &&
+                phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_MULTI_USER)) {
+            if (initiatingUser != null &&
+                    UserUtil.isManagedProfile(mContext, initiatingUser)) {
+                paramBuilder.setUserToBeInsertedTo(initiatingUser);
+                paramBuilder.setAddForAllUsers(false);
+            } else {
+                paramBuilder.setAddForAllUsers(true);
+            }
+        } else {
+            if (accountHandle == null) {
+                paramBuilder.setAddForAllUsers(true);
+            } else {
+                paramBuilder.setUserToBeInsertedTo(accountHandle.getUserHandle());
+                paramBuilder.setAddForAllUsers(accountHandle.getUserHandle() == null);
+            }
+        }
+        if (call.getIntentExtras() != null) {
+            if (call.getIntentExtras().containsKey(TelecomManager.EXTRA_PRIORITY)) {
+                paramBuilder.setPriority(call.getIntentExtras()
+                        .getInt(TelecomManager.EXTRA_PRIORITY));
+            }
+            if (call.getIntentExtras().containsKey(TelecomManager.EXTRA_CALL_SUBJECT)) {
+                paramBuilder.setSubject(call.getIntentExtras()
+                        .getString(TelecomManager.EXTRA_CALL_SUBJECT));
+            }
+            if (call.getIntentExtras().containsKey(TelecomManager.EXTRA_PICTURE_URI)) {
+                paramBuilder.setPictureUri(call.getIntentExtras()
+                        .getParcelable(TelecomManager.EXTRA_PICTURE_URI));
+            }
+            // The picture uri can end up either in extras or in intent extras due to how these
+            // two bundles are set. For incoming calls they're in extras, but for outgoing calls
+            // they're in intentExtras.
+            if (call.getExtras() != null
+                    && call.getExtras().containsKey(TelecomManager.EXTRA_PICTURE_URI)) {
+                paramBuilder.setPictureUri(call.getExtras()
+                        .getParcelable(TelecomManager.EXTRA_PICTURE_URI));
+            }
+            if (call.getIntentExtras().containsKey(TelecomManager.EXTRA_LOCATION)) {
+                Location l = call.getIntentExtras().getParcelable(TelecomManager.EXTRA_LOCATION);
+                if (l != null) {
+                    paramBuilder.setLatitude(l.getLatitude());
+                    paramBuilder.setLongitude(l.getLongitude());
+                }
+            }
+        }
+
+        paramBuilder.setCallerInfo(call.getCallerInfo());
+        paramBuilder.setPostDialDigits(call.getPostDialDigits());
+        paramBuilder.setPresentation(call.getHandlePresentation());
+        paramBuilder.setCallType(callLogType);
+        paramBuilder.setIsRead(call.isSelfManaged());
+        paramBuilder.setMissedReason(call.getMissedReason());
+
+        sendAddCallBroadcast(callLogType, call.getAgeMillis());
+
+        boolean okayToLog =
+                okayToLogCall(accountHandle, logNumber, call.isEmergencyCall());
+        if (okayToLog) {
+            AddCallArgs args = new AddCallArgs(mContext, paramBuilder.build(),
+                    logCallCompletedListener);
+            logCallAsync(args);
         }
     }
 
-    /**
-     * Inserts a call into the call log, based on the parameters passed in.
-     *
-     * @param callerInfo Caller details.
-     * @param number The number the call was made to or from.
-     * @param postDialDigits The post-dial digits that were dialed after the number,
-     *                       if it was an outgoing call. Otherwise ''.
-     * @param presentation
-     * @param callType The type of call.
-     * @param features The features of the call.
-     * @param start The start time of the call, in milliseconds.
-     * @param duration The duration of the call, in milliseconds.
-     * @param dataUsage The data usage for the call, null if not applicable.
-     * @param isEmergency {@code true} if this is an emergency call, {@code false} otherwise.
-     * @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.
-     */
-    private void logCall(
-            CallerInfo callerInfo,
-            String number,
-            String postDialDigits,
-            String viaNumber,
-            int presentation,
-            int callType,
-            int features,
-            PhoneAccountHandle accountHandle,
-            long start,
-            long duration,
-            Long dataUsage,
-            boolean isEmergency,
-            UserHandle initiatingUser,
-            boolean isSelfManaged,
-            @Nullable LogCallCompletedListener logCallCompletedListener,
-            int callBlockReason,
-            CharSequence callScreeningAppName,
-            String callScreeningComponentName) {
-
+    boolean okayToLogCall(PhoneAccountHandle accountHandle, String number, boolean isEmergency) {
         // On some devices, to avoid accidental redialing of emergency numbers, we *never* log
         // emergency calls to the Call Log.  (This behavior is set on a per-product basis, based
         // on carrier requirements.)
@@ -425,29 +405,8 @@
         }
 
         // Don't log emergency numbers if the device doesn't allow it.
-        final boolean isOkToLogThisCall = (!isEmergency || okToLogEmergencyNumber)
+        return (!isEmergency || okToLogEmergencyNumber)
                 && !isUnloggableNumber(number, configBundle);
-
-        sendAddCallBroadcast(callType, duration);
-
-        if (isOkToLogThisCall) {
-            Log.d(TAG, "Logging Call log entry: " + callerInfo + ", "
-                    + Log.pii(number) + "," + presentation + ", " + callType
-                    + ", " + start + ", " + duration);
-            boolean isRead = false;
-            if (isSelfManaged) {
-                // Mark self-managed calls are read since they're being handled by their own app.
-                // Their inclusion in the call log is informational only.
-                isRead = true;
-            }
-            AddCallArgs args = new AddCallArgs(mContext, callerInfo, number, postDialDigits,
-                    viaNumber, presentation, callType, features, accountHandle, start, duration,
-                    dataUsage, initiatingUser, isRead, logCallCompletedListener, callBlockReason,
-                    callScreeningAppName, callScreeningComponentName);
-            logCallAsync(args);
-        } else {
-          Log.d(TAG, "Not adding emergency call to call log.");
-        }
     }
 
     private boolean isUnloggableNumber(String callNumber, PersistableBundle carrierConfig) {
@@ -553,7 +512,7 @@
                 mListeners[i] = c.logCallCompletedListener;
                 try {
                     // May block.
-                    result[i] = addCall(c);
+                    result[i] = Calls.addCall(c.context, c.params);
                 } catch (Exception e) {
                     // This is very rare but may happen in legitimate cases.
                     // E.g. If the phone is encrypted and thus write request fails, it may cause
@@ -569,43 +528,13 @@
             return result;
         }
 
-        private Uri addCall(AddCallArgs c) {
-            PhoneAccount phoneAccount = mPhoneAccountRegistrar
-                    .getPhoneAccountUnchecked(c.accountHandle);
-            if (phoneAccount != null &&
-                    phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_MULTI_USER)) {
-                if (c.initiatingUser != null &&
-                        UserUtil.isManagedProfile(mContext, c.initiatingUser)) {
-                    return addCall(c, c.initiatingUser);
-                } else {
-                    return addCall(c, null);
-                }
-            } else {
-                return addCall(c, c.accountHandle == null ? null : c.accountHandle.getUserHandle());
-            }
-        }
-
-        /**
-         * Insert the call to a specific user or all users except managed profile.
-         * @param c context
-         * @param userToBeInserted user handle of user that the call going be inserted to. null
-         *                         if insert to all users except managed profile.
-         */
-        private Uri addCall(AddCallArgs c, UserHandle userToBeInserted) {
-            return Calls.addCall(c.callerInfo, c.context, c.number, c.postDialDigits, c.viaNumber,
-                    c.presentation, c.callType, c.features, c.accountHandle, c.timestamp,
-                    c.durationInSec, c.dataUsage, userToBeInserted == null,
-                    userToBeInserted, c.isRead, c.callBockReason, c.callScreeningAppName,
-                    c.callScreeningComponentName);
-        }
-
-
         @Override
         protected void onPostExecute(Uri[] result) {
             for (int i = 0; i < result.length; i++) {
                 Uri uri = result[i];
                 /*
-                 Performs a simple sanity check to make sure the call was written in the database.
+                 Performs a simple correctness check to make sure the call was written in the
+                 database.
                  Typically there is only one result per call so it is easy to identify which one
                  failed.
                  */
diff --git a/src/com/android/server/telecom/CallScreeningServiceHelper.java b/src/com/android/server/telecom/CallScreeningServiceHelper.java
index f02b924..9435250 100644
--- a/src/com/android/server/telecom/CallScreeningServiceHelper.java
+++ b/src/com/android/server/telecom/CallScreeningServiceHelper.java
@@ -55,23 +55,8 @@
         }
 
         @Override
-        public void allowCall(String s) throws RemoteException {
-            unbindCallScreeningService();
-        }
-
-        @Override
-        public void silenceCall(String s) throws RemoteException {
-            unbindCallScreeningService();
-        }
-
-        @Override
-        public void screenCallFurther(String callId) throws RemoteException {
-            unbindCallScreeningService();
-        }
-
-        @Override
-        public void disallowCall(String s, boolean b, boolean b1, boolean b2,
-                ComponentName componentName) throws RemoteException {
+        public void onScreeningResponse(String callId, ComponentName componentName,
+                CallScreeningService.ParcelableCallResponse callResponse) {
             unbindCallScreeningService();
         }
 
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index d785e47..ea45abb 100755
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -16,6 +16,7 @@
 
 package com.android.server.telecom;
 
+import static android.provider.CallLog.Calls.MISSED_REASON_NOT_MISSED;
 import static android.telecom.TelecomManager.ACTION_POST_CALL;
 import static android.telecom.TelecomManager.DURATION_LONG;
 import static android.telecom.TelecomManager.DURATION_MEDIUM;
@@ -27,6 +28,11 @@
 import static android.telecom.TelecomManager.MEDIUM_CALL_TIME_MS;
 import static android.telecom.TelecomManager.SHORT_CALL_TIME_MS;
 import static android.telecom.TelecomManager.VERY_SHORT_CALL_TIME_MS;
+import static android.provider.CallLog.Calls.AUTO_MISSED_EMERGENCY_CALL;
+import static android.provider.CallLog.Calls.AUTO_MISSED_MAXIMUM_DIALING;
+import static android.provider.CallLog.Calls.AUTO_MISSED_MAXIMUM_RINGING;
+import static android.provider.CallLog.Calls.USER_MISSED_CALL_FILTERS_TIMEOUT;
+import static android.provider.CallLog.Calls.USER_MISSED_CALL_SCREENING_SERVICE_SILENCED;
 
 import android.Manifest;
 import android.annotation.NonNull;
@@ -66,6 +72,7 @@
 import android.provider.Settings;
 import android.sysprop.TelephonyProperties;
 import android.telecom.CallAudioState;
+import android.telecom.CallScreeningService;
 import android.telecom.CallerInfo;
 import android.telecom.Conference;
 import android.telecom.Connection;
@@ -102,7 +109,6 @@
 import com.android.server.telecom.callfiltering.CallFilteringResult.Builder;
 import com.android.server.telecom.callfiltering.CallScreeningServiceFilter;
 import com.android.server.telecom.callfiltering.DirectToVoicemailFilter;
-import com.android.server.telecom.callfiltering.IncomingCallFilter;
 import com.android.server.telecom.callfiltering.IncomingCallFilterGraph;
 import com.android.server.telecom.callredirection.CallRedirectionProcessor;
 import com.android.server.telecom.components.ErrorDialogActivity;
@@ -327,6 +333,7 @@
     private final ConnectionServiceRepository mConnectionServiceRepository;
     private final DtmfLocalTonePlayer mDtmfLocalTonePlayer;
     private final InCallController mInCallController;
+    private final CallDiagnosticServiceController mCallDiagnosticServiceController;
     private final CallAudioManager mCallAudioManager;
     private final CallRecordingTonePlayer mCallRecordingTonePlayer;
     private RespondViaSmsManager mRespondViaSmsManager;
@@ -352,7 +359,6 @@
     private final DisconnectedCallNotifier mDisconnectedCallNotifier;
     private IncomingCallNotifier mIncomingCallNotifier;
     private final CallerInfoLookupHelper mCallerInfoLookupHelper;
-    private final IncomingCallFilter.Factory mIncomingCallFilterFactory;
     private final DefaultDialerCache mDefaultDialerCache;
     private final Timeouts.Adapter mTimeoutsAdapter;
     private final PhoneNumberUtilsAdapter mPhoneNumberUtilsAdapter;
@@ -483,8 +489,8 @@
             CallAudioRouteStateMachine.Factory callAudioRouteStateMachineFactory,
             CallAudioModeStateMachine.Factory callAudioModeStateMachineFactory,
             InCallControllerFactory inCallControllerFactory,
+            CallDiagnosticServiceController callDiagnosticServiceController,
             RoleManagerAdapter roleManagerAdapter,
-            IncomingCallFilter.Factory incomingCallFilterFactory,
             ToastFactory toastFactory) {
         mContext = context;
         mLock = lock;
@@ -502,7 +508,6 @@
         mTimeoutsAdapter = timeoutsAdapter;
         mEmergencyCallHelper = emergencyCallHelper;
         mCallerInfoLookupHelper = callerInfoLookupHelper;
-        mIncomingCallFilterFactory = incomingCallFilterFactory;
 
         mDtmfLocalTonePlayer =
                 new DtmfLocalTonePlayer(new DtmfLocalTonePlayer.ToneGeneratorProxy());
@@ -541,6 +546,8 @@
         mInCallController = inCallControllerFactory.create(context, mLock, this,
                 systemStateHelper, defaultDialerCache, mTimeoutsAdapter,
                 emergencyCallHelper);
+        mCallDiagnosticServiceController = callDiagnosticServiceController;
+        mCallDiagnosticServiceController.setInCallTonePlayerFactory(playerFactory);
         mRinger = new Ringer(playerFactory, context, systemSettingsUtil, asyncRingtonePlayer,
                 ringtoneFactory, systemVibrator,
                 new Ringer.VibrationEffectProxy(), mInCallController);
@@ -570,6 +577,7 @@
         mListeners.add(mCallLogManager);
         mListeners.add(mPhoneStateBroadcaster);
         mListeners.add(mInCallController);
+        mListeners.add(mCallDiagnosticServiceController);
         mListeners.add(mCallAudioManager);
         mListeners.add(mCallRecordingTonePlayer);
         mListeners.add(missedCallNotifier);
@@ -620,6 +628,10 @@
         return mRoleManagerAdapter;
     }
 
+    public CallDiagnosticServiceController getCallDiagnosticServiceController() {
+        return mCallDiagnosticServiceController;
+    }
+
     @Override
     public void onSuccessfulOutgoingCall(Call call, int callState) {
         Log.v(this, "onSuccessfulOutgoingCall, %s", call);
@@ -669,7 +681,7 @@
                     .setShouldReject(false)
                     .setShouldAddToCallLog(true)
                     .setShouldShowNotification(true)
-                    .build());
+                    .build(), false);
             incomingCall.setIsUsingCallFiltering(false);
             return;
         }
@@ -735,12 +747,19 @@
     }
 
     @Override
-    public void onCallFilteringComplete(Call incomingCall, CallFilteringResult result) {
+    public void onCallFilteringComplete(Call incomingCall, CallFilteringResult result,
+            boolean timeout) {
         // Only set the incoming call as ringing if it isn't already disconnected. It is possible
         // that the connection service disconnected the call before it was even added to Telecom, in
         // which case it makes no sense to set it back to a ringing state.
+        Log.i(this, "onCallFilteringComplete");
         mGraphHandlerThreads.clear();
 
+        if (timeout) {
+            Log.i(this, "onCallFilteringCompleted: Call filters timeout!");
+            incomingCall.setUserMissed(USER_MISSED_CALL_FILTERS_TIMEOUT);
+        }
+
         if (incomingCall.getState() != CallState.DISCONNECTED &&
                 incomingCall.getState() != CallState.DISCONNECTING) {
             setCallState(incomingCall, CallState.RINGING,
@@ -750,26 +769,66 @@
             return;
         }
 
+        // Inform our connection service that call filtering is done (if it was performed at all).
+        if (incomingCall.isUsingCallFiltering()) {
+            boolean isInContacts = incomingCall.getCallerInfo() != null
+                    && incomingCall.getCallerInfo().contactExists;
+            Connection.CallFilteringCompletionInfo completionInfo =
+                    new Connection.CallFilteringCompletionInfo(!result.shouldAllowCall,
+                            isInContacts,
+                            result.mCallScreeningResponse == null
+                                    ? null : result.mCallScreeningResponse.toCallResponse(),
+                            result.mCallScreeningComponentName == null ? null
+                                    : ComponentName.unflattenFromString(
+                                            result.mCallScreeningComponentName));
+            incomingCall.getConnectionService().onCallFilteringCompleted(incomingCall,
+                    completionInfo);
+        }
+
+        // Get rid of the call composer attachments that aren't wanted
+        if (result.mIsResponseFromSystemDialer && result.mCallScreeningResponse != null
+                && result.mCallScreeningResponse.getCallComposerAttachmentsToShow() >= 0) {
+            int attachmentMask = result.mCallScreeningResponse.getCallComposerAttachmentsToShow();
+            if ((attachmentMask
+                    & CallScreeningService.CallResponse.CALL_COMPOSER_ATTACHMENT_LOCATION) == 0) {
+                incomingCall.getIntentExtras().remove(TelecomManager.EXTRA_LOCATION);
+            }
+
+            if ((attachmentMask
+                    & CallScreeningService.CallResponse.CALL_COMPOSER_ATTACHMENT_SUBJECT) == 0) {
+                incomingCall.getIntentExtras().remove(TelecomManager.EXTRA_CALL_SUBJECT);
+            }
+
+            if ((attachmentMask
+                    & CallScreeningService.CallResponse.CALL_COMPOSER_ATTACHMENT_PRIORITY) == 0) {
+                incomingCall.getIntentExtras().remove(TelecomManager.EXTRA_PRIORITY);
+            }
+        }
+
         if (result.shouldAllowCall) {
             incomingCall.setPostCallPackageName(
                     getRoleManagerAdapter().getDefaultCallScreeningApp());
 
+            Log.i(this, "onCallFilteringComplete: allow call.");
             if (hasMaximumManagedRingingCalls(incomingCall)) {
                 if (shouldSilenceInsteadOfReject(incomingCall)) {
                     incomingCall.silence();
                 } else {
                     Log.i(this, "onCallFilteringCompleted: Call rejected! " +
                             "Exceeds maximum number of ringing calls.");
-                    rejectCallAndLog(incomingCall, result);
+                    incomingCall.setMissedReason(AUTO_MISSED_MAXIMUM_RINGING);
+                    autoMissCallAndLog(incomingCall, result);
+                    return;
                 }
             } else if (hasMaximumManagedDialingCalls(incomingCall)) {
                 if (shouldSilenceInsteadOfReject(incomingCall)) {
                     incomingCall.silence();
                 } else {
-
                     Log.i(this, "onCallFilteringCompleted: Call rejected! Exceeds maximum number of " +
                             "dialing calls.");
-                    rejectCallAndLog(incomingCall, result);
+                    incomingCall.setMissedReason(AUTO_MISSED_MAXIMUM_DIALING);
+                    autoMissCallAndLog(incomingCall, result);
+                    return;
                 }
             } else if (result.shouldScreenViaAudio) {
                 Log.i(this, "onCallFilteringCompleted: starting background audio processing");
@@ -778,6 +837,9 @@
             } else if (result.shouldSilence) {
                 Log.i(this, "onCallFilteringCompleted: setting the call to silent ringing state");
                 incomingCall.setSilentRingingRequested(true);
+                incomingCall.setUserMissed(USER_MISSED_CALL_SCREENING_SERVICE_SILENCED);
+                incomingCall.setCallScreeningAppName(result.mCallScreeningAppName);
+                incomingCall.setCallScreeningComponentName(result.mCallScreeningComponentName);
                 addCall(incomingCall);
             } else {
                 addCall(incomingCall);
@@ -1221,10 +1283,14 @@
         PhoneAccount phoneAccount = mPhoneAccountRegistrar.getPhoneAccountUnchecked(
                 phoneAccountHandle);
         if (phoneAccount != null) {
+            Bundle phoneAccountExtras = phoneAccount.getExtras();
             call.setIsSelfManaged(phoneAccount.isSelfManaged());
             if (call.isSelfManaged()) {
                 // Self managed calls will always be voip audio mode.
                 call.setIsVoipAudioMode(true);
+                call.setVisibleToInCallService(phoneAccountExtras == null
+                        || phoneAccountExtras.getBoolean(
+                        PhoneAccount.EXTRA_ADD_SELF_MANAGED_CALLS_TO_INCALLSERVICE, true));
             } else {
                 // Incoming call is managed, the active call is self-managed and can't be held.
                 // We need to set extras on it to indicate whether answering will cause a 
@@ -1243,7 +1309,6 @@
                 }
             }
 
-            Bundle phoneAccountExtras = phoneAccount.getExtras();
             if (phoneAccountExtras != null
                     && phoneAccountExtras.getBoolean(
                             PhoneAccount.EXTRA_ALWAYS_USE_VOIP_AUDIO_MODE)) {
@@ -1335,12 +1400,19 @@
             if (isConference) {
                 notifyCreateConferenceFailed(phoneAccountHandle, call);
             } else {
+                if (hasMaximumManagedRingingCalls(call)) {
+                    call.setMissedReason(AUTO_MISSED_MAXIMUM_RINGING);
+                    mCallLogManager.logCall(call, Calls.MISSED_TYPE,
+                            true /*showNotificationForMissedCall*/, null /*CallFilteringResult*/);
+                }
                 notifyCreateConnectionFailed(phoneAccountHandle, call);
             }
         } else if (isInEmergencyCall()) {
             // The incoming call is implicitly being rejected so the user does not get any incoming
             // call UI during an emergency call. In this case, log the call as missed instead of
             // rejected since the user did not explicitly reject.
+            call.setMissedReason(AUTO_MISSED_EMERGENCY_CALL);
+            call.getAnalytics().setMissedReason(call.getMissedReason());
             mCallLogManager.logCall(call, Calls.MISSED_TYPE,
                     true /*showNotificationForMissedCall*/, null /*CallFilteringResult*/);
             if (isConference) {
@@ -1451,6 +1523,7 @@
 
         PhoneAccount account =
                 mPhoneAccountRegistrar.getPhoneAccount(requestedAccountHandle, initiatingUser);
+        Bundle phoneAccountExtra = account != null ? account.getExtras() : null;
         boolean isSelfManaged = account != null && account.isSelfManaged();
 
         // Create a call with original handle. The handle may be changed when the call is attached
@@ -1481,6 +1554,9 @@
             if (isSelfManaged) {
                 // Self-managed calls will ALWAYS use voip audio mode.
                 call.setIsVoipAudioMode(true);
+                call.setVisibleToInCallService(phoneAccountExtra == null
+                        || phoneAccountExtra.getBoolean(
+                                PhoneAccount.EXTRA_ADD_SELF_MANAGED_CALLS_TO_INCALLSERVICE, true));
             }
             call.setInitiatingUser(initiatingUser);
             isReusedCall = false;
@@ -1586,6 +1662,32 @@
                     // call transitioning into the CONNECTING state.
                     if (isReusedCall) {
                         return CompletableFuture.completedFuture(finalCall);
+                    } else {
+                        Call reusableCall = reuseOutgoingCall(handle);
+                        if (reusableCall != null) {
+                            Log.i(CallsManager.this,
+                                    "reusable call %s came in later; disconnect it.",
+                                    reusableCall.getId());
+                            mPendingCallsToDisconnect.remove(reusableCall);
+                            reusableCall.disconnect();
+                            markCallAsDisconnected(reusableCall,
+                                    new DisconnectCause(DisconnectCause.CANCELED));
+                        }
+                    }
+
+                    if (!finalCall.isEmergencyCall() && isInEmergencyCall()) {
+                        Log.i(CallsManager.this, "Aborting call since there's an"
+                                + " ongoing emergency call");
+                        // If the ongoing call is a managed call, we will prevent the outgoing
+                        // call from dialing.
+                        if (isConference) {
+                            notifyCreateConferenceFailed(finalCall.getTargetPhoneAccount(),
+                                    finalCall);
+                        } else {
+                            notifyCreateConnectionFailed(
+                                    finalCall.getTargetPhoneAccount(), finalCall);
+                        }
+                        return CompletableFuture.completedFuture(null);
                     }
 
                     // If we can not supportany more active calls, our options are to move a call
@@ -1977,6 +2079,8 @@
                             handle.getSchemeSpecificPart());
         } catch (IllegalStateException ise) {
             isPotentialEmergencyNumber = false;
+        } catch (RuntimeException r) {
+            isPotentialEmergencyNumber = false;
         }
 
         if (shouldCancelCall) {
@@ -2154,8 +2258,16 @@
     public void processRedirectedOutgoingCallAfterUserInteraction(String callId, String action) {
         Log.i(this, "processRedirectedOutgoingCallAfterUserInteraction for Call ID %s, action=%s",
                 callId, action);
-        if (mPendingRedirectedOutgoingCall != null && mPendingRedirectedOutgoingCall.getId()
-                .equals(callId)) {
+        if (mPendingRedirectedOutgoingCall != null) {
+            String pendingCallId = mPendingRedirectedOutgoingCall.getId();
+            if (!pendingCallId.equals(callId)) {
+                Log.i(this, "processRedirectedOutgoingCallAfterUserInteraction for new Call ID %s, "
+                        + "cancel the previous pending Call with ID %s", callId, pendingCallId);
+                mPendingRedirectedOutgoingCall.disconnect("Another call redirection requested");
+                mPendingRedirectedOutgoingCallInfo.remove(pendingCallId);
+                mPendingUnredirectedOutgoingCallInfo.remove(pendingCallId);
+            }
+
             if (action.equals(TelecomBroadcastIntentProcessor.ACTION_PLACE_REDIRECTED_CALL)) {
                 mHandler.post(mPendingRedirectedOutgoingCallInfo.get(callId).prepare());
             } else if (action.equals(
@@ -2695,6 +2807,16 @@
         updateCanAddCall();
     }
 
+    @Override
+    public void onRemoteRttRequest(Call call, int requestId) {
+        Log.i(this, "onRemoteRttRequest: call %s", call.getId());
+        playRttUpgradeToneForCall(call);
+    }
+
+    public void playRttUpgradeToneForCall(Call call) {
+        mCallAudioManager.playRttUpgradeTone(call);
+    }
+
     // Construct the list of possible PhoneAccounts that the outgoing call can use based on the
     // active calls in CallsManager. If any of the active calls are on a SIM based PhoneAccount,
     // then include only that SIM based PhoneAccount and any non-SIM PhoneAccounts, such as SIP.
@@ -2818,8 +2940,8 @@
     }
 
     private boolean isRttSettingOn(PhoneAccountHandle handle) {
-        boolean isRttModeSettingOn = Settings.Secure.getInt(mContext.getContentResolver(),
-                Settings.Secure.RTT_CALLING_MODE, 0) != 0;
+        boolean isRttModeSettingOn = Settings.Secure.getIntForUser(mContext.getContentResolver(),
+                Settings.Secure.RTT_CALLING_MODE, 0, mContext.getUserId()) != 0;
         // If the carrier config says that we should ignore the RTT mode setting from the user,
         // assume that it's off (i.e. only make an RTT call if it's requested through the extra).
         boolean shouldIgnoreRttModeSetting = getCarrierConfigForPhoneAccount(handle)
@@ -2897,6 +3019,7 @@
      */
     boolean holdActiveCallForNewCall(Call call) {
         Call activeCall = (Call) mConnectionSvrFocusMgr.getCurrentFocusCall();
+        Log.i(this, "holdActiveCallForNewCall, newCall: %s, activeCall: %s", call, activeCall);
         if (activeCall != null && activeCall != call) {
             if (canHold(activeCall)) {
                 activeCall.hold();
@@ -2952,6 +3075,7 @@
 
     @VisibleForTesting
     public void markCallAsActive(Call call) {
+        Log.i(this, "markCallAsActive, isSelfManaged: " + call.isSelfManaged());
         if (call.isSelfManaged()) {
             // backward compatibility, the self-managed connection service will set the call state
             // to active directly. We should hold or disconnect the current active call based on the
@@ -2994,35 +3118,92 @@
      *
      * @param disconnectCause The disconnect cause, see {@link android.telecom.DisconnectCause}.
      */
-    void markCallAsDisconnected(Call call, DisconnectCause disconnectCause) {
-      int oldState = call.getState();
-      if (call.getState() == CallState.SIMULATED_RINGING
+    @VisibleForTesting
+    public void markCallAsDisconnected(Call call, DisconnectCause disconnectCause) {
+        int oldState = call.getState();
+        if (call.getState() == CallState.SIMULATED_RINGING
                 && disconnectCause.getCode() == DisconnectCause.REMOTE) {
             // If the remote end hangs up while in SIMULATED_RINGING, the call should
             // be marked as missed.
             call.setOverrideDisconnectCauseCode(new DisconnectCause(DisconnectCause.MISSED));
         }
-        call.setDisconnectCause(disconnectCause);
-        setCallState(call, CallState.DISCONNECTED, "disconnected set explicitly");
 
-        if(oldState == CallState.NEW && disconnectCause.getCode() == DisconnectCause.MISSED) {
+        // If a call diagnostic service is in use, we will log the original telephony-provided
+        // disconnect cause, inform the CDS of the disconnection, and then chain the update of the
+        // call state until AFTER the CDS reports it's result back.
+        if ((oldState == CallState.ACTIVE || oldState == CallState.DIALING)
+                && disconnectCause.getCode() != DisconnectCause.MISSED
+                && mCallDiagnosticServiceController.isConnected()
+                && mCallDiagnosticServiceController.onCallDisconnected(call, disconnectCause)) {
+            Log.i(this, "markCallAsDisconnected; callid=%s, postingToFuture.", call.getId());
+
+            // Log the original disconnect reason prior to calling into the
+            // CallDiagnosticService.
+            Log.addEvent(call, LogUtils.Events.SET_DISCONNECTED_ORIG, disconnectCause);
+
+            // Setup the future with a timeout so that the CDS is time boxed.
+            CompletableFuture<Boolean> future = call.initializeDisconnectFuture(
+                    mTimeoutsAdapter.getCallDiagnosticServiceTimeoutMillis(
+                            mContext.getContentResolver()));
+
+            // Post the disconnection updates to the future for completion once the CDS returns
+            // with it's overridden disconnect message.
+            future.thenRunAsync(() -> {
+                call.setDisconnectCause(disconnectCause);
+                setCallState(call, CallState.DISCONNECTED, "disconnected set explicitly");
+            }, new LoggedHandlerExecutor(mHandler, "CM.mCAD", mLock))
+                    .exceptionally((throwable) -> {
+                        Log.e(TAG, throwable, "Error while executing disconnect future.");
+                        return null;
+                    });
+        } else {
+            // No CallDiagnosticService, or it doesn't handle this call, so just do this
+            // synchronously as always.
+            call.setDisconnectCause(disconnectCause);
+            setCallState(call, CallState.DISCONNECTED, "disconnected set explicitly");
+        }
+
+        if (oldState == CallState.NEW && disconnectCause.getCode() == DisconnectCause.MISSED) {
             Log.i(this, "markCallAsDisconnected: logging missed call ");
             mCallLogManager.logCall(call, Calls.MISSED_TYPE, true, null);
         }
-
     }
 
     /**
      * Removes an existing disconnected call, and notifies the in-call app.
      */
     void markCallAsRemoved(Call call) {
+        if (call.isDisconnectHandledViaFuture()) {
+            Log.i(this, "markCallAsRemoved; callid=%s, postingToFuture.", call.getId());
+            // A future is being used due to a CallDiagnosticService handling the call.  We will
+            // chain the removal operation to the end of any outstanding disconnect work.
+            call.getDisconnectFuture().thenRunAsync(() -> {
+                performRemoval(call);
+            }, new LoggedHandlerExecutor(mHandler, "CM.mCAR", mLock))
+                    .exceptionally((throwable) -> {
+                        Log.e(TAG, throwable, "Error while executing disconnect future");
+                        return null;
+                    });
+
+        } else {
+            Log.i(this, "markCallAsRemoved; callid=%s, immediate.", call.getId());
+            performRemoval(call);
+        }
+    }
+
+    /**
+     * Work which is completed when a call is to be removed. Can either be be run synchronously or
+     * posted to a {@link Call#getDisconnectFuture()}.
+     * @param call The call.
+     */
+    private void performRemoval(Call call) {
         mInCallController.getBindingFuture().thenRunAsync(() -> {
             call.maybeCleanupHandover();
             removeCall(call);
             Call foregroundCall = mCallAudioManager.getPossiblyHeldForegroundCall();
             if (mLocallyDisconnectingCalls.contains(call)) {
                 boolean isDisconnectingChildCall = call.isDisconnectingChildCall();
-                Log.v(this, "markCallAsRemoved: isDisconnectingChildCall = "
+                Log.v(this, "performRemoval: isDisconnectingChildCall = "
                         + isDisconnectingChildCall + "call -> %s", call);
                 mLocallyDisconnectingCalls.remove(call);
                 // Auto-unhold the foreground call due to a locally disconnected call, except if the
@@ -3039,10 +3220,15 @@
                 // The new foreground call is on hold, however the carrier does not display the hold
                 // button in the UI.  Therefore, we need to auto unhold the held call since the user
                 // has no means of unholding it themselves.
-                Log.i(this, "Auto-unholding held foreground call (call doesn't support hold)");
+                Log.i(this, "performRemoval: Auto-unholding held foreground call (call doesn't "
+                        + "support hold)");
                 foregroundCall.unhold();
             }
-        }, new LoggedHandlerExecutor(mHandler, "CM.mCAR", mLock));
+        }, new LoggedHandlerExecutor(mHandler, "CM.pR", mLock))
+                .exceptionally((throwable) -> {
+                    Log.e(TAG, throwable, "Error while executing call removal");
+                    return null;
+                });
     }
 
     /**
@@ -3437,7 +3623,8 @@
      * 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, CallFilteringResult result) {
+    private void autoMissCallAndLog(Call incomingCall, CallFilteringResult result) {
+        incomingCall.getAnalytics().setMissedReason(incomingCall.getMissedReason());
         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
@@ -3463,7 +3650,7 @@
     @VisibleForTesting
     public void addCall(Call call) {
         Trace.beginSection("addCall");
-        Log.v(this, "addCall(%s)", call);
+        Log.i(this, "addCall(%s)", call);
         call.addListener(this);
         mCalls.add(call);
 
@@ -3575,27 +3762,21 @@
                         (newState == CallState.DISCONNECTED)) {
                     maybeSendPostCallScreenIntent(call);
                 }
+                int disconnectCode = call.getDisconnectCause().getCode();
+                if ((newState == CallState.ABORTED || newState == CallState.DISCONNECTED)
+                        && ((disconnectCode != DisconnectCause.MISSED)
+                        && (disconnectCode != DisconnectCause.CANCELED))) {
+                    call.setMissedReason(MISSED_REASON_NOT_MISSED);
+                }
+                call.getAnalytics().setMissedReason(call.getMissedReason());
+
                 maybeShowErrorDialogOnDisconnect(call);
 
                 Trace.beginSection("onCallStateChanged");
 
                 maybeHandleHandover(call, newState);
+                notifyCallStateChanged(call, oldState, newState);
 
-                // Only broadcast state change for calls that are being tracked.
-                if (mCalls.contains(call)) {
-                    updateCanAddCall();
-                    updateHasActiveRttCall();
-                    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");
@@ -3603,6 +3784,24 @@
         }
     }
 
+    private void notifyCallStateChanged(Call call, int oldState, int newState) {
+        // Only broadcast state change for calls that are being tracked.
+        if (mCalls.contains(call)) {
+            updateCanAddCall();
+            updateHasActiveRttCall();
+            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();
+                }
+            }
+        }
+    }
+
     /**
      * Identifies call state transitions for a call which trigger handover events.
      * - If this call has a handover to it which just started and this call goes active, treat
@@ -3988,7 +4187,7 @@
                 + " livecall = " + liveCall);
 
         if (emergencyCall == liveCall) {
-            // Not likely, but a good sanity check.
+            // Not likely, but a good correctness check.
             return true;
         }
 
@@ -4002,7 +4201,7 @@
                 return true;
             }
             if (outgoingCall.getState() == CallState.SELECT_PHONE_ACCOUNT) {
-                // Sanity check: if there is an orphaned emergency call in the
+                // Correctness check: if there is an orphaned emergency call in the
                 // {@link CallState#SELECT_PHONE_ACCOUNT} state, just disconnect it since the user
                 // has explicitly started a new call.
                 emergencyCall.getAnalytics().setCallIsAdditional(true);
@@ -4103,7 +4302,8 @@
         return false;
     }
 
-    private boolean makeRoomForOutgoingCall(Call call) {
+    @VisibleForTesting
+    public boolean makeRoomForOutgoingCall(Call call) {
         // Already room!
         if (!hasMaximumLiveCalls(call)) return true;
 
@@ -4120,6 +4320,13 @@
             return true;
         }
 
+        // If the live call is stuck in a connecting state, then we should disconnect it in favor
+        // of the new outgoing call.
+        if (liveCall.getState() == CallState.CONNECTING) {
+            liveCall.disconnect("Force disconnect CONNECTING call.");
+            return true;
+        }
+
         if (hasMaximumOutgoingCalls(call)) {
             Call outgoingCall = getFirstCallWithState(OUTGOING_CALL_STATES);
             if (outgoingCall.getState() == CallState.SELECT_PHONE_ACCOUNT) {
@@ -4640,6 +4847,13 @@
             pw.decreaseIndent();
         }
 
+        if (mCallDiagnosticServiceController != null) {
+            pw.println("mCallDiagnosticServiceController:");
+            pw.increaseIndent();
+            mCallDiagnosticServiceController.dump(pw);
+            pw.decreaseIndent();
+        }
+
         if (mDefaultDialerCache != null) {
             pw.println("mDefaultDialerCache:");
             pw.increaseIndent();
@@ -4696,6 +4910,9 @@
         extras.putLong(TelecomManager.EXTRA_CALL_TELECOM_ROUTING_START_TIME_MILLIS,
               SystemClock.elapsedRealtime());
 
+        if (call.visibleToInCallService()) {
+            extras.putBoolean(PhoneAccount.EXTRA_ADD_SELF_MANAGED_CALLS_TO_INCALLSERVICE, true);
+        }
         call.setIntentExtras(extras);
     }
 
@@ -5340,4 +5557,15 @@
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         mContext.startActivityAsUser(intent, mCurrentUserHandle);
     }
+
+    @VisibleForTesting
+    public void addToPendingCallsToDisconnect(Call call) {
+        mPendingCallsToDisconnect.add(call);
+    }
+
+    @VisibleForTesting
+    public void addConnectionServiceRepositoryCache(ComponentName componentName,
+            UserHandle userHandle, ConnectionServiceWrapper service) {
+        mConnectionServiceRepository.setService(componentName, userHandle, service);
+    }
 }
diff --git a/src/com/android/server/telecom/CarModeTracker.java b/src/com/android/server/telecom/CarModeTracker.java
index 0ec4917..737ce5a 100644
--- a/src/com/android/server/telecom/CarModeTracker.java
+++ b/src/com/android/server/telecom/CarModeTracker.java
@@ -28,7 +28,9 @@
 import java.util.Comparator;
 import java.util.List;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.PriorityQueue;
+import java.util.function.Function;
 import java.util.stream.Collectors;
 
 /**
@@ -39,30 +41,40 @@
      * Data class holding information about apps which have requested to enter car mode.
      */
     private class CarModeApp {
-        private @IntRange(from = 0) int mPriority;
+        private final boolean mAutomotiveProjection;
+        private final @IntRange(from = 0) int mPriority;
         private @NonNull String mPackageName;
 
+        public CarModeApp(@NonNull String packageName) {
+            this(true, 0, packageName);
+        }
+
         public CarModeApp(int priority, @NonNull String packageName) {
+            this(false, priority, packageName);
+        }
+
+        private CarModeApp(boolean automotiveProjection, int priority, @NonNull String packageName) {
+            mAutomotiveProjection = automotiveProjection;
             mPriority = priority;
             mPackageName = Objects.requireNonNull(packageName);
         }
 
+        public boolean hasSetAutomotiveProjection() {
+            return mAutomotiveProjection;
+        }
+
         /**
          * The priority at which the app requested to enter car mode.
          * Will be the same as the one specified when {@link UiModeManager#enableCarMode(int, int)}
-         * was called, or {@link UiModeManager#DEFAULT_PRIORITY} if no priority was specifeid.
+         * was called, or {@link UiModeManager#DEFAULT_PRIORITY} if no priority was specified.
          * @return The priority.
          */
         public int getPriority() {
             return mPriority;
         }
 
-        public void setPriority(int priority) {
-            mPriority = priority;
-        }
-
         /**
-         * @return The package name of the app which requested to enter car mode.
+         * @return The package name of the app which requested to enter car mode/set projection.
          */
         public String getPackageName() {
             return mPackageName;
@@ -71,26 +83,24 @@
         public void setPackageName(String packageName) {
             mPackageName = packageName;
         }
-    }
 
-    /**
-     * Comparator used to maintain the car mode priority queue ordering.
-     */
-    private class CarModeAppComparator implements Comparator<CarModeApp> {
-        @Override
-        public int compare(CarModeApp o1, CarModeApp o2) {
-            // highest priority takes precedence.
-            return Integer.compare(o2.getPriority(), o1.getPriority());
+        public String toString() {
+            return String.format("[%s, %s]",
+                    mAutomotiveProjection ? "PROJECTION SET" : mPriority,
+                    mPackageName);
         }
     }
 
     /**
-     * Priority list of apps which have entered or exited car mode, ordered with the highest
-     * priority app at the top of the queue.  Where items have the same priority, they are ordered
-     * by insertion time.
+     * Priority list of apps which have entered or exited car mode, ordered first by whether the app
+     * has set automotive projection, and then by highest priority.  Where items have the same
+     * priority, order is arbitrary, but we only allow one item in the queue per priority.
      */
     private PriorityQueue<CarModeApp> mCarModeApps = new PriorityQueue<>(2,
-            new CarModeAppComparator());
+            // Natural ordering of booleans is False, True. Natural ordering of ints is increasing.
+            Comparator.comparing(CarModeApp::hasSetAutomotiveProjection)
+                    .thenComparing(CarModeApp::getPriority)
+                    .reversed());
 
     private final LocalLog mCarModeChangeLog = new LocalLog(20);
 
@@ -143,6 +153,71 @@
         mCarModeApps.removeIf(c -> c.getPriority() == priority);
     }
 
+    public void handleSetAutomotiveProjection(@NonNull String packageName) {
+        Optional<CarModeApp> projectingApp = mCarModeApps.stream()
+                .filter(CarModeApp::hasSetAutomotiveProjection)
+                .findAny();
+        // No app with automotive projection? Easy peasy, just add it.
+        if (!projectingApp.isPresent()) {
+            Log.i(this, "handleSetAutomotiveProjection: %s", packageName);
+            mCarModeChangeLog.log("setAutomotiveProjection: packageName=" + packageName);
+            mCarModeApps.add(new CarModeApp(packageName));
+            return;
+        }
+        // Otherwise an app already has automotive projection set. Is it the same app?
+        if (packageName.equals(projectingApp.get().getPackageName())) {
+            Log.w(this, "handleSetAutomotiveProjection: %s already the automotive projection app",
+                    packageName);
+            return;
+        }
+        // We have a new app for automotive projection. As a shortcut just reuse the same object by
+        // overwriting the package name.
+        Log.i(this, "handleSetAutomotiveProjection: %s replacing %s as automotive projection app",
+                packageName, projectingApp.get().getPackageName());
+        mCarModeChangeLog.log("setAutomotiveProjection: " + packageName + " replaces "
+                + projectingApp.get().getPackageName());
+        projectingApp.get().setPackageName(packageName);
+    }
+
+    public void handleReleaseAutomotiveProjection() {
+        Optional<String> projectingPackage = mCarModeApps.stream()
+                .filter(CarModeApp::hasSetAutomotiveProjection)
+                .map(CarModeApp::getPackageName)
+                .findAny();
+        if (!projectingPackage.isPresent()) {
+            Log.w(this, "handleReleaseAutomotiveProjection: no current automotive projection app");
+            return;
+        }
+        Log.i(this, "handleReleaseAutomotiveProjection: %s", projectingPackage.get());
+        mCarModeChangeLog.log("releaseAutomotiveProjection: packageName="
+                + projectingPackage.get());
+        mCarModeApps.removeIf(CarModeApp::hasSetAutomotiveProjection);
+    }
+
+    /**
+     * Force-removes a package from the car mode tracking list, no matter at which priority.
+     *
+     * This handles the case where packages are disabled or uninstalled. In those case, remove them
+     * from the tracking list so they don't cause a leak.
+     * @param packageName Package name of the app to force-remove
+     */
+    public void forceRemove(@NonNull String packageName) {
+        // We must account for the possibility that the app has set both car mode AND projection.
+        List<CarModeApp> forcedApp = mCarModeApps.stream()
+                .filter(c -> c.getPackageName().equals(packageName))
+                .collect(Collectors.toList());
+        if (forcedApp.isEmpty()) {
+            Log.i(this, "Package %s is not tracked.", packageName);
+            return;
+        }
+        for (CarModeApp app : forcedApp) {
+            String logString = "forceRemove: " + app;
+            Log.i(this, logString);
+            mCarModeChangeLog.log(logString);
+        }
+        mCarModeApps.removeIf(c -> c.getPackageName().equals(packageName));
+    }
+
     /**
      * Retrieves a list of the apps which are currently in car mode, ordered by priority such that
      * the highest priority app is first.
@@ -152,7 +227,7 @@
         return mCarModeApps
                 .stream()
                 .sorted(mCarModeApps.comparator())
-                .map(cma -> cma.getPackageName())
+                .map(CarModeApp::getPackageName)
                 .collect(Collectors.toList());
     }
 
@@ -160,7 +235,7 @@
         return mCarModeApps
                 .stream()
                 .sorted(mCarModeApps.comparator())
-                .map(cma -> "[" + cma.getPriority() + ", " + cma.getPackageName() + "]")
+                .map(CarModeApp::toString)
                 .collect(Collectors.joining(", "));
     }
 
@@ -193,7 +268,7 @@
         pw.increaseIndent();
         for (CarModeApp app : mCarModeApps) {
             pw.print("[");
-            pw.print(app.getPriority());
+            pw.print(app.hasSetAutomotiveProjection() ? "PROJECTION SET" : app.getPriority());
             pw.print("] ");
             pw.println(app.getPackageName());
         }
diff --git a/src/com/android/server/telecom/ConnectionServiceFocusManager.java b/src/com/android/server/telecom/ConnectionServiceFocusManager.java
index fbb23f4..aa0a64f 100644
--- a/src/com/android/server/telecom/ConnectionServiceFocusManager.java
+++ b/src/com/android/server/telecom/ConnectionServiceFocusManager.java
@@ -391,7 +391,7 @@
     }
 
     private void handleRequestFocus(FocusRequest focusRequest) {
-        Log.d(this, "handleRequestFocus req = %s", focusRequest);
+        Log.i(this, "handleRequestFocus req = %s", focusRequest);
         if (mCurrentFocus == null
                 || mCurrentFocus.equals(focusRequest.call.getConnectionServiceWrapper())) {
             updateConnectionServiceFocus(focusRequest.call.getConnectionServiceWrapper());
diff --git a/src/com/android/server/telecom/ConnectionServiceRepository.java b/src/com/android/server/telecom/ConnectionServiceRepository.java
index d34ea3c..3991ed5 100644
--- a/src/com/android/server/telecom/ConnectionServiceRepository.java
+++ b/src/com/android/server/telecom/ConnectionServiceRepository.java
@@ -79,6 +79,13 @@
         return service;
     }
 
+    @VisibleForTesting
+    public void setService(ComponentName componentName, UserHandle userHandle,
+            ConnectionServiceWrapper service) {
+        Pair<ComponentName, UserHandle> cacheKey = Pair.create(componentName, userHandle);
+        mServiceCache.put(cacheKey, service);
+    }
+
     /**
      * Dumps the state of the {@link ConnectionServiceRepository}.
      *
diff --git a/src/com/android/server/telecom/ConnectionServiceWrapper.java b/src/com/android/server/telecom/ConnectionServiceWrapper.java
index 01acdd1..da2669c 100755
--- a/src/com/android/server/telecom/ConnectionServiceWrapper.java
+++ b/src/com/android/server/telecom/ConnectionServiceWrapper.java
@@ -18,6 +18,7 @@
 
 import static android.Manifest.permission.MODIFY_PHONE_STATE;
 
+import android.Manifest;
 import android.app.AppOpsManager;
 import android.content.ComponentName;
 import android.content.Context;
@@ -30,6 +31,7 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.telecom.CallAudioState;
+import android.telecom.CallScreeningService;
 import android.telecom.Connection;
 import android.telecom.ConnectionRequest;
 import android.telecom.ConnectionService;
@@ -91,6 +93,7 @@
                             mServiceInterface.createConnectionComplete(callId,
                                     Log.getExternalSession());
                         } catch (RemoteException e) {
+                            logOutgoing("createConnectionComplete remote exception=%s", e);
                         }
                     }
                 }
@@ -349,7 +352,7 @@
                     logIncoming("removeCall %s", callId);
                     Call call = mCallIdMapper.getCall(callId);
                     if (call != null) {
-                        if (call.isAlive()) {
+                        if (call.isAlive() && !call.isDisconnectHandledViaFuture()) {
                             mCallsManager.markCallAsDisconnected(
                                     call, new DisconnectCause(DisconnectCause.REMOTE));
                         } else {
@@ -917,6 +920,45 @@
                         } else {
                             connectIdToCheck = callId;
                         }
+
+                        // Handle the case where an existing connection was added by Telephony via
+                        // a connection manager.  The remote connection service API does not include
+                        // the ability to specify a parent connection when adding an existing
+                        // connection, so we stash the desired parent in the connection extras.
+                        if (connectionExtras != null
+                                && connectionExtras.containsKey(
+                                        Connection.EXTRA_ADD_TO_CONFERENCE_ID)
+                                && connection.getParentCallId() == null) {
+                            String parentId = connectionExtras.getString(
+                                    Connection.EXTRA_ADD_TO_CONFERENCE_ID);
+                            Log.i(ConnectionServiceWrapper.this, "addExistingConnection: remote "
+                                    + "connection will auto-add to parent %s", parentId);
+                            // Replace parcelable connection instance, swapping the new desired
+                            // parent in.
+                            connection = new ParcelableConnection(
+                                    connection.getPhoneAccount(),
+                                    connection.getState(),
+                                    connection.getConnectionCapabilities(),
+                                    connection.getConnectionProperties(),
+                                    connection.getSupportedAudioRoutes(),
+                                    connection.getHandle(),
+                                    connection.getHandlePresentation(),
+                                    connection.getCallerDisplayName(),
+                                    connection.getCallerDisplayNamePresentation(),
+                                    connection.getVideoProvider(),
+                                    connection.getVideoState(),
+                                    connection.isRingbackRequested(),
+                                    connection.getIsVoipAudioMode(),
+                                    connection.getConnectTimeMillis(),
+                                    connection.getConnectElapsedTimeMillis(),
+                                    connection.getStatusHints(),
+                                    connection.getDisconnectCause(),
+                                    connection.getConferenceableConnectionIds(),
+                                    connection.getExtras(),
+                                    parentId,
+                                    connection.getCallDirection(),
+                                    connection.getCallerNumberVerificationStatus());
+                        }
                         // Check to see if this Connection has already been added.
                         Call alreadyAddedConnection = mCallsManager
                                 .getAlreadyAddedConnection(connectIdToCheck);
@@ -1206,6 +1248,10 @@
                 mPendingResponses.put(callId, response);
 
                 Bundle extras = call.getIntentExtras();
+                if (extras == null) {
+                    extras = new Bundle();
+                }
+                extras.putString(Connection.EXTRA_ORIGINAL_CONNECTION_ID, callId);
 
                 Log.addEvent(call, LogUtils.Events.START_CONFERENCE,
                         Log.piiHandle(call.getHandle()));
@@ -1264,6 +1310,14 @@
             @Override
             public void onSuccess() {
                 String callId = mCallIdMapper.getCallId(call);
+                if (callId == null) {
+                    Log.w(ConnectionServiceWrapper.this, "Call not present"
+                            + " in call id mapper, maybe it was aborted before the bind"
+                            + " completed successfully?");
+                    response.handleCreateConnectionFailure(
+                            new DisconnectCause(DisconnectCause.CANCELED));
+                    return;
+                }
                 mPendingResponses.put(callId, response);
 
                 GatewayInfo gatewayInfo = call.getGatewayInfo();
@@ -1347,7 +1401,8 @@
      * create a connection has been denied or failed.
      * @param call The call.
      */
-    void createConnectionFailed(final Call call) {
+    @VisibleForTesting
+    public void createConnectionFailed(final Call call) {
         Log.d(this, "createConnectionFailed(%s) via %s.", call, getComponentName());
         BindCallback callback = new BindCallback() {
             @Override
@@ -1568,6 +1623,34 @@
         }
     }
 
+    /** @see IConnectionService#onUsingAlternativeUi(String, boolean, Session.Info) */
+    @VisibleForTesting
+    public void onUsingAlternativeUi(Call activeCall, boolean isUsingAlternativeUi) {
+        final String callId = mCallIdMapper.getCallId(activeCall);
+        if (callId != null && isServiceValid("onUsingAlternativeUi")) {
+            try {
+                logOutgoing("onUsingAlternativeUi %s", isUsingAlternativeUi);
+                mServiceInterface.onUsingAlternativeUi(callId, isUsingAlternativeUi,
+                        Log.getExternalSession(TELECOM_ABBREVIATION));
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
+    /** @see IConnectionService#onTrackedByNonUiService(String, boolean, Session.Info) */
+    @VisibleForTesting
+    public void onTrackedByNonUiService(Call activeCall, boolean isTracked) {
+        final String callId = mCallIdMapper.getCallId(activeCall);
+        if (callId != null && isServiceValid("onTrackedByNonUiService")) {
+            try {
+                logOutgoing("onTrackedByNonUiService %s", isTracked);
+                mServiceInterface.onTrackedByNonUiService(callId, isTracked,
+                        Log.getExternalSession(TELECOM_ABBREVIATION));
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
     /** @see IConnectionService#disconnect(String, Session.Info) */
     void disconnect(Call call) {
         final String callId = mCallIdMapper.getCallId(call);
@@ -1830,6 +1913,28 @@
         }
     }
 
+    void onCallFilteringCompleted(Call call,
+            Connection.CallFilteringCompletionInfo completionInfo) {
+        final String callId = mCallIdMapper.getCallId(call);
+        if (callId != null && isServiceValid("onCallFilteringCompleted")) {
+            try {
+                logOutgoing("onCallFilteringCompleted %s", completionInfo);
+                int contactsPermission = mContext.getPackageManager()
+                        .checkPermission(Manifest.permission.READ_CONTACTS,
+                                getComponentName().getPackageName());
+                if (contactsPermission == PackageManager.PERMISSION_GRANTED) {
+                    mServiceInterface.onCallFilteringCompleted(callId, completionInfo,
+                            Log.getExternalSession(TELECOM_ABBREVIATION));
+                } else {
+                    logOutgoing("Skipping call filtering complete message for %s due"
+                            + " to lack of READ_CONTACTS", getComponentName().getPackageName());
+                }
+            } catch (RemoteException e) {
+                Log.e(this, e, "Remote exception calling onCallFilteringCompleted");
+            }
+        }
+    }
+
     void onExtrasChanged(Call call, Bundle extras) {
         final String callId = mCallIdMapper.getCallId(call);
         if (callId != null && isServiceValid("onExtrasChanged")) {
diff --git a/src/com/android/server/telecom/CreateConnectionProcessor.java b/src/com/android/server/telecom/CreateConnectionProcessor.java
index 700dac7..2e67b08 100644
--- a/src/com/android/server/telecom/CreateConnectionProcessor.java
+++ b/src/com/android/server/telecom/CreateConnectionProcessor.java
@@ -403,6 +403,7 @@
             // When testing emergency calls, we want the calls to go through to the test connection
             // service, not the telephony ConnectionService.
             if (mCall.isTestEmergencyCall()) {
+                Log.i(this, "Processing test emergency call -- special rules");
                 allAccounts = mPhoneAccountRegistrar.filterRestrictedPhoneAccounts(allAccounts);
             }
 
@@ -411,7 +412,7 @@
                     preferredPAH);
             // Next, add all SIM phone accounts which can place emergency calls.
             sortSimPhoneAccountsForEmergency(allAccounts, preferredPA);
-            // and pick the fist one that can place emergency calls.
+            // and pick the first one that can place emergency calls.
             for (PhoneAccount phoneAccount : allAccounts) {
                 if (phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS)
                         && phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) {
@@ -438,7 +439,10 @@
                             mPhoneAccountRegistrar.getOutgoingPhoneAccountForSchemeOfCurrentUser(
                                     mCall.getHandle() == null
                                             ? null : mCall.getHandle().getScheme()));
-                    if (!mAttemptRecords.contains(callAttemptRecord)) {
+                    // If the target phone account is null, we'll run into a NPE during the retry
+                    // process, so skip it now if it's null.
+                    if (callAttemptRecord.targetPhoneAccount != null
+                            && !mAttemptRecords.contains(callAttemptRecord)) {
                         Log.i(this, "Will try Connection Manager account %s for emergency",
                                 callManager);
                         mAttemptRecords.add(callAttemptRecord);
diff --git a/src/com/android/server/telecom/CreateConnectionTimeout.java b/src/com/android/server/telecom/CreateConnectionTimeout.java
index 399c28a..14e5bf0 100644
--- a/src/com/android/server/telecom/CreateConnectionTimeout.java
+++ b/src/com/android/server/telecom/CreateConnectionTimeout.java
@@ -69,7 +69,7 @@
         }
 
         // Timeout is only supported for SIM call managers that are set by the carrier.
-        if (!Objects.equals(connectionManager.getComponentName(),
+        if (connectionManager != null && !Objects.equals(connectionManager.getComponentName(),
                 mPhoneAccountRegistrar.getSystemSimCallManagerComponent())) {
             Log.d(this, "isTimeoutNeededForCall, not a system sim call manager");
             return false;
diff --git a/src/com/android/server/telecom/DefaultDialerCache.java b/src/com/android/server/telecom/DefaultDialerCache.java
index c2b78ee..a4a0242 100644
--- a/src/com/android/server/telecom/DefaultDialerCache.java
+++ b/src/com/android/server/telecom/DefaultDialerCache.java
@@ -217,6 +217,12 @@
         return mSystemDialerComponentName;
     }
 
+    public ComponentName getDialtactsSystemDialerComponent() {
+        final Resources resources = mContext.getResources();
+        return new ComponentName(getSystemDialerApplication(),
+                resources.getString(R.string.dialer_default_class));
+    }
+
     public void observeDefaultDialerApplication(Executor executor, IntConsumer observer) {
         mRoleManagerAdapter.observeDefaultDialerApp(executor, observer);
     }
@@ -289,4 +295,8 @@
     public ContentObserver getContentObserver() {
         return mDefaultDialerObserver;
     }
+
+    public RoleManagerAdapter getRoleManagerAdapter() {
+        return mRoleManagerAdapter;
+    }
 }
\ No newline at end of file
diff --git a/src/com/android/server/telecom/DtmfLocalTonePlayer.java b/src/com/android/server/telecom/DtmfLocalTonePlayer.java
index 304a698..5869008 100644
--- a/src/com/android/server/telecom/DtmfLocalTonePlayer.java
+++ b/src/com/android/server/telecom/DtmfLocalTonePlayer.java
@@ -193,8 +193,9 @@
         final Context context = call.getContext();
         final boolean areLocalTonesEnabled;
         if (context.getResources().getBoolean(R.bool.allow_local_dtmf_tones)) {
-            areLocalTonesEnabled = Settings.System.getInt(
-                    context.getContentResolver(), Settings.System.DTMF_TONE_WHEN_DIALING, 1) == 1;
+            areLocalTonesEnabled = Settings.System.getIntForUser(
+                    context.getContentResolver(), Settings.System.DTMF_TONE_WHEN_DIALING, 1,
+                    context.getUserId()) == 1;
         } else {
             areLocalTonesEnabled = false;
         }
diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java
index 72a4a39..ca76456 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -16,6 +16,7 @@
 
 package com.android.server.telecom;
 
+import static android.app.AppOpsManager.OPSTR_RECORD_AUDIO;
 import static android.os.Process.myUid;
 
 import android.Manifest;
@@ -23,20 +24,28 @@
 import android.app.AppOpsManager;
 import android.app.Notification;
 import android.app.NotificationManager;
+import android.content.AttributionSource;
+import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.PermissionChecker;
 import android.content.ServiceConnection;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
+import android.hardware.SensorPrivacyManager;
+import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
+import android.os.PackageTagsList;
 import android.os.RemoteException;
 import android.os.Trace;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.telecom.CallAudioState;
 import android.telecom.ConnectionService;
 import android.telecom.InCallService;
@@ -46,10 +55,12 @@
 import android.telecom.TelecomManager;
 import android.text.TextUtils;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 
 import com.android.internal.annotations.VisibleForTesting;
 // TODO: Needed for move to system service: import com.android.internal.R;
 import com.android.internal.telecom.IInCallService;
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.telecom.SystemStateHelper.SystemStateListener;
 import com.android.server.telecom.ui.NotificationChannelManager;
@@ -61,6 +72,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
@@ -70,9 +82,10 @@
  * can send updates to the in-call app. This class is created and owned by CallsManager and retains
  * a binding to the {@link IInCallService} (implemented by the in-call app).
  */
-public class InCallController extends CallsManagerListenerBase {
-    public static final int IN_CALL_SERVICE_NOTIFICATION_ID = 3;
+public class InCallController extends CallsManagerListenerBase implements
+        AppOpsManager.OnOpActiveChangedListener {
     public static final String NOTIFICATION_TAG = InCallController.class.getSimpleName();
+    public static final int IN_CALL_SERVICE_NOTIFICATION_ID = 3;
 
     public class InCallServiceConnection {
         /**
@@ -108,7 +121,7 @@
         public Call mCall;
     }
 
-    private class InCallServiceInfo {
+    public static class InCallServiceInfo {
         private final ComponentName mComponentName;
         private boolean mIsExternalCallsSupported;
         private boolean mIsSelfManagedCallsSupported;
@@ -269,12 +282,22 @@
         @Override
         public int connect(Call call) {
             if (mIsConnected) {
-                Log.addEvent(call, LogUtils.Events.INFO, "Already connected, ignoring request.");
+                Log.addEvent(call, LogUtils.Events.INFO, "Already connected, ignoring request: "
+                        + mInCallServiceInfo);
+                if (call != null) {
+                    // Track the call if we don't already know about it.
+                    addCall(call);
+
+                    // Notify this new added call
+                    sendCallToService(call, mInCallServiceInfo,
+                            mInCallServices.get(mInCallServiceInfo));
+                }
                 return CONNECTION_SUCCEEDED;
             }
 
             if (call != null && call.isSelfManaged() &&
-                    !mInCallServiceInfo.isSelfManagedCallsSupported()) {
+                    (!mInCallServiceInfo.isSelfManagedCallsSupported()
+                            || !call.visibleToInCallService())) {
                 Log.i(this, "Skipping binding to %s - doesn't support self-mgd calls",
                         mInCallServiceInfo);
                 mIsConnected = false;
@@ -283,7 +306,7 @@
 
             Intent intent = new Intent(InCallService.SERVICE_INTERFACE);
             intent.setComponent(mInCallServiceInfo.getComponentName());
-            if (call != null && !call.isIncoming() && !call.isExternalCall()){
+            if (call != null && !call.isIncoming() && !call.isExternalCall()) {
                 intent.putExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS,
                         call.getIntentExtras());
                 intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
@@ -294,9 +317,9 @@
             mIsConnected = true;
             mInCallServiceInfo.setBindingStartTime(mClockProxy.elapsedRealtime());
             if (!mContext.bindServiceAsUser(intent, mServiceConnection,
-                        Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
-                        | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS,
-                        UserHandle.CURRENT)) {
+                    Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
+                            | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS,
+                    UserHandle.CURRENT)) {
                 Log.w(this, "Failed to connect.");
                 mIsConnected = false;
             }
@@ -337,6 +360,7 @@
                             mInCallServiceInfo.getType(),
                             mInCallServiceInfo.getDisconnectTime()
                                     - mInCallServiceInfo.getBindingStartTime(), mIsNullBinding);
+                    updateCallTracking(mCall, mInCallServiceInfo, false /* isAdd */);
                 }
 
                 InCallController.this.onDisconnected(mInCallServiceInfo);
@@ -435,15 +459,15 @@
             }
 
             mEmergencyCallHelper.maybeGrantTemporaryLocationPermission(call,
-                mCallsManager.getCurrentUserHandle());
+                    mCallsManager.getCurrentUserHandle());
 
             if (call != null && call.isIncoming()
-                && mEmergencyCallHelper.getLastEmergencyCallTimeMillis() > 0) {
-              // Add the last emergency call time to the call
-              Bundle extras = new Bundle();
-              extras.putLong(android.telecom.Call.EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS,
-                      mEmergencyCallHelper.getLastEmergencyCallTimeMillis());
-              call.putExtras(Call.SOURCE_CONNECTION_SERVICE, extras);
+                    && mEmergencyCallHelper.getLastEmergencyCallTimeMillis() > 0) {
+                // Add the last emergency call time to the call
+                Bundle extras = new Bundle();
+                extras.putLong(android.telecom.Call.EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS,
+                        mEmergencyCallHelper.getLastEmergencyCallTimeMillis());
+                call.putExtras(Call.SOURCE_CONNECTION_SERVICE, extras);
             }
 
             // If we are here, we didn't or could not connect to child. So lets connect ourselves.
@@ -452,7 +476,7 @@
 
         @Override
         public void disconnect() {
-            Log.i(this, "Disconnect forced!");
+            Log.i(this, "Disconnecting from InCallService");
             if (mIsProxying) {
                 mSubConnection.disconnect();
             } else {
@@ -477,6 +501,7 @@
                 return super.getInfo();
             }
         }
+
         @Override
         protected void onDisconnected() {
             // Save this here because super.onDisconnected() could force us to explicitly
@@ -540,6 +565,7 @@
         /**
          * Called when we move to a state where calls are present on the device.  Chooses the
          * {@link InCallService} to which we should connect.
+         *
          * @param isCarMode {@code true} if device is in car mode, {@code false} otherwise.
          */
         public synchronized void chooseInitialInCallService(boolean isCarMode) {
@@ -578,6 +604,7 @@
         /**
          * Changes the active {@link InCallService} to a car mode app.  Called whenever the device
          * changes to car mode or the currently active car mode app changes.
+         *
          * @param packageName The package name of the car mode app.
          */
         public synchronized void changeCarModeApp(String packageName) {
@@ -586,7 +613,8 @@
             InCallServiceInfo currentConnectionInfo = mCurrentConnection == null ? null
                     : mCurrentConnection.getInfo();
             InCallServiceInfo carModeConnectionInfo =
-                    getInCallServiceComponent(packageName, IN_CALL_SERVICE_TYPE_CAR_MODE_UI);
+                    getInCallServiceComponent(packageName,
+                            IN_CALL_SERVICE_TYPE_CAR_MODE_UI, true /* ignoreDisabed */);
 
             if (!Objects.equals(currentConnectionInfo, carModeConnectionInfo)) {
                 Log.i(this, "changeCarModeApp: " + currentConnectionInfo + " => "
@@ -601,7 +629,7 @@
                             new InCallServiceBindingConnection(carModeConnectionInfo);
                     mIsCarMode = true;
                 } else {
-                    // Invalid car mode app; don't expect this but should handle it gracefully.
+                    // The app is not enabled. Using the default dialer connection instead
                     mCarModeConnection = null;
                     mIsCarMode = false;
                     mCurrentConnection = mDialerConnection;
@@ -615,6 +643,10 @@
             }
         }
 
+        public boolean isCarMode() {
+            return mIsCarMode;
+        }
+
         @Override
         public int connect(Call call) {
             if (mIsConnected) {
@@ -729,6 +761,24 @@
             }
             pw.decreaseIndent();
         }
+
+        public void addConnections(List<InCallServiceBindingConnection> newConnections) {
+            // connect() needs to be called with a Call object. Since we're in the middle of any
+            // possible number of calls right now, choose an arbitrary one from the ones that
+            // InCallController is tracking.
+            if (mCallIdMapper.getCalls().isEmpty()) {
+                Log.w(InCallController.this, "No calls tracked while adding new NonUi incall");
+                return;
+            }
+            Call callToConnectWith = mCallIdMapper.getCalls().iterator().next();
+            for (InCallServiceBindingConnection newConnection : newConnections) {
+                newConnection.connect(callToConnectWith);
+            }
+        }
+
+        public List<InCallServiceBindingConnection> getSubConnections() {
+            return mSubConnections;
+        }
     }
 
     private final Call.Listener mCallListener = new Call.ListenerBase() {
@@ -853,19 +903,83 @@
         public void onRemoteRttRequest(Call call, int requestId) {
             notifyRemoteRttRequest(call, requestId);
         }
+
+        @Override
+        public void onCallerNumberVerificationStatusChanged(Call call,
+                int callerNumberVerificationStatus) {
+            updateCall(call);
+        }
     };
 
-    private final SystemStateListener mSystemStateListener =
-            (priority, packageName, isCarMode) -> InCallController.this.handleCarModeChange(
-                    priority, packageName, isCarMode);
+    private BroadcastReceiver mPackageChangedReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            Log.startSession("ICC.pCR");
+            try {
+                if (Intent.ACTION_PACKAGE_CHANGED.equals(intent.getAction())) {
+                    synchronized (mLock) {
+                        String changedPackage = intent.getData().getSchemeSpecificPart();
+                        List<InCallServiceBindingConnection> componentsToBind =
+                                Arrays.stream(intent.getStringArrayExtra(
+                                        Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST))
+                                        .map((className) ->
+                                                ComponentName.createRelative(changedPackage,
+                                                        className))
+                                        .filter(mKnownNonUiInCallServices::contains)
+                                        .flatMap(componentName -> getInCallServiceComponents(
+                                                componentName,
+                                                IN_CALL_SERVICE_TYPE_NON_UI).stream())
+                                        .map(InCallServiceBindingConnection::new)
+                                        .collect(Collectors.toList());
+
+                        if (mNonUIInCallServiceConnections != null) {
+                            mNonUIInCallServiceConnections.addConnections(componentsToBind);
+                        }
+
+                        // If the current car mode app become enabled from disabled, update
+                        // the connection to binding
+                        updateCarModeForConnections();
+                    }
+                }
+            } finally {
+                Log.endSession();
+            }
+        }
+    };
+
+    private final SystemStateListener mSystemStateListener = new SystemStateListener() {
+        @Override
+        public void onCarModeChanged(int priority, String packageName, boolean isCarMode) {
+            InCallController.this.handleCarModeChange(priority, packageName, isCarMode);
+        }
+
+        @Override
+        public void onAutomotiveProjectionStateSet(String automotiveProjectionPackage) {
+            InCallController.this.handleSetAutomotiveProjection(automotiveProjectionPackage);
+        }
+
+        @Override
+        public void onAutomotiveProjectionStateReleased() {
+            InCallController.this.handleReleaseAutomotiveProjection();
+        }
+
+        @Override
+        public void onPackageUninstalled(String packageName) {
+            mCarModeTracker.forceRemove(packageName);
+            updateCarModeForConnections();
+        }
+    };
 
     private static final int IN_CALL_SERVICE_TYPE_INVALID = 0;
-    private static final int IN_CALL_SERVICE_TYPE_DIALER_UI = 1;
+    private static final int IN_CALL_SERVICE_TYPE_DEFAULT_DIALER_UI = 1;
     private static final int IN_CALL_SERVICE_TYPE_SYSTEM_UI = 2;
     private static final int IN_CALL_SERVICE_TYPE_CAR_MODE_UI = 3;
     private static final int IN_CALL_SERVICE_TYPE_NON_UI = 4;
     private static final int IN_CALL_SERVICE_TYPE_COMPANION = 5;
 
+    private static final int[] LIVE_CALL_STATES = { CallState.ACTIVE, CallState.PULLING,
+            CallState.DISCONNECTING };
+
     /** The in-call app implementations, see {@link IInCallService}. */
     private final Map<InCallServiceInfo, IInCallService> mInCallServices = new ArrayMap<>();
 
@@ -873,6 +987,7 @@
 
     private final Context mContext;
     private final AppOpsManager mAppOpsManager;
+    private final SensorPrivacyManager mSensorPrivacyManager;
     private final TelecomSystem.SyncRoot mLock;
     private final CallsManager mCallsManager;
     private final SystemStateHelper mSystemStateHelper;
@@ -883,6 +998,12 @@
     private CarSwappingInCallServiceConnection mInCallServiceConnection;
     private NonUIInCallServiceConnectionCollection mNonUIInCallServiceConnections;
     private final ClockProxy mClockProxy;
+    private final IBinder mToken = new Binder();
+
+    // A set of known non-UI in call services on the device, including those that are disabled.
+    // We track this so that we can efficiently bind to them when we're notified that a new
+    // component has been enabled.
+    private Set<ComponentName> mKnownNonUiInCallServices = new ArraySet<>();
 
     // Future that's in a completed state unless we're in the middle of binding to a service.
     // The future will complete with true if binding succeeds, false if it timed out.
@@ -897,17 +1018,33 @@
 
     /**
      * {@code true} if InCallController is tracking a managed, not external call which is using the
-     * microphone, {@code false} otherwise.
+     * microphone, and is not muted {@code false} otherwise.
      */
     private boolean mIsCallUsingMicrophone = false;
 
+    /**
+     * {@code true} if InCallController is tracking a managed, not external call which is using the
+     * microphone, {@code false} otherwise.
+     */
+    private boolean mIsTrackingManagedAliveCall = false;
+
+    private boolean mIsStartCallDelayScheduled = false;
+
+    /**
+     * A list of call IDs which are currently using the camera.
+     */
+    private ArrayList<String> mCallsUsingCamera = new ArrayList<>();
+
+    private ArraySet<String> mAllCarrierPrivilegedApps = new ArraySet<>();
+    private ArraySet<String> mActiveCarrierPrivilegedApps = new ArraySet<>();
+
     public InCallController(Context context, TelecomSystem.SyncRoot lock, CallsManager callsManager,
-            SystemStateHelper systemStateHelper,
-            DefaultDialerCache defaultDialerCache, Timeouts.Adapter timeoutsAdapter,
-            EmergencyCallHelper emergencyCallHelper, CarModeTracker carModeTracker,
-            ClockProxy clockProxy) {
+            SystemStateHelper systemStateHelper, DefaultDialerCache defaultDialerCache,
+            Timeouts.Adapter timeoutsAdapter, EmergencyCallHelper emergencyCallHelper,
+            CarModeTracker carModeTracker, ClockProxy clockProxy) {
         mContext = context;
         mAppOpsManager = context.getSystemService(AppOpsManager.class);
+        mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
         mLock = lock;
         mCallsManager = callsManager;
         mSystemStateHelper = systemStateHelper;
@@ -917,6 +1054,76 @@
         mCarModeTracker = carModeTracker;
         mSystemStateHelper.addListener(mSystemStateListener);
         mClockProxy = clockProxy;
+        restrictPhoneCallOps();
+    }
+
+    private void restrictPhoneCallOps() {
+        PackageTagsList packageRestriction = new PackageTagsList.Builder()
+                .add(mContext.getPackageName())
+                .build();
+        mAppOpsManager.setUserRestrictionForUser(AppOpsManager.OP_PHONE_CALL_MICROPHONE, true,
+                mToken, packageRestriction, UserHandle.USER_ALL);
+        mAppOpsManager.setUserRestrictionForUser(AppOpsManager.OP_PHONE_CALL_CAMERA, true,
+                mToken, packageRestriction, UserHandle.USER_ALL);
+    }
+
+    @Override
+    public void onOpActiveChanged(@androidx.annotation.NonNull String op, int uid,
+            @androidx.annotation.NonNull String packageName, boolean active) {
+        synchronized (mLock) {
+            if (!mAllCarrierPrivilegedApps.contains(packageName)) {
+                return;
+            }
+
+            if (active) {
+                mActiveCarrierPrivilegedApps.add(packageName);
+            } else {
+                mActiveCarrierPrivilegedApps.remove(packageName);
+            }
+            maybeTrackMicrophoneUse(isMuted());
+        }
+    }
+
+    private void updateAllCarrierPrivilegedUsingMic() {
+        mActiveCarrierPrivilegedApps.clear();
+        UserManager userManager = mContext.getSystemService(UserManager.class);
+        PackageManager pkgManager = mContext.getPackageManager();
+        for (String pkg : mAllCarrierPrivilegedApps) {
+            boolean isActive = mActiveCarrierPrivilegedApps.contains(pkg);
+            List<UserHandle> users = userManager.getUserHandles(true);
+            for (UserHandle user : users) {
+                if (isActive) {
+                    break;
+                }
+
+                int uid;
+                try {
+                    uid = pkgManager.getPackageUidAsUser(pkg, user.getIdentifier());
+                } catch (PackageManager.NameNotFoundException e) {
+                    continue;
+                }
+                List<AppOpsManager.PackageOps> pkgOps = mAppOpsManager.getOpsForPackage(
+                        uid, pkg, OPSTR_RECORD_AUDIO);
+                for (int j = 0; j < pkgOps.size(); j++) {
+                    List<AppOpsManager.OpEntry> opEntries = pkgOps.get(j).getOps();
+                    for (int k = 0; k < opEntries.size(); k++) {
+                        AppOpsManager.OpEntry entry = opEntries.get(k);
+                        if (entry.isRunning()) {
+                            mActiveCarrierPrivilegedApps.add(pkg);
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    private void updateAllCarrierPrivileged() {
+        mAllCarrierPrivilegedApps.clear();
+        for (Call call : mCallIdMapper.getCalls()) {
+            mAllCarrierPrivilegedApps.add(call.getConnectionManagerPhoneAccount()
+                    .getComponentName().getPackageName());
+        }
     }
 
     @Override
@@ -948,12 +1155,16 @@
                     continue;
                 }
 
-                if (call.isSelfManaged() && !info.isSelfManagedCallsSupported()) {
+                if (call.isSelfManaged() && (!call.visibleToInCallService()
+                        || !info.isSelfManagedCallsSupported())) {
                     continue;
                 }
 
                 // Only send the RTT call if it's a UI in-call service
-                boolean includeRttCall = info.equals(mInCallServiceConnection.getInfo());
+                boolean includeRttCall = false;
+                if (mInCallServiceConnection != null) {
+                    includeRttCall = info.equals(mInCallServiceConnection.getInfo());
+                }
 
                 componentsUpdated.add(info.getComponentName());
                 IInCallService inCallService = entry.getValue();
@@ -961,9 +1172,11 @@
                 ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(call,
                         true /* includeVideoProvider */, mCallsManager.getPhoneAccountRegistrar(),
                         info.isExternalCallsSupported(), includeRttCall,
-                        info.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI);
+                        info.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI ||
+                                info.getType() == IN_CALL_SERVICE_TYPE_NON_UI);
                 try {
                     inCallService.addCall(sanitizeParcelableCallForService(info, parcelableCall));
+                    updateCallTracking(call, info, true /* isAdd */);
                 } catch (RemoteException ignored) {
                 }
             }
@@ -989,11 +1202,16 @@
                     }
                 }
             }.prepare(), mTimeoutsAdapter.getCallRemoveUnbindInCallServicesDelay(
-                            mContext.getContentResolver()));
+                    mContext.getContentResolver()));
         }
         call.removeListener(mCallListener);
         mCallIdMapper.removeCall(call);
+        if (mCallIdMapper.getCalls().isEmpty()) {
+            mActiveCarrierPrivilegedApps.clear();
+            mAppOpsManager.stopWatchingActive(this);
+        }
         maybeTrackMicrophoneUse(isMuted());
+        onSetCamera(call, null);
     }
 
     @Override
@@ -1013,7 +1231,8 @@
                     continue;
                 }
 
-                if (call.isSelfManaged() && !info.isSelfManagedCallsSupported()) {
+                if (call.isSelfManaged() && !call.visibleToInCallService()
+                        && !info.isSelfManagedCallsSupported()) {
                     continue;
                 }
 
@@ -1026,9 +1245,11 @@
                 ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(call,
                         true /* includeVideoProvider */, mCallsManager.getPhoneAccountRegistrar(),
                         info.isExternalCallsSupported(), includeRttCall,
-                        info.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI);
+                        info.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI
+                                || info.getType() == IN_CALL_SERVICE_TYPE_NON_UI);
                 try {
                     inCallService.addCall(sanitizeParcelableCallForService(info, parcelableCall));
+                    updateCallTracking(call, info, true /* isAdd */);
                 } catch (RemoteException ignored) {
                 }
             }
@@ -1057,7 +1278,8 @@
                         android.telecom.Call.STATE_DISCONNECTED /* overrideState */,
                         false /* includeRttCall */,
                         info.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI
-                        );
+                                || info.getType() == IN_CALL_SERVICE_TYPE_NON_UI
+                );
 
                 try {
                     inCallService.updateCall(
@@ -1141,6 +1363,7 @@
     public void onIsVoipAudioModeChanged(Call call) {
         Log.d(this, "onIsVoipAudioModeChanged %s", call);
         updateCall(call);
+        maybeTrackMicrophoneUse(isMuted());
     }
 
     @Override
@@ -1157,18 +1380,35 @@
 
     /**
      * Track changes to camera usage for a call.
-     * @param call The call.
+     *
+     * @param call     The call.
      * @param cameraId The id of the camera to use, or {@code null} if camera is off.
      */
     @Override
     public void onSetCamera(Call call, String cameraId) {
+        if (call == null) {
+            return;
+        }
+
         Log.i(this, "onSetCamera callId=%s, cameraId=%s", call.getId(), cameraId);
         if (cameraId != null) {
-            mAppOpsManager.startOp(AppOpsManager.OP_PHONE_CALL_CAMERA, myUid(),
-                    mContext.getOpPackageName(), false, null, null);
+            boolean shouldStart = mCallsUsingCamera.isEmpty();
+            if (!mCallsUsingCamera.contains(call.getId())) {
+                mCallsUsingCamera.add(call.getId());
+            }
+
+            if (shouldStart) {
+                mAppOpsManager.startOp(AppOpsManager.OP_PHONE_CALL_CAMERA, myUid(),
+                        mContext.getOpPackageName(), false, null, null);
+                mSensorPrivacyManager.showSensorUseDialog(SensorPrivacyManager.Sensors.CAMERA);
+            }
         } else {
-            mAppOpsManager.finishOp(AppOpsManager.OP_PHONE_CALL_CAMERA, myUid(),
-                    mContext.getOpPackageName(), null);
+            boolean hadCall = !mCallsUsingCamera.isEmpty();
+            mCallsUsingCamera.remove(call.getId());
+            if (hadCall && mCallsUsingCamera.isEmpty()) {
+                mAppOpsManager.finishOp(AppOpsManager.OP_PHONE_CALL_CAMERA, myUid(),
+                        mContext.getOpPackageName(), null);
+            }
         }
     }
 
@@ -1201,8 +1441,8 @@
             for (IInCallService inCallService : mInCallServices.values()) {
                 try {
                     Log.i(this, "notifyConnectionEvent {Call: %s, Event: %s, Extras:[%s]}",
-                            (call != null ? call.toString() :"null"),
-                            (event != null ? event : "null") ,
+                            (call != null ? call.toString() : "null"),
+                            (event != null ? event : "null"),
                             (extras != null ? extras.toString() : "null"));
                     inCallService.onConnectionEvent(mCallIdMapper.getCallId(call), event, extras);
                 } catch (RemoteException ignored) {
@@ -1213,7 +1453,7 @@
 
     private void notifyRttInitiationFailure(Call call, int reason) {
         if (!mInCallServices.isEmpty()) {
-             mInCallServices.entrySet().stream()
+            mInCallServices.entrySet().stream()
                     .filter((entry) -> entry.getKey().equals(mInCallServiceConnection.getInfo()))
                     .forEach((entry) -> {
                         try {
@@ -1268,7 +1508,13 @@
     /**
      * Unbinds an existing bound connection to the in-call app.
      */
-    private void unbindFromServices() {
+    public void unbindFromServices() {
+        try {
+            mContext.unregisterReceiver(mPackageChangedReceiver);
+        } catch (IllegalArgumentException e) {
+            // Ignore this -- we may or may not have registered it, but when we bind, we want to
+            // unregister no matter what.
+        }
         if (mInCallServiceConnection != null) {
             mInCallServiceConnection.disconnect();
             mInCallServiceConnection = null;
@@ -1282,7 +1528,8 @@
 
     /**
      * Binds to all the UI-providing InCallService as well as system-implemented non-UI
-     * InCallServices. Method-invoker must check {@link #isBoundAndConnectedToServices()} before invoking.
+     * InCallServices. Method-invoker must check {@link #isBoundAndConnectedToServices()}
+     * before invoking.
      *
      * @param call The newly added call that triggered the binding to the in-call services.
      */
@@ -1319,11 +1566,12 @@
 
         mInCallServiceConnection.chooseInitialInCallService(shouldUseCarModeUI());
 
-        // Actually try binding to the UI InCallService.  If the response
+        // Actually try binding to the UI InCallService.
         if (mInCallServiceConnection.connect(call) ==
-                InCallServiceConnection.CONNECTION_SUCCEEDED) {
+                InCallServiceConnection.CONNECTION_SUCCEEDED || call.isSelfManaged()) {
             // Only connect to the non-ui InCallServices if we actually connected to the main UI
-            // one.
+            // one, or if the call is self-managed (in which case we'd still want to keep Wear, BT,
+            // etc. informed.
             connectToNonUiInCallServices(call);
             mBindingFuture = new CompletableFuture<Boolean>().completeOnTimeout(false,
                     mTimeoutsAdapter.getCallRemoveUnbindInCallServicesDelay(
@@ -1332,9 +1580,13 @@
         } else {
             Log.i(this, "bindToServices: current UI doesn't support call; not binding.");
         }
+
+        IntentFilter packageChangedFilter = new IntentFilter(Intent.ACTION_PACKAGE_CHANGED);
+        packageChangedFilter.addDataScheme("package");
+        mContext.registerReceiver(mPackageChangedReceiver, packageChangedFilter);
     }
 
-    private void connectToNonUiInCallServices(Call call) {
+    private void updateNonUiInCallServices() {
         List<InCallServiceInfo> nonUIInCallComponents =
                 getInCallServiceComponents(IN_CALL_SERVICE_TYPE_NON_UI);
         List<InCallServiceBindingConnection> nonUIInCalls = new LinkedList<>();
@@ -1344,15 +1596,22 @@
         List<String> callCompanionApps = mCallsManager
                 .getRoleManagerAdapter().getCallCompanionApps();
         if (callCompanionApps != null && !callCompanionApps.isEmpty()) {
-            for(String pkg : callCompanionApps) {
+            for (String pkg : callCompanionApps) {
                 InCallServiceInfo info = getInCallServiceComponent(pkg,
-                        IN_CALL_SERVICE_TYPE_COMPANION);
+                        IN_CALL_SERVICE_TYPE_COMPANION, true /* ignoreDisabled */);
                 if (info != null) {
                     nonUIInCalls.add(new InCallServiceBindingConnection(info));
                 }
             }
         }
-        mNonUIInCallServiceConnections = new NonUIInCallServiceConnectionCollection(nonUIInCalls);
+        mNonUIInCallServiceConnections = new NonUIInCallServiceConnectionCollection(
+                nonUIInCalls);
+    }
+
+    private void connectToNonUiInCallServices(Call call) {
+        if (mNonUIInCallServiceConnections == null) {
+            updateNonUiInCallServices();
+        }
         mNonUIInCallServiceConnections.connect(call);
     }
 
@@ -1364,8 +1623,10 @@
 
         InCallServiceInfo defaultDialerComponent =
                 (systemPackageName != null && systemPackageName.equals(packageName))
-                ? getInCallServiceComponent(packageName, IN_CALL_SERVICE_TYPE_SYSTEM_UI)
-                : getInCallServiceComponent(packageName, IN_CALL_SERVICE_TYPE_DIALER_UI);
+                        ? getInCallServiceComponent(packageName, IN_CALL_SERVICE_TYPE_SYSTEM_UI,
+                        true /* ignoreDisabled */)
+                        : getInCallServiceComponent(packageName,
+                                IN_CALL_SERVICE_TYPE_DEFAULT_DIALER_UI, true /* ignoreDisabled */);
         /* TODO: in Android 12 re-enable this an InCallService is required by the dialer role.
             if (packageName != null && defaultDialerComponent == null) {
                 // The in call service of default phone app is disabled, send notification.
@@ -1377,7 +1638,7 @@
 
     private InCallServiceInfo getCurrentCarModeComponent() {
         return getInCallServiceComponent(mCarModeTracker.getCurrentCarModePackage(),
-                IN_CALL_SERVICE_TYPE_CAR_MODE_UI);
+                IN_CALL_SERVICE_TYPE_CAR_MODE_UI, true /* ignoreDisabled */);
     }
 
     private InCallServiceInfo getInCallServiceComponent(ComponentName componentName, int type) {
@@ -1387,13 +1648,15 @@
         } else {
             // Last Resort: Try to bind to the ComponentName given directly.
             Log.e(this, new Exception(), "Package Manager could not find ComponentName: "
-                    + componentName +". Trying to bind anyway.");
+                    + componentName + ". Trying to bind anyway.");
             return new InCallServiceInfo(componentName, false, false, type);
         }
     }
 
-    private InCallServiceInfo getInCallServiceComponent(String packageName, int type) {
-        List<InCallServiceInfo> list = getInCallServiceComponents(packageName, type);
+    private InCallServiceInfo getInCallServiceComponent(String packageName, int type,
+            boolean ignoreDisabled) {
+        List<InCallServiceInfo> list = getInCallServiceComponents(packageName, type,
+                ignoreDisabled);
         if (list != null && !list.isEmpty()) {
             return list.get(0);
         }
@@ -1404,8 +1667,9 @@
         return getInCallServiceComponents(null, null, type);
     }
 
-    private List<InCallServiceInfo> getInCallServiceComponents(String packageName, int type) {
-        return getInCallServiceComponents(packageName, null, type);
+    private List<InCallServiceInfo> getInCallServiceComponents(String packageName, int type,
+            boolean ignoreDisabled) {
+        return getInCallServiceComponents(packageName, null, type, ignoreDisabled);
     }
 
     private List<InCallServiceInfo> getInCallServiceComponents(ComponentName componentName,
@@ -1415,7 +1679,12 @@
 
     private List<InCallServiceInfo> getInCallServiceComponents(String packageName,
             ComponentName componentName, int requestedType) {
+        return getInCallServiceComponents(packageName, componentName, requestedType,
+                true /* ignoreDisabled */);
+    }
 
+    private List<InCallServiceInfo> getInCallServiceComponents(String packageName,
+            ComponentName componentName, int requestedType, boolean ignoreDisabled) {
         List<InCallServiceInfo> retval = new LinkedList<>();
 
         Intent serviceIntent = new Intent(InCallService.SERVICE_INTERFACE);
@@ -1429,7 +1698,7 @@
         PackageManager packageManager = mContext.getPackageManager();
         for (ResolveInfo entry : packageManager.queryIntentServicesAsUser(
                 serviceIntent,
-                PackageManager.GET_META_DATA,
+                PackageManager.GET_META_DATA | PackageManager.MATCH_DISABLED_COMPONENTS,
                 mCallsManager.getCurrentUserHandle().getIdentifier())) {
             ServiceInfo serviceInfo = entry.serviceInfo;
             if (serviceInfo != null) {
@@ -1442,22 +1711,45 @@
 
                 int currentType = getInCallServiceType(entry.serviceInfo, packageManager,
                         packageName);
-                if (requestedType == 0 || requestedType == currentType) {
-                    if (requestedType == IN_CALL_SERVICE_TYPE_NON_UI) {
-                        // We enforce the rule that self-managed calls are not supported by non-ui
-                        // InCallServices.
-                        isSelfManageCallsSupported = false;
-                    }
-                    retval.add(new InCallServiceInfo(
-                            new ComponentName(serviceInfo.packageName, serviceInfo.name),
-                            isExternalCallsSupported, isSelfManageCallsSupported, requestedType));
+                ComponentName foundComponentName =
+                        new ComponentName(serviceInfo.packageName, serviceInfo.name);
+                if (requestedType == IN_CALL_SERVICE_TYPE_NON_UI) {
+                    mKnownNonUiInCallServices.add(foundComponentName);
+                }
+
+                boolean isEnabled = isServiceEnabled(foundComponentName,
+                        serviceInfo, packageManager);
+                boolean isRequestedType;
+                if (requestedType == IN_CALL_SERVICE_TYPE_INVALID) {
+                    isRequestedType = true;
+                } else {
+                    isRequestedType = requestedType == currentType;
+                }
+
+                if ((!ignoreDisabled || isEnabled) && isRequestedType) {
+                    retval.add(new InCallServiceInfo(foundComponentName, isExternalCallsSupported,
+                            isSelfManageCallsSupported, requestedType));
                 }
             }
         }
-
         return retval;
     }
 
+    private boolean isServiceEnabled(ComponentName componentName,
+            ServiceInfo serviceInfo, PackageManager packageManager) {
+        int componentEnabledState = packageManager.getComponentEnabledSetting(componentName);
+
+        if (componentEnabledState == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
+            return true;
+        }
+
+        if (componentEnabledState == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) {
+            return serviceInfo.isEnabled();
+        }
+
+        return false;
+    }
+
     private boolean shouldUseCarModeUI() {
         return mCarModeTracker.isInCarMode();
     }
@@ -1500,9 +1792,17 @@
                 p -> packageManager.checkPermission(
                         Manifest.permission.CONTROL_INCALL_EXPERIENCE,
                         p) == PackageManager.PERMISSION_GRANTED);
+
+        boolean hasAppOpsPermittedManageOngoingCalls = false;
+        if (isAppOpsPermittedManageOngoingCalls(serviceInfo.applicationInfo.uid,
+                serviceInfo.packageName)) {
+            hasAppOpsPermittedManageOngoingCalls = true;
+        }
+
         boolean isCarModeUIService = serviceInfo.metaData != null &&
                 serviceInfo.metaData.getBoolean(
                         TelecomManager.METADATA_IN_CALL_SERVICE_CAR_MODE_UI, false);
+
         if (isCarModeUIService && hasControlInCallPermission) {
             return IN_CALL_SERVICE_TYPE_CAR_MODE_UI;
         }
@@ -1512,12 +1812,13 @@
                 mDefaultDialerCache.getDefaultDialerApplication(
                     mCallsManager.getCurrentUserHandle().getIdentifier()));
         if (isDefaultDialerPackage && isUIService) {
-            return IN_CALL_SERVICE_TYPE_DIALER_UI;
+            return IN_CALL_SERVICE_TYPE_DEFAULT_DIALER_UI;
         }
 
         // Also allow any in-call service that has the control-experience permission (to ensure
         // that it is a system app) and doesn't claim to show any UI.
-        if (!isUIService && !isCarModeUIService && hasControlInCallPermission) {
+        if (!isUIService && !isCarModeUIService && (hasControlInCallPermission ||
+                hasAppOpsPermittedManageOngoingCalls)) {
             return IN_CALL_SERVICE_TYPE_NON_UI;
         }
 
@@ -1550,7 +1851,7 @@
 
         if (info.getType() == IN_CALL_SERVICE_TYPE_CAR_MODE_UI
                 || info.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI
-                || info.getType() == IN_CALL_SERVICE_TYPE_DIALER_UI) {
+                || info.getType() == IN_CALL_SERVICE_TYPE_DEFAULT_DIALER_UI) {
             trackCallingUserInterfaceStarted(info);
         }
         IInCallService inCallService = IInCallService.Stub.asInterface(service);
@@ -1575,28 +1876,7 @@
                 "calls", calls.size(), info.getComponentName());
         int numCallsSent = 0;
         for (Call call : calls) {
-            try {
-                if ((call.isSelfManaged() && !info.isSelfManagedCallsSupported()) ||
-                        (call.isExternalCall() && !info.isExternalCallsSupported())) {
-                    continue;
-                }
-
-                // Only send the RTT call if it's a UI in-call service
-                boolean includeRttCall = info.equals(mInCallServiceConnection.getInfo());
-
-                // Track the call if we don't already know about it.
-                addCall(call);
-                numCallsSent += 1;
-                ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(
-                        call,
-                        true /* includeVideoProvider */,
-                        mCallsManager.getPhoneAccountRegistrar(),
-                        info.isExternalCallsSupported(),
-                        includeRttCall,
-                        info.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI);
-                inCallService.addCall(sanitizeParcelableCallForService(info, parcelableCall));
-            } catch (RemoteException ignored) {
-            }
+            numCallsSent += sendCallToService(call, info, inCallService);
         }
         try {
             inCallService.onCallAudioStateChanged(mCallsManager.getAudioState());
@@ -1604,7 +1884,7 @@
         } catch (RemoteException ignored) {
         }
         // Don't complete the binding future for non-ui incalls
-        if (info.getType() != IN_CALL_SERVICE_TYPE_NON_UI) {
+        if (info.getType() != IN_CALL_SERVICE_TYPE_NON_UI && !mBindingFuture.isDone()) {
             mBindingFuture.complete(true);
         }
 
@@ -1612,6 +1892,39 @@
         return true;
     }
 
+    private int sendCallToService(Call call, InCallServiceInfo info,
+            IInCallService inCallService) {
+        try {
+            if ((call.isSelfManaged() && (!info.isSelfManagedCallsSupported()
+                    || !call.visibleToInCallService())) ||
+                    (call.isExternalCall() && !info.isExternalCallsSupported())) {
+                return 0;
+            }
+
+            // Only send the RTT call if it's a UI in-call service
+            boolean includeRttCall = false;
+            if (mInCallServiceConnection != null) {
+                includeRttCall = info.equals(mInCallServiceConnection.getInfo());
+            }
+
+            // Track the call if we don't already know about it.
+            addCall(call);
+            ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(
+                    call,
+                    true /* includeVideoProvider */,
+                    mCallsManager.getPhoneAccountRegistrar(),
+                    info.isExternalCallsSupported(),
+                    includeRttCall,
+                    info.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI ||
+                            info.getType() == IN_CALL_SERVICE_TYPE_NON_UI);
+            inCallService.addCall(sanitizeParcelableCallForService(info, parcelableCall));
+            updateCallTracking(call, info, true /* isAdd */);
+            return 1;
+        } catch (RemoteException ignored) {
+        }
+        return 0;
+    }
+
     /**
      * Cleans up an instance of in-call app after the service has been unbound.
      *
@@ -1621,7 +1934,7 @@
         Log.i(this, "onDisconnected from %s", disconnectedInfo.getComponentName());
         if (disconnectedInfo.getType() == IN_CALL_SERVICE_TYPE_CAR_MODE_UI
                 || disconnectedInfo.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI
-                || disconnectedInfo.getType() == IN_CALL_SERVICE_TYPE_DIALER_UI) {
+                || disconnectedInfo.getType() == IN_CALL_SERVICE_TYPE_DEFAULT_DIALER_UI) {
             trackCallingUserInterfaceStopped(disconnectedInfo);
         }
         mInCallServices.remove(disconnectedInfo);
@@ -1655,7 +1968,8 @@
                     continue;
                 }
 
-                if (call.isSelfManaged() && !info.isSelfManagedCallsSupported()) {
+                if (call.isSelfManaged() && (!call.visibleToInCallService()
+                        || !info.isSelfManagedCallsSupported())) {
                     continue;
                 }
 
@@ -1665,7 +1979,8 @@
                         mCallsManager.getPhoneAccountRegistrar(),
                         info.isExternalCallsSupported(),
                         rttInfoChanged && info.equals(mInCallServiceConnection.getInfo()),
-                        info.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI);
+                        info.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI ||
+                        info.getType() == IN_CALL_SERVICE_TYPE_NON_UI);
                 ComponentName componentName = info.getComponentName();
                 IInCallService inCallService = entry.getValue();
                 componentsUpdated.add(componentName);
@@ -1685,10 +2000,18 @@
      * @param call The call to add.
      */
     private void addCall(Call call) {
+        if (mCallIdMapper.getCalls().size() == 0) {
+            mAppOpsManager.startWatchingActive(new String[] { OPSTR_RECORD_AUDIO },
+                    java.lang.Runnable::run, this);
+            updateAllCarrierPrivileged();
+            updateAllCarrierPrivilegedUsingMic();
+        }
+
         if (mCallIdMapper.getCallId(call) == null) {
             mCallIdMapper.addCall(call);
             call.addListener(mCallListener);
         }
+
         maybeTrackMicrophoneUse(isMuted());
     }
 
@@ -1735,7 +2058,7 @@
      */
     private ComponentName getConnectedUi() {
         InCallServiceInfo connectedUi = mInCallServices.keySet().stream().filter(
-                i -> i.getType() == IN_CALL_SERVICE_TYPE_DIALER_UI
+                i -> i.getType() == IN_CALL_SERVICE_TYPE_DEFAULT_DIALER_UI
                         || i.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI)
                 .findAny()
                 .orElse(null);
@@ -1757,7 +2080,7 @@
 
         if (TextUtils.isEmpty(ringingPackage)) {
             // The current in-call UI returned nothing, so lets use the default dialer.
-            ringingPackage = mDefaultDialerCache.getDefaultDialerApplication(
+            ringingPackage = mDefaultDialerCache.getRoleManagerAdapter().getDefaultDialerApp(
                     mCallsManager.getCurrentUserHandle().getIdentifier());
             if (ringingPackage != null) {
                 Log.d(this, "doesConnectedDialerSupportRinging: notCurentlyConnectedPackage=%s",
@@ -1832,15 +2155,19 @@
      * {@code false} otherwise.
      */
     private boolean isCarModeInCallService(@NonNull String packageName) {
+        // Disabled InCallService should also be considered as a valid InCallService here so that
+        // it can be added to the CarModeTracker, in case it will be enabled in future.
         InCallServiceInfo info =
-                getInCallServiceComponent(packageName, IN_CALL_SERVICE_TYPE_CAR_MODE_UI);
+                getInCallServiceComponent(packageName, IN_CALL_SERVICE_TYPE_CAR_MODE_UI,
+                        false /* ignoreDisabled */);
         return info != null && info.getType() == IN_CALL_SERVICE_TYPE_CAR_MODE_UI;
     }
 
     public void handleCarModeChange(int priority, String packageName, boolean isCarMode) {
         Log.i(this, "handleCarModeChange: packageName=%s, priority=%d, isCarMode=%b",
                 packageName, priority, isCarMode);
-        if (!isCarModeInCallService(packageName)) {
+        // Don't ignore the signal if we are disabling car mode; package may be uninstalled.
+        if (isCarMode && !isCarModeInCallService(packageName)) {
             Log.i(this, "handleCarModeChange: not a valid InCallService; packageName=%s",
                     packageName);
             return;
@@ -1852,14 +2179,42 @@
             mCarModeTracker.handleExitCarMode(priority, packageName);
         }
 
+        updateCarModeForConnections();
+    }
+
+    public void handleSetAutomotiveProjection(@NonNull String packageName) {
+        Log.i(this, "handleSetAutomotiveProjection: packageName=%s", packageName);
+        if (!isCarModeInCallService(packageName)) {
+            Log.i(this, "handleSetAutomotiveProjection: not a valid InCallService: packageName=%s",
+                    packageName);
+            return;
+        }
+        mCarModeTracker.handleSetAutomotiveProjection(packageName);
+
+        updateCarModeForConnections();
+    }
+
+    public void handleReleaseAutomotiveProjection() {
+        Log.i(this, "handleReleaseAutomotiveProjection");
+        mCarModeTracker.handleReleaseAutomotiveProjection();
+
+        updateCarModeForConnections();
+    }
+
+    public void updateCarModeForConnections() {
+        Log.i(this, "updateCarModeForConnections: car mode apps: %s",
+                mCarModeTracker.getCarModeApps().stream().collect(Collectors.joining(", ")));
         if (mInCallServiceConnection != null) {
-            Log.i(this, "handleCarModeChange: car mode apps: %s",
-                    mCarModeTracker.getCarModeApps().stream().collect(Collectors.joining(", ")));
             if (shouldUseCarModeUI()) {
+                Log.i(this, "updateCarModeForConnections: potentially update car mode app.");
                 mInCallServiceConnection.changeCarModeApp(
                         mCarModeTracker.getCurrentCarModePackage());
             } else {
-                mInCallServiceConnection.disableCarMode();
+                if (mInCallServiceConnection.isCarMode()) {
+                    Log.i(this, "updateCarModeForConnections: car mode no longer "
+                            + "applicable; disabling");
+                    mInCallServiceConnection.disableCarMode();
+                }
             }
         }
     }
@@ -1888,18 +2243,42 @@
         Log.i(this, "trackCallingUserInterfaceStopped: %s is no longer calling UX", packageName);
     }
 
+    private void maybeTrackMicrophoneUse(boolean isMuted) {
+        maybeTrackMicrophoneUse(isMuted, false);
+    }
+
     /**
      * As calls are added, removed and change between external and non-external status, track
      * whether the current active calling UX is using the microphone.  We assume if there is a
      * managed call present and the mic is not muted that the microphone is in use.
      */
-    private void maybeTrackMicrophoneUse(boolean isMuted) {
-        boolean wasTrackingManagedCall = mIsCallUsingMicrophone;
-        mIsCallUsingMicrophone = isTrackingManagedAliveCall() && !isMuted;
-        if (wasTrackingManagedCall != mIsCallUsingMicrophone) {
+    private void maybeTrackMicrophoneUse(boolean isMuted, boolean isScheduledDelay) {
+        if (mIsStartCallDelayScheduled && !isScheduledDelay) {
+            return;
+        }
+
+        mIsStartCallDelayScheduled = false;
+        boolean wasUsingMicrophone = mIsCallUsingMicrophone;
+        boolean wasTrackingCall = mIsTrackingManagedAliveCall;
+        mIsTrackingManagedAliveCall = isTrackingManagedAliveCall();
+        if (!wasTrackingCall && mIsTrackingManagedAliveCall) {
+            mIsStartCallDelayScheduled = true;
+            mHandler.postDelayed(new Runnable("ICC.mTMU", mLock) {
+                @Override
+                public void loggedRun() {
+                    maybeTrackMicrophoneUse(isMuted(), true);
+                }
+            }.prepare(), mTimeoutsAdapter.getCallStartAppOpDebounceIntervalMillis());
+            return;
+        }
+
+        mIsCallUsingMicrophone = mIsTrackingManagedAliveCall && !isMuted
+                && !isCarrierPrivilegedUsingMicDuringVoipCall();
+        if (wasUsingMicrophone != mIsCallUsingMicrophone) {
             if (mIsCallUsingMicrophone) {
                 mAppOpsManager.startOp(AppOpsManager.OP_PHONE_CALL_MICROPHONE, myUid(),
                         mContext.getOpPackageName(), false, null, null);
+                mSensorPrivacyManager.showSensorUseDialog(SensorPrivacyManager.Sensors.MICROPHONE);
             } else {
                 mAppOpsManager.finishOp(AppOpsManager.OP_PHONE_CALL_MICROPHONE, myUid(),
                         mContext.getOpPackageName(), null);
@@ -1913,8 +2292,13 @@
      */
     private boolean isTrackingManagedAliveCall() {
         return mCallIdMapper.getCalls().stream().anyMatch(c -> !c.isExternalCall()
-            && !c.isSelfManaged() && c.isAlive() && c.getState() != CallState.ON_HOLD
-                && c.getState() != CallState.AUDIO_PROCESSING);
+            && !c.isSelfManaged() && c.isAlive() && ArrayUtils.contains(LIVE_CALL_STATES,
+                c.getState()));
+    }
+
+    private boolean isCarrierPrivilegedUsingMicDuringVoipCall() {
+        return !mActiveCarrierPrivilegedApps.isEmpty() &&
+                mCallIdMapper.getCalls().stream().anyMatch(Call::getIsVoipAudioMode);
     }
 
     /**
@@ -1927,6 +2311,16 @@
         return mCallsManager.getAudioState().isMuted();
     }
 
+    private boolean isAppOpsPermittedManageOngoingCalls(int uid, String callingPackage) {
+        return PermissionChecker.checkPermissionForDataDeliveryFromDataSource(mContext,
+                Manifest.permission.MANAGE_ONGOING_CALLS, PermissionChecker.PID_UNKNOWN,
+                        new AttributionSource(mContext.getAttributionSource(),
+                                new AttributionSource(uid, callingPackage,
+                                        /*attributionTag*/ null)), "Checking whether the app has"
+                                                + " MANAGE_ONGOING_CALLS permission")
+                                                        == PermissionChecker.PERMISSION_GRANTED;
+    }
+
     private void sendCrashedInCallServiceNotification(String packageName) {
         PackageManager packageManager = mContext.getPackageManager();
         CharSequence appName;
@@ -1958,4 +2352,11 @@
         notificationManager.notify(NOTIFICATION_TAG, IN_CALL_SERVICE_NOTIFICATION_ID,
                 builder.build());
     }
+
+    private void updateCallTracking(Call call, InCallServiceInfo info, boolean isAdd) {
+        int type = info.getType();
+        boolean hasUi = type == IN_CALL_SERVICE_TYPE_CAR_MODE_UI
+                || type == IN_CALL_SERVICE_TYPE_DEFAULT_DIALER_UI;
+        call.maybeOnInCallServiceTrackingChanged(isAdd, hasUi);
+    }
 }
diff --git a/src/com/android/server/telecom/InCallTonePlayer.java b/src/com/android/server/telecom/InCallTonePlayer.java
index 4f0cf8d..524d119 100644
--- a/src/com/android/server/telecom/InCallTonePlayer.java
+++ b/src/com/android/server/telecom/InCallTonePlayer.java
@@ -162,6 +162,8 @@
     public static final int TONE_UNOBTAINABLE_NUMBER = 12;
     public static final int TONE_VOICE_PRIVACY = 13;
     public static final int TONE_VIDEO_UPGRADE = 14;
+    public static final int TONE_RTT_REQUEST = 15;
+    public static final int TONE_IN_CALL_QUALITY_NOTIFICATION = 16;
 
     private static final int TONE_RESOURCE_ID_UNDEFINED = -1;
 
@@ -329,12 +331,22 @@
                     // TODO: fill in.
                     throw new IllegalStateException("Voice privacy tone NYI.");
                 case TONE_VIDEO_UPGRADE:
+                case TONE_RTT_REQUEST:
                     // Similar to the call waiting tone, but does not repeat.
                     toneType = ToneGenerator.TONE_SUP_CALL_WAITING;
                     toneVolume = RELATIVE_VOLUME_HIPRI;
                     toneLengthMillis = 4000;
                     mediaResourceId = TONE_RESOURCE_ID_UNDEFINED;
                     break;
+                case TONE_IN_CALL_QUALITY_NOTIFICATION:
+                    // 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.InCallQualityNotification;
+                    break;
                 default:
                     throw new IllegalStateException("Bad toneId: " + mToneId);
             }
@@ -427,7 +439,7 @@
                 @Override
                 public void onCompletion(MediaPlayer mp) {
                     Log.i(this, "playMediaTone: toneResourceId=%d completed.", toneResourceId);
-                    synchronized (this) {
+                    synchronized (InCallTonePlayer.this) {
                         mState = STATE_OFF;
                     }
                     mToneMediaPlayer.release();
diff --git a/src/com/android/server/telecom/LogUtils.java b/src/com/android/server/telecom/LogUtils.java
index 5bb14e6..f53f239 100644
--- a/src/com/android/server/telecom/LogUtils.java
+++ b/src/com/android/server/telecom/LogUtils.java
@@ -101,6 +101,8 @@
         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";
+        public static final String SET_AUDIO_PROCESSING = "SET_AUDIO_PROCESSING";
+        public static final String SET_SIMULATED_RINGING = "SET_SIMULATED_RINGING";
         public static final String REQUEST_HOLD = "REQUEST_HOLD";
         public static final String REQUEST_UNHOLD = "REQUEST_UNHOLD";
         public static final String REQUEST_DISCONNECT = "REQUEST_DISCONNECT";
@@ -136,6 +138,7 @@
         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 CONF_CALLS_CHANGED = "CONF_CALLS_CHANGED";
         public static final String CALL_DIRECTION_CHANGED = "CALL_DIRECTION_CHANGED";
         public static final String MUTE = "MUTE";
         public static final String UNMUTE = "UNMUTE";
@@ -194,6 +197,12 @@
         public static final String REDIRECTION_USER_CONFIRMATION = "REDIRECTION_USER_CONFIRMATION";
         public static final String REDIRECTION_USER_CONFIRMED = "REDIRECTION_USER_CONFIRMED";
         public static final String REDIRECTION_USER_CANCELLED = "REDIRECTION_USER_CANCELLED";
+        public static final String BT_QUALITY_REPORT = "BT_QUALITY_REPORT";
+        public static final String SET_DISCONNECTED_ORIG = "SET_DISCONNECTED_ORIG";
+        public static final String OVERRIDE_DISCONNECT_MESSAGE = "OVERRIDE_DISCONNECT_MSG";
+        public static final String CALL_DIAGNOSTIC_SERVICE_TIMEOUT =
+                "CALL_DIAGNOSTIC_SERVICE_TIMEOUT";
+        public static final String VERSTAT_CHANGED = "VERSTAT_CHANGED";
 
         public static class Timings {
             public static final String ACCEPT_TIMING = "accept";
diff --git a/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java b/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
index f0efe92..86fedd5 100644
--- a/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
+++ b/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
@@ -21,7 +21,6 @@
 import android.app.Activity;
 import android.app.BroadcastOptions;
 import android.content.BroadcastReceiver;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources;
@@ -495,9 +494,7 @@
 
     private void launchSystemDialer(Uri handle) {
         Intent systemDialerIntent = new Intent();
-        systemDialerIntent.setComponent(
-                new ComponentName(mDefaultDialerCache.getSystemDialerApplication(),
-                    mContext.getResources().getString(R.string.dialer_default_class)));
+        systemDialerIntent.setComponent(mDefaultDialerCache.getDialtactsSystemDialerComponent());
         systemDialerIntent.setAction(Intent.ACTION_DIAL);
         systemDialerIntent.setData(handle);
         systemDialerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
diff --git a/src/com/android/server/telecom/ParcelableCallUtils.java b/src/com/android/server/telecom/ParcelableCallUtils.java
index 5c821a4..f500828 100644
--- a/src/com/android/server/telecom/ParcelableCallUtils.java
+++ b/src/com/android/server/telecom/ParcelableCallUtils.java
@@ -27,6 +27,7 @@
 import android.telecom.ParcelableCall;
 import android.telecom.ParcelableRttCall;
 import android.telecom.TelecomManager;
+import android.telephony.ims.ImsCallProfile;
 import android.text.TextUtils;
 
 import java.util.ArrayList;
@@ -42,9 +43,9 @@
 
     /**
      * 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.
+     * generated for the purpose of sending to a incallservice other than the system incallservice.
      * 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.
+     * should not be passed to non-system incallservice apps either.
      */
     private static List<String> EXTRA_KEYS_TO_SANITIZE;
     static {
@@ -61,6 +62,7 @@
     static {
         RESTRICTED_CALL_SCREENING_EXTRA_KEYS = new ArrayList<>();
         RESTRICTED_CALL_SCREENING_EXTRA_KEYS.add(android.telecom.Connection.EXTRA_SIP_INVITE);
+        RESTRICTED_CALL_SCREENING_EXTRA_KEYS.add(ImsCallProfile.EXTRA_IS_BUSINESS_CALL);
     }
 
     public static class Converter {
@@ -90,9 +92,9 @@
      *      {@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
+     * @param isForSystemInCallService {@code true} if this call is being parcelled for the system incallservice,
+     *      {@code false} otherwise.  When parceling for the system incallservice, the entire call extras
+     *      is included.  When parceling for anything other than the system incallservice, some extra key
      *      values will be stripped for privacy sake.
      */
     public static ParcelableCall toParcelableCall(
@@ -101,10 +103,10 @@
             PhoneAccountRegistrar phoneAccountRegistrar,
             boolean supportsExternalCalls,
             boolean includeRttCall,
-            boolean isForSystemDialer) {
+            boolean isForSystemInCallService) {
         return toParcelableCall(call, includeVideoProvider, phoneAccountRegistrar,
                 supportsExternalCalls, CALL_STATE_OVERRIDE_NONE /* overrideState */,
-                includeRttCall, isForSystemDialer);
+                includeRttCall, isForSystemInCallService);
     }
 
     /**
@@ -120,9 +122,9 @@
      *      {@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
+     * @param isForSystemInCallService {@code true} if this call is being parcelled for the system incallservice,
+     *      {@code false} otherwise.  When parceling for the system incallservice, the entire call extras
+     *      is included.  When parceling for anything other than the system incallservice, some extra key
      *      values will be stripped for privacy sake.
      * @return The {@link ParcelableCall} containing all call information from the {@link Call}.
      */
@@ -133,7 +135,7 @@
             boolean supportsExternalCalls,
             int overrideState,
             boolean includeRttCall,
-            boolean isForSystemDialer) {
+            boolean isForSystemInCallService) {
         int state;
         if (overrideState == CALL_STATE_OVERRIDE_NONE) {
             state = getParcelableState(call, supportsExternalCalls);
@@ -216,7 +218,7 @@
         }
 
         Bundle extras;
-        if (isForSystemDialer) {
+        if (isForSystemInCallService) {
             extras = call.getExtras();
         } else {
             extras = sanitizeExtras(call.getExtras());
@@ -269,9 +271,9 @@
      *     <li>Caller number verification status (verstat)</li>
      * </ul>
      * All other fields are nulled or set to 0 values.
-     * Where the call screening service is part of the system dialer, the
+     * Where the call screening service is part of the system incallservice, the
      * {@link Connection#EXTRA_SIP_INVITE} header information is also sent to the call screening
-     * service (since the system dialer has access to this anyways).
+     * service (since the system incallservice has access to this anyways).
      * @param call The telecom call to send to a call screening service.
      * @param areRestrictedExtrasIncluded {@code true} if the set of restricted extras defined in
      *                                    {@link #RESTRICTED_CALL_SCREENING_EXTRA_KEYS} are to
@@ -333,7 +335,7 @@
 
     /**
      * Sanitize the extras bundle passed in, removing keys which should not be sent to non-system
-     * dialer apps.
+     * incallservice apps.
      * @param oldExtras Extras bundle to sanitize.
      * @return The sanitized extras bundle.
      */
@@ -554,7 +556,10 @@
         android.telecom.Call.Details.PROPERTY_NETWORK_IDENTIFIED_EMERGENCY_CALL,
 
         Connection.PROPERTY_IS_ADHOC_CONFERENCE,
-        android.telecom.Call.Details.PROPERTY_IS_ADHOC_CONFERENCE
+        android.telecom.Call.Details.PROPERTY_IS_ADHOC_CONFERENCE,
+
+        Connection.PROPERTY_CROSS_SIM,
+        android.telecom.Call.Details.PROPERTY_CROSS_SIM
     };
 
     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 ef2840a..5ef3dd5 100644
--- a/src/com/android/server/telecom/PhoneAccountRegistrar.java
+++ b/src/com/android/server/telecom/PhoneAccountRegistrar.java
@@ -18,6 +18,7 @@
 
 import android.Manifest;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -485,6 +486,7 @@
      * target phone account.
      * @return phone account handle of sim call manager based on the ongoing call.
      */
+    @Nullable
     public PhoneAccountHandle getSimCallManagerFromCall(Call call) {
         if (call == null) {
             return null;
@@ -1264,11 +1266,11 @@
             // Comparator which places PhoneAccounts with a specified sort order first, followed by
             // those with no sort order.
             Comparator<PhoneAccount> bySortOrder = (p1, p2) -> {
-                String sort1 = p1.getExtras() == null ? null :
-                        p1.getExtras().getString(PhoneAccount.EXTRA_SORT_ORDER, null);
-                String sort2 = p2.getExtras() == null ? null :
-                        p2.getExtras().getString(PhoneAccount.EXTRA_SORT_ORDER, null);
-                return nullSafeStringComparator.compare(sort1, sort2);
+                int sort1 = p1.getExtras() == null ? Integer.MAX_VALUE:
+                        p1.getExtras().getInt(PhoneAccount.EXTRA_SORT_ORDER, Integer.MAX_VALUE);
+                int sort2 = p2.getExtras() == null ? Integer.MAX_VALUE:
+                        p2.getExtras().getInt(PhoneAccount.EXTRA_SORT_ORDER, Integer.MAX_VALUE);
+                return Integer.compare(sort1, sort2);
             };
 
             // Comparator which sorts PhoneAccounts by label.
@@ -1597,10 +1599,16 @@
             return BitmapFactory.decodeByteArray(imageByteArray, 0, imageByteArray.length);
         }
 
+        @Nullable
         protected Icon readIcon(XmlPullParser parser) throws IOException {
-            byte[] iconByteArray = Base64.decode(parser.getText(), 0);
-            ByteArrayInputStream stream = new ByteArrayInputStream(iconByteArray);
-            return Icon.createFromStream(stream);
+            try {
+                byte[] iconByteArray = Base64.decode(parser.getText(), 0);
+                ByteArrayInputStream stream = new ByteArrayInputStream(iconByteArray);
+                return Icon.createFromStream(stream);
+            } catch (IllegalArgumentException e) {
+                Log.e(this, e, "Bitmap must not be null.");
+                return null;
+            }
         }
     }
 
@@ -1985,8 +1993,8 @@
          * @return {@code True} if SIP should be used for all calls.
          */
         private boolean useSipForPstnCalls(Context context) {
-            String option = Settings.System.getString(context.getContentResolver(),
-                    Settings.System.SIP_CALL_OPTIONS);
+            String option = Settings.System.getStringForUser(context.getContentResolver(),
+                    Settings.System.SIP_CALL_OPTIONS, context.getUserId());
             option = (option != null) ? option : Settings.System.SIP_ADDRESS_ONLY;
             return option.equals(Settings.System.SIP_ALWAYS);
         }
diff --git a/src/com/android/server/telecom/PhoneStateBroadcaster.java b/src/com/android/server/telecom/PhoneStateBroadcaster.java
index f2531ed..490db85 100644
--- a/src/com/android/server/telecom/PhoneStateBroadcaster.java
+++ b/src/com/android/server/telecom/PhoneStateBroadcaster.java
@@ -17,8 +17,16 @@
 package com.android.server.telecom;
 
 import android.telecom.Log;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.telephony.TelephonyRegistryManager;
+import android.telephony.emergency.EmergencyNumber;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
 
 /**
  * Send a {@link TelephonyManager#ACTION_PHONE_STATE_CHANGED} broadcast when the call state
@@ -52,6 +60,10 @@
             return;
         }
         updateStates(call);
+
+        if (call.isEmergencyCall() && !call.isIncoming()) {
+            sendOutgoingEmergencyCallEvent(call);
+        }
     }
 
     @Override
@@ -113,4 +125,38 @@
             Log.i(this, "Broadcasted state change: %s", mCurrentState);
         }
     }
+
+    private void sendOutgoingEmergencyCallEvent(Call call) {
+        TelephonyManager tm = mCallsManager.getContext().getSystemService(TelephonyManager.class);
+        String strippedNumber =
+                PhoneNumberUtils.stripSeparators(call.getHandle().getSchemeSpecificPart());
+        Optional<EmergencyNumber> emergencyNumber;
+        try {
+            emergencyNumber = tm.getEmergencyNumberList().values().stream()
+                    .flatMap(List::stream)
+                    .filter(numberObj -> Objects.equals(numberObj.getNumber(), strippedNumber))
+                    .findFirst();
+        } catch (IllegalStateException ie) {
+            emergencyNumber = Optional.empty();
+        } catch (RuntimeException r) {
+            emergencyNumber = Optional.empty();
+        }
+
+        int subscriptionId = tm.getSubscriptionId(call.getTargetPhoneAccount());
+        SubscriptionManager subscriptionManager =
+                mCallsManager.getContext().getSystemService(SubscriptionManager.class);
+        int simSlotIndex = SubscriptionManager.DEFAULT_PHONE_INDEX;
+        if (subscriptionManager != null) {
+            SubscriptionInfo subInfo =
+                    subscriptionManager.getActiveSubscriptionInfo(subscriptionId);
+            if (subInfo != null) {
+                simSlotIndex = subInfo.getSimSlotIndex();
+            }
+        }
+
+        if (emergencyNumber.isPresent()) {
+            mRegistry.notifyOutgoingEmergencyCall(
+                    simSlotIndex, subscriptionId, emergencyNumber.get());
+        }
+    }
 }
diff --git a/src/com/android/server/telecom/RespondViaSmsManager.java b/src/com/android/server/telecom/RespondViaSmsManager.java
index 00d94f0..23ccc1c 100644
--- a/src/com/android/server/telecom/RespondViaSmsManager.java
+++ b/src/com/android/server/telecom/RespondViaSmsManager.java
@@ -204,7 +204,7 @@
             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);
+                        PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE);
                 sentIntents.add(pendingIntent);
             }
             MessageSentReceiver receiver = new MessageSentReceiver(
diff --git a/src/com/android/server/telecom/Ringer.java b/src/com/android/server/telecom/Ringer.java
index a769a94..91276de 100644
--- a/src/com/android/server/telecom/Ringer.java
+++ b/src/com/android/server/telecom/Ringer.java
@@ -20,26 +20,27 @@
 import android.app.NotificationManager;
 import android.app.Person;
 import android.content.Context;
-import android.os.VibrationEffect;
-import android.telecom.Log;
-import android.telecom.TelecomManager;
 import android.media.AudioAttributes;
 import android.media.AudioManager;
 import android.media.Ringtone;
 import android.media.VolumeShaper;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.VibrationEffect;
 import android.os.Vibrator;
+import android.telecom.Log;
+import android.telecom.TelecomManager;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.telecom.LogUtils.EventTimer;
 
 import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutionException;
-import java.util.stream.Collectors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
 
 /**
  * Controls the ringtone player.
@@ -114,6 +115,8 @@
 
     private static final int REPEAT_SIMPLE_VIBRATION_AT = 1;
 
+    private static final long RINGER_ATTRIBUTES_TIMEOUT = 5000; // 5 seconds
+
     private static final float EPSILON = 1e-6f;
 
     private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder()
@@ -147,6 +150,7 @@
 
     private InCallTonePlayer mCallWaitingPlayer;
     private RingtoneFactory mRingtoneFactory;
+    private AudioManager mAudioManager;
 
     /**
      * Call objects that are ringing, vibrating or call-waiting. These are used only for logging
@@ -161,6 +165,8 @@
      */
     private boolean mIsVibrating = false;
 
+    private Handler mHandler = null;
+
     /** Initializes the Ringer. */
     @VisibleForTesting
     public Ringer(
@@ -183,6 +189,7 @@
         mRingtoneFactory = ringtoneFactory;
         mInCallController = inCallController;
         mVibrationEffectProxy = vibrationEffectProxy;
+        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
 
         if (mContext.getResources().getBoolean(R.bool.use_simple_vibration_pattern)) {
             mDefaultVibrationEffect = mVibrationEffectProxy.createWaveform(SIMPLE_VIBRATION_PATTERN,
@@ -216,58 +223,39 @@
             return false;
         }
 
-        AudioManager audioManager =
-                (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
-        LogUtils.EventTimer timer = new EventTimer();
-        boolean isVolumeOverZero = audioManager.getStreamVolume(AudioManager.STREAM_RING) > 0;
-        timer.record("isVolumeOverZero");
-        boolean shouldRingForContact = shouldRingForContact(foregroundCall.getContactUri());
-        timer.record("shouldRingForContact");
-        boolean isRingtonePresent = !(mRingtoneFactory.getRingtone(foregroundCall) == null);
-        timer.record("getRingtone");
-        boolean isSelfManaged = foregroundCall.isSelfManaged();
-        timer.record("isSelfManaged");
-        boolean isSilentRingingRequested = foregroundCall.isSilentRingingRequested();
-        timer.record("isSilentRingRequested");
+        // Use completable future to establish a timeout, not intent to make these work outside the
+        // main thread asynchronously
+        // TODO: moving these RingerAttributes calculation out of Telecom lock to avoid blocking.
+        CompletableFuture<RingerAttributes> ringerAttributesFuture = CompletableFuture
+                .supplyAsync(() -> getRingerAttributes(foregroundCall, isHfpDeviceAttached),
+                        new LoggedHandlerExecutor(getHandler(), "R.sR", null));
 
-        boolean isRingerAudible = isVolumeOverZero && shouldRingForContact && isRingtonePresent;
-        timer.record("isRingerAudible");
-        boolean hasExternalRinger = hasExternalRinger(foregroundCall);
-        timer.record("hasExternalRinger");
-        // Don't do call waiting operations or vibration unless these are false.
-        boolean isTheaterModeOn = mSystemSettingsUtil.isTheaterModeOn(mContext);
-        timer.record("isTheaterModeOn");
-        boolean letDialerHandleRinging = mInCallController.doesConnectedDialerSupportRinging();
-        timer.record("letDialerHandleRinging");
+        RingerAttributes attributes = null;
+        try {
+            attributes = ringerAttributesFuture.get(
+                    RINGER_ATTRIBUTES_TIMEOUT, TimeUnit.MILLISECONDS);
+        } catch (ExecutionException | InterruptedException | TimeoutException e) {
+            // Keep attributs as null
+            Log.i(this, "getAttributes error: " + e);
+        }
 
-        Log.i(this, "startRinging timings: " + timer);
-        boolean endEarly = isTheaterModeOn || letDialerHandleRinging || isSelfManaged ||
-                hasExternalRinger || isSilentRingingRequested;
+        if (attributes == null) {
+            Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING, "RingerAttributes error");
+            return false;
+        }
 
-        // Acquire audio focus under any of the following conditions:
-        // 1. Should ring for contact and there's an HFP device attached
-        // 2. Volume is over zero, we should ring for the contact, and there's a audible ringtone
-        //    present.
-        // 3. The call is self-managed.
-        boolean shouldAcquireAudioFocus =
-                isRingerAudible || (isHfpDeviceAttached && shouldRingForContact) || isSelfManaged;
-
-        if (endEarly) {
-            if (letDialerHandleRinging) {
+        if (attributes.isEndEarly()) {
+            if (attributes.letDialerHandleRinging()) {
                 Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING, "Dialer handles");
             }
-            if (isSilentRingingRequested) {
+            if (attributes.isSilentRingingRequested()) {
                 Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING, "Silent ringing "
                         + "requested");
             }
-            Log.i(this, "Ending early -- isTheaterModeOn=%s, letDialerHandleRinging=%s, " +
-                            "isSelfManaged=%s, hasExternalRinger=%s, silentRingingRequested=%s",
-                    isTheaterModeOn, letDialerHandleRinging, isSelfManaged, hasExternalRinger,
-                    isSilentRingingRequested);
             if (mBlockOnRingingFuture != null) {
                 mBlockOnRingingFuture.complete(null);
             }
-            return shouldAcquireAudioFocus;
+            return attributes.shouldAcquireAudioFocus();
         }
 
         stopCallWaiting();
@@ -276,7 +264,7 @@
         CompletableFuture<Boolean> hapticsFuture = null;
         // Determine if the settings and DND mode indicate that the vibrator can be used right now.
         boolean isVibratorEnabled = isVibratorEnabled(mContext, foregroundCall);
-        if (isRingerAudible) {
+        if (attributes.isRingerAudible()) {
             mRingingCall = foregroundCall;
             Log.addEvent(foregroundCall, LogUtils.Events.START_RINGER);
             // Because we wait until a contact info query to complete before processing a
@@ -309,15 +297,14 @@
                 effect = getVibrationEffectForCall(mRingtoneFactory, foregroundCall);
             }
         } else {
-            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);
+            Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING, "Inaudible: "
+                    + attributes.getInaudibleReason());
             effect = mDefaultVibrationEffect;
         }
 
         if (hapticsFuture != null) {
+            final boolean shouldRingForContact = attributes.shouldRingForContact();
+            final boolean isRingerAudible = attributes.isRingerAudible();
             mVibrateFuture = hapticsFuture.thenAccept(isUsingAudioCoupledHaptics -> {
                 if (!isUsingAudioCoupledHaptics || !mIsHapticPlaybackSupportedByDevice) {
                     Log.i(this, "startRinging: fileHasHaptics=%b, hapticsSupported=%b",
@@ -342,11 +329,11 @@
                 mBlockOnRingingFuture.complete(null);
             }
             Log.w(this, "startRinging: No haptics future; fallback to default behavior");
-            maybeStartVibration(foregroundCall, shouldRingForContact, effect, isVibratorEnabled,
-                    isRingerAudible);
+            maybeStartVibration(foregroundCall, attributes.shouldRingForContact(), effect,
+                    isVibratorEnabled, attributes.isRingerAudible());
         }
 
-        return shouldAcquireAudioFocus;
+        return attributes.shouldAcquireAudioFocus();
     }
 
     private void maybeStartVibration(Call foregroundCall, boolean shouldRingForContact,
@@ -517,4 +504,75 @@
         return mSystemSettingsUtil.canVibrateWhenRinging(context)
             || mSystemSettingsUtil.applyRampingRinger(context);
     }
+
+    private RingerAttributes getRingerAttributes(Call call, boolean isHfpDeviceAttached) {
+        RingerAttributes.Builder builder = new RingerAttributes.Builder();
+
+        LogUtils.EventTimer timer = new EventTimer();
+
+        boolean isVolumeOverZero = mAudioManager.getStreamVolume(AudioManager.STREAM_RING) > 0;
+        timer.record("isVolumeOverZero");
+        boolean shouldRingForContact = shouldRingForContact(call.getHandle());
+        timer.record("shouldRingForContact");
+        boolean isRingtonePresent = !(mRingtoneFactory.getRingtone(call) == null);
+        timer.record("getRingtone");
+        boolean isSelfManaged = call.isSelfManaged();
+        timer.record("isSelfManaged");
+        boolean isSilentRingingRequested = call.isSilentRingingRequested();
+        timer.record("isSilentRingRequested");
+
+        boolean isRingerAudible = isVolumeOverZero && shouldRingForContact && isRingtonePresent;
+        timer.record("isRingerAudible");
+        String inaudibleReason = "";
+        if (!isRingerAudible) {
+            inaudibleReason = String.format(
+                    "isVolumeOverZero=%s, shouldRingForContact=%s, isRingtonePresent=%s",
+                    isVolumeOverZero, shouldRingForContact, isRingtonePresent);
+        }
+
+        boolean hasExternalRinger = hasExternalRinger(call);
+        timer.record("hasExternalRinger");
+        // Don't do call waiting operations or vibration unless these are false.
+        boolean isTheaterModeOn = mSystemSettingsUtil.isTheaterModeOn(mContext);
+        timer.record("isTheaterModeOn");
+        boolean letDialerHandleRinging = mInCallController.doesConnectedDialerSupportRinging();
+        timer.record("letDialerHandleRinging");
+
+        Log.i(this, "startRinging timings: " + timer);
+        boolean endEarly = isTheaterModeOn || letDialerHandleRinging || isSelfManaged ||
+                hasExternalRinger || isSilentRingingRequested;
+
+        if (endEarly) {
+            Log.i(this, "Ending early -- isTheaterModeOn=%s, letDialerHandleRinging=%s, " +
+                            "isSelfManaged=%s, hasExternalRinger=%s, silentRingingRequested=%s",
+                    isTheaterModeOn, letDialerHandleRinging, isSelfManaged, hasExternalRinger,
+                    isSilentRingingRequested);
+        }
+
+        // Acquire audio focus under any of the following conditions:
+        // 1. Should ring for contact and there's an HFP device attached
+        // 2. Volume is over zero, we should ring for the contact, and there's a audible ringtone
+        //    present.
+        // 3. The call is self-managed.
+        boolean shouldAcquireAudioFocus =
+                isRingerAudible || (isHfpDeviceAttached && shouldRingForContact) || isSelfManaged;
+
+        return builder.setEndEarly(endEarly)
+                .setLetDialerHandleRinging(letDialerHandleRinging)
+                .setAcquireAudioFocus(shouldAcquireAudioFocus)
+                .setRingerAudible(isRingerAudible)
+                .setInaudibleReason(inaudibleReason)
+                .setShouldRingForContact(shouldRingForContact)
+                .setSilentRingingRequested(isSilentRingingRequested)
+                .build();
+    }
+
+    private Handler getHandler() {
+        if (mHandler == null) {
+            HandlerThread handlerThread = new HandlerThread("Ringer");
+            handlerThread.start();
+            mHandler = handlerThread.getThreadHandler();
+        }
+        return mHandler;
+    }
 }
diff --git a/src/com/android/server/telecom/RingerAttributes.java b/src/com/android/server/telecom/RingerAttributes.java
new file mode 100644
index 0000000..840d815
--- /dev/null
+++ b/src/com/android/server/telecom/RingerAttributes.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2020 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;
+
+public class RingerAttributes {
+    public static class Builder {
+        private boolean mEndEarly;
+        private boolean mLetDialerHandleRinging;
+        private boolean mAcquireAudioFocus;
+        private boolean mRingerAudible;
+        private String mInaudibleReason;
+        private boolean mShouldRingForContact;
+        private boolean mSilentRingingRequested;
+
+        public RingerAttributes.Builder setEndEarly(boolean endEarly) {
+            mEndEarly = endEarly;
+            return this;
+        }
+
+        public RingerAttributes.Builder setLetDialerHandleRinging(boolean letDialerHandleRinging) {
+            mLetDialerHandleRinging = letDialerHandleRinging;
+            return this;
+        }
+
+        public RingerAttributes.Builder setAcquireAudioFocus(boolean acquireAudioFocus) {
+            mAcquireAudioFocus = acquireAudioFocus;
+            return this;
+        }
+
+        public RingerAttributes.Builder setRingerAudible(boolean ringerAudible) {
+            mRingerAudible = ringerAudible;
+            return this;
+        }
+
+        public RingerAttributes.Builder setInaudibleReason(String inaudibleReason) {
+            mInaudibleReason = inaudibleReason;
+            return this;
+        }
+
+        public RingerAttributes.Builder setShouldRingForContact(boolean shouldRingForContact) {
+            mShouldRingForContact = shouldRingForContact;
+            return this;
+        }
+
+        public RingerAttributes.Builder setSilentRingingRequested(boolean silentRingingRequested) {
+            mSilentRingingRequested = silentRingingRequested;
+            return this;
+        }
+
+        public RingerAttributes build() {
+            return new RingerAttributes(mEndEarly, mLetDialerHandleRinging, mAcquireAudioFocus,
+                    mRingerAudible, mInaudibleReason, mShouldRingForContact,
+                    mSilentRingingRequested);
+        }
+    }
+
+    private boolean mEndEarly;
+    private boolean mLetDialerHandleRinging;
+    private boolean mAcquireAudioFocus;
+    private boolean mRingerAudible;
+    private String mInaudibleReason;
+    private boolean mShouldRingForContact;
+    private boolean mSilentRingingRequested;
+
+    private RingerAttributes(boolean endEarly, boolean letDialerHandleRinging,
+            boolean acquireAudioFocus, boolean ringerAudible, String inaudibleReason,
+            boolean shouldRingForContact, boolean silentRingingRequested) {
+        mEndEarly = endEarly;
+        mLetDialerHandleRinging = letDialerHandleRinging;
+        mAcquireAudioFocus = acquireAudioFocus;
+        mRingerAudible = ringerAudible;
+        mInaudibleReason = inaudibleReason;
+        mShouldRingForContact = shouldRingForContact;
+        mSilentRingingRequested = silentRingingRequested;
+    }
+
+    public boolean isEndEarly() {
+        return mEndEarly;
+    }
+
+    public boolean letDialerHandleRinging() {
+        return mLetDialerHandleRinging;
+    }
+
+    public boolean shouldAcquireAudioFocus() {
+        return mAcquireAudioFocus;
+    }
+
+    public boolean isRingerAudible() {
+        return mRingerAudible;
+    }
+
+    public String getInaudibleReason() {
+        return mInaudibleReason;
+    }
+
+    public boolean shouldRingForContact() {
+        return mShouldRingForContact;
+    }
+
+    public boolean isSilentRingingRequested() {
+        return mSilentRingingRequested;
+    }
+}
diff --git a/src/com/android/server/telecom/RingtoneFactory.java b/src/com/android/server/telecom/RingtoneFactory.java
index d3d2d0e..df89ee7 100644
--- a/src/com/android/server/telecom/RingtoneFactory.java
+++ b/src/com/android/server/telecom/RingtoneFactory.java
@@ -75,7 +75,12 @@
 
         if(ringtoneUri != null && userContext != null) {
             // Ringtone URI is explicitly specified. First, try to create a Ringtone with that.
-            ringtone = RingtoneManager.getRingtone(userContext, ringtoneUri, volumeShaperConfig);
+            try {
+                ringtone = RingtoneManager.getRingtone(userContext, ringtoneUri,
+                        volumeShaperConfig);
+            } catch (NullPointerException npe) {
+                Log.e(this, npe, "getRingtone: NPE while getting ringtone.");
+            }
         }
         if(ringtone == null) {
             // Contact didn't specify ringtone or custom Ringtone creation failed. Get default
@@ -91,8 +96,12 @@
             if (defaultRingtoneUri == null) {
                 return null;
             }
-            ringtone = RingtoneManager.getRingtone(
-                contextToUse, defaultRingtoneUri, volumeShaperConfig);
+            try {
+                ringtone = RingtoneManager.getRingtone(
+                        contextToUse, defaultRingtoneUri, volumeShaperConfig);
+            } catch (NullPointerException npe) {
+                Log.e(this, npe, "getRingtone: NPE while getting ringtone.");
+            }
         }
         if (ringtone != null) {
             ringtone.setAudioAttributes(new AudioAttributes.Builder()
diff --git a/src/com/android/server/telecom/RoleManagerAdapterImpl.java b/src/com/android/server/telecom/RoleManagerAdapterImpl.java
index eb216d0..4a98d7b 100644
--- a/src/com/android/server/telecom/RoleManagerAdapterImpl.java
+++ b/src/com/android/server/telecom/RoleManagerAdapterImpl.java
@@ -85,8 +85,11 @@
 
     @Override
     public void observeDefaultDialerApp(Executor executor, IntConsumer observer) {
-        mRoleManager.addOnRoleHoldersChangedListenerAsUser(executor, (roleName, user) ->
-                observer.accept(user.getIdentifier()), UserHandle.ALL);
+        mRoleManager.addOnRoleHoldersChangedListenerAsUser(executor, (roleName, user) -> {
+                    if (ROLE_DIALER.equals(roleName)) {
+                        observer.accept(user.getIdentifier());
+                    }
+                }, UserHandle.ALL);
     }
 
     @Override
diff --git a/src/com/android/server/telecom/SystemSettingsUtil.java b/src/com/android/server/telecom/SystemSettingsUtil.java
index f104f27..7baae05 100644
--- a/src/com/android/server/telecom/SystemSettingsUtil.java
+++ b/src/com/android/server/telecom/SystemSettingsUtil.java
@@ -40,18 +40,19 @@
     }
 
     public boolean canVibrateWhenRinging(Context context) {
-        return Settings.System.getInt(context.getContentResolver(),
-                Settings.System.VIBRATE_WHEN_RINGING, 0) != 0;
+        return Settings.System.getIntForUser(context.getContentResolver(),
+                Settings.System.VIBRATE_WHEN_RINGING, 0, context.getUserId()) != 0;
     }
 
     public boolean isEnhancedCallBlockingEnabled(Context context) {
-        return Settings.System.getInt(context.getContentResolver(),
-                Settings.System.DEBUG_ENABLE_ENHANCED_CALL_BLOCKING, 0) != 0;
+        return Settings.System.getIntForUser(context.getContentResolver(),
+                Settings.System.DEBUG_ENABLE_ENHANCED_CALL_BLOCKING, 0, context.getUserId()) != 0;
     }
 
     public boolean setEnhancedCallBlockingEnabled(Context context, boolean enabled) {
-        return Settings.System.putInt(context.getContentResolver(),
-                Settings.System.DEBUG_ENABLE_ENHANCED_CALL_BLOCKING, enabled ? 1 : 0);
+        return Settings.System.putIntForUser(context.getContentResolver(),
+                Settings.System.DEBUG_ENABLE_ENHANCED_CALL_BLOCKING, enabled ? 1 : 0,
+                context.getUserId());
     }
 
     public boolean applyRampingRinger(Context context) {
diff --git a/src/com/android/server/telecom/SystemStateHelper.java b/src/com/android/server/telecom/SystemStateHelper.java
index 073b081..dd978c2 100644
--- a/src/com/android/server/telecom/SystemStateHelper.java
+++ b/src/com/android/server/telecom/SystemStateHelper.java
@@ -16,6 +16,7 @@
 
 package com.android.server.telecom;
 
+import android.annotation.NonNull;
 import android.app.UiModeManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -26,6 +27,7 @@
 import android.hardware.SensorEvent;
 import android.hardware.SensorEventListener;
 import android.hardware.SensorManager;
+import android.net.Uri;
 import android.telecom.Log;
 
 import java.util.Set;
@@ -37,8 +39,8 @@
 /**
  * Provides various system states to the rest of the telecom codebase.
  */
-public class SystemStateHelper {
-    public static interface SystemStateListener {
+public class SystemStateHelper implements UiModeManager.OnProjectionStateChangedListener {
+    public interface SystemStateListener {
         /**
          * Listener method to inform interested parties when a package name requests to enter or
          * exit car mode.
@@ -48,33 +50,66 @@
          *                              otherwise.
          */
         void onCarModeChanged(int priority, String packageName, boolean isCarMode);
+
+        /**
+         * Listener method to inform interested parties when a package has set automotive projection
+         * state.
+         * @param automotiveProjectionPackage the package that set automotive projection.
+         */
+        void onAutomotiveProjectionStateSet(String automotiveProjectionPackage);
+
+        /**
+         * Listener method to inform interested parties when automotive projection state has been
+         * cleared.
+         */
+        void onAutomotiveProjectionStateReleased();
+
+        /**
+         * Notifies when a package has been uninstalled.
+         * @param packageName the package name of the uninstalled package
+         */
+        void onPackageUninstalled(String packageName);
     }
 
     private final Context mContext;
     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            Log.startSession("SSP.oR");
+            Log.startSession("SSH.oR");
             try {
-                String action = intent.getAction();
-                if (UiModeManager.ACTION_ENTER_CAR_MODE_PRIORITIZED.equals(action)) {
-                    int priority = intent.getIntExtra(UiModeManager.EXTRA_PRIORITY,
-                            UiModeManager.DEFAULT_PRIORITY);
-                    String callingPackage = intent.getStringExtra(
-                            UiModeManager.EXTRA_CALLING_PACKAGE);
-                    Log.i(SystemStateHelper.this, "ENTER_CAR_MODE_PRIVILEGED; priority=%d, pkg=%s",
-                            priority, callingPackage);
-                    onEnterCarMode(priority, callingPackage);
-                } else if (UiModeManager.ACTION_EXIT_CAR_MODE_PRIORITIZED.equals(action)) {
-                    int priority = intent.getIntExtra(UiModeManager.EXTRA_PRIORITY,
-                            UiModeManager.DEFAULT_PRIORITY);
-                    String callingPackage = intent.getStringExtra(
-                            UiModeManager.EXTRA_CALLING_PACKAGE);
-                    Log.i(SystemStateHelper.this, "EXIT_CAR_MODE_PRIVILEGED; priority=%d, pkg=%s",
-                            priority, callingPackage);
-                    onExitCarMode(priority, callingPackage);
-                } else {
-                    Log.w(this, "Unexpected intent received: %s", intent.getAction());
+                synchronized (mLock) {
+                    String action = intent.getAction();
+                    if (UiModeManager.ACTION_ENTER_CAR_MODE_PRIORITIZED.equals(action)) {
+                        int priority = intent.getIntExtra(UiModeManager.EXTRA_PRIORITY,
+                                UiModeManager.DEFAULT_PRIORITY);
+                        String callingPackage = intent.getStringExtra(
+                                UiModeManager.EXTRA_CALLING_PACKAGE);
+                        Log.i(SystemStateHelper.this,
+                                "ENTER_CAR_MODE_PRIORITIZED; priority=%d, pkg=%s",
+                                priority, callingPackage);
+                        onEnterCarMode(priority, callingPackage);
+                    } else if (UiModeManager.ACTION_EXIT_CAR_MODE_PRIORITIZED.equals(action)) {
+                        int priority = intent.getIntExtra(UiModeManager.EXTRA_PRIORITY,
+                                UiModeManager.DEFAULT_PRIORITY);
+                        String callingPackage = intent.getStringExtra(
+                                UiModeManager.EXTRA_CALLING_PACKAGE);
+                        Log.i(SystemStateHelper.this,
+                                "EXIT_CAR_MODE_PRIORITIZED; priority=%d, pkg=%s",
+                                priority, callingPackage);
+                        onExitCarMode(priority, callingPackage);
+                    } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
+                        Uri data = intent.getData();
+                        if (data == null) {
+                            Log.w(SystemStateHelper.this,
+                                    "Got null data for package removed, ignoring");
+                            return;
+                        }
+                        mListeners.forEach(
+                                l -> l.onPackageUninstalled(data.getEncodedSchemeSpecificPart()));
+                    } else {
+                        Log.w(SystemStateHelper.this,
+                                "Unexpected intent received: %s", intent.getAction());
+                    }
                 }
             } finally {
                 Log.endSession();
@@ -82,19 +117,46 @@
         }
     };
 
+    @Override
+    public void onProjectionStateChanged(int activeProjectionTypes,
+            @NonNull Set<String> projectingPackages) {
+        Log.startSession("SSH.oPSC");
+        try {
+            synchronized (mLock) {
+                if (projectingPackages.isEmpty()) {
+                    onReleaseAutomotiveProjection();
+                } else {
+                    onSetAutomotiveProjection(projectingPackages.iterator().next());
+                }
+            }
+        } finally {
+            Log.endSession();
+        }
+
+    }
+
     private Set<SystemStateListener> mListeners = new CopyOnWriteArraySet<>();
-    private boolean mIsCarMode;
+    private boolean mIsCarModeOrProjectionActive;
+    private final TelecomSystem.SyncRoot mLock;
 
-    public SystemStateHelper(Context context) {
+    public SystemStateHelper(Context context, TelecomSystem.SyncRoot lock) {
         mContext = context;
+        mLock = lock;
 
-        IntentFilter intentFilter = new IntentFilter(
+        IntentFilter intentFilter1 = new IntentFilter(
                 UiModeManager.ACTION_ENTER_CAR_MODE_PRIORITIZED);
-        intentFilter.addAction(UiModeManager.ACTION_EXIT_CAR_MODE_PRIORITIZED);
-        mContext.registerReceiver(mBroadcastReceiver, intentFilter);
-        Log.i(this, "Registering car mode receiver: %s", intentFilter);
+        intentFilter1.addAction(UiModeManager.ACTION_EXIT_CAR_MODE_PRIORITIZED);
 
-        mIsCarMode = getSystemCarMode();
+        IntentFilter intentFilter2 = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED);
+        intentFilter2.addDataScheme("package");
+        mContext.registerReceiver(mBroadcastReceiver, intentFilter1);
+        mContext.registerReceiver(mBroadcastReceiver, intentFilter2);
+        Log.i(this, "Registering broadcast receiver: %s", intentFilter1);
+        Log.i(this, "Registering broadcast receiver: %s", intentFilter2);
+
+        mContext.getSystemService(UiModeManager.class).addOnProjectionStateChangedListener(
+                UiModeManager.PROJECTION_TYPE_AUTOMOTIVE, mContext.getMainExecutor(), this);
+        mIsCarModeOrProjectionActive = getSystemCarModeOrProjectionState();
     }
 
     public void addListener(SystemStateListener listener) {
@@ -107,8 +169,8 @@
         return mListeners.remove(listener);
     }
 
-    public boolean isCarMode() {
-        return mIsCarMode;
+    public boolean isCarModeOrProjectionActive() {
+        return mIsCarModeOrProjectionActive;
     }
 
     public boolean isDeviceAtEar() {
@@ -193,7 +255,7 @@
 
     private void onEnterCarMode(int priority, String packageName) {
         Log.i(this, "Entering carmode");
-        mIsCarMode = getSystemCarMode();
+        mIsCarModeOrProjectionActive = getSystemCarModeOrProjectionState();
         for (SystemStateListener listener : mListeners) {
             listener.onCarModeChanged(priority, packageName, true /* isCarMode */);
         }
@@ -201,25 +263,44 @@
 
     private void onExitCarMode(int priority, String packageName) {
         Log.i(this, "Exiting carmode");
-        mIsCarMode = getSystemCarMode();
+        mIsCarModeOrProjectionActive = getSystemCarModeOrProjectionState();
         for (SystemStateListener listener : mListeners) {
             listener.onCarModeChanged(priority, packageName, false /* isCarMode */);
         }
     }
 
-    /**
-     * 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;
+    private void onSetAutomotiveProjection(String packageName) {
+        Log.i(this, "Automotive projection set.");
+        mIsCarModeOrProjectionActive = getSystemCarModeOrProjectionState();
+        for (SystemStateListener listener : mListeners) {
+            listener.onAutomotiveProjectionStateSet(packageName);
         }
 
+    }
+
+    private void onReleaseAutomotiveProjection() {
+        Log.i(this, "Automotive projection released.");
+        mIsCarModeOrProjectionActive = getSystemCarModeOrProjectionState();
+        for (SystemStateListener listener : mListeners) {
+            listener.onAutomotiveProjectionStateReleased();
+        }
+    }
+
+    /**
+     * Checks the system for the current car projection state.
+     *
+     * @return True if projection is active, false otherwise.
+     */
+    private boolean getSystemCarModeOrProjectionState() {
+        UiModeManager uiModeManager = mContext.getSystemService(UiModeManager.class);
+
+        if (uiModeManager != null) {
+            return uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_CAR
+                    || (uiModeManager.getActiveProjectionTypes()
+                            & UiModeManager.PROJECTION_TYPE_AUTOMOTIVE) != 0;
+        }
+
+        Log.w(this, "Got null UiModeManager, returning false.");
         return false;
     }
 }
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index 9ccae9a..86d5ebc 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -30,10 +30,14 @@
 import android.Manifest;
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
+import android.app.UiModeManager;
+import android.app.compat.CompatChanges;
+import android.content.AttributionSource;
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.PermissionChecker;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
@@ -57,7 +61,6 @@
 import android.util.EventLog;
 
 import com.android.internal.telecom.ITelecomService;
-import com.android.internal.telephony.TelephonyPermissions;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.telecom.components.UserCallIntentProcessorFactory;
 import com.android.server.telecom.settings.BlockedNumbersActivity;
@@ -314,9 +317,21 @@
         }
 
         @Override
-        public PhoneAccount getPhoneAccount(PhoneAccountHandle accountHandle) {
+        public PhoneAccount getPhoneAccount(PhoneAccountHandle accountHandle,
+                String callingPackage) {
             synchronized (mLock) {
                 final UserHandle callingUserHandle = Binder.getCallingUserHandle();
+                if (CompatChanges.isChangeEnabled(
+                        TelecomManager.ENABLE_GET_PHONE_ACCOUNT_PERMISSION_PROTECTION,
+                        callingPackage, Binder.getCallingUserHandle())) {
+                    if (Binder.getCallingUid() != Process.SHELL_UID &&
+                            !canGetPhoneAccount(callingPackage, accountHandle)) {
+                        SecurityException e = new SecurityException("getPhoneAccount API requires" +
+                                "READ_PHONE_NUMBERS");
+                        Log.e(this, e, "getPhoneAccount %s", accountHandle);
+                        throw e;
+                    }
+                }
                 long token = Binder.clearCallingIdentity();
                 try {
                     Log.startSession("TSI.gPA");
@@ -326,7 +341,7 @@
                     // profile's phone account handle.
                     return mPhoneAccountRegistrar
                             .getPhoneAccount(accountHandle, callingUserHandle,
-                            /* acrossProfiles */ true);
+                                    /* acrossProfiles */ true);
                 } catch (Exception e) {
                     Log.e(this, e, "getPhoneAccount %s", accountHandle);
                     throw e;
@@ -456,11 +471,11 @@
                 try {
                     Log.startSession("TSI.gSCMFU");
                     final int callingUid = Binder.getCallingUid();
+                    if (user != ActivityManager.getCurrentUser()) {
+                        enforceCrossUserPermission(callingUid);
+                    }
                     long token = Binder.clearCallingIdentity();
                     try {
-                        if (user != ActivityManager.getCurrentUser()) {
-                            enforceCrossUserPermission(callingUid);
-                        }
                         return mPhoneAccountRegistrar.getSimCallManager(UserHandle.of(user));
                     } finally {
                         Binder.restoreCallingIdentity(token);
@@ -727,7 +742,7 @@
         public ComponentName getDefaultPhoneApp() {
             try {
                 Log.startSession("TSI.gDPA");
-                return mDefaultDialerCache.getSystemDialerComponent();
+                return mDefaultDialerCache.getDialtactsSystemDialerComponent();
             } finally {
                 Log.endSession();
             }
@@ -831,6 +846,27 @@
         }
 
         /**
+         * @see android.telecom.TelecomManager#hasManageOngoingCallsPermission
+         */
+        @Override
+        public boolean hasManageOngoingCallsPermission(String callingPackage) {
+            try {
+                Log.startSession("TSI.hMOCP");
+                enforceCallingPackage(callingPackage);
+                return PermissionChecker.checkPermissionForDataDeliveryFromDataSource(
+                        mContext, Manifest.permission.MANAGE_ONGOING_CALLS,
+                        Binder.getCallingPid(),
+                        new AttributionSource(mContext.getAttributionSource(),
+                                new AttributionSource(Binder.getCallingUid(),
+                                        callingPackage, /*attributionTag*/ null)),
+                        "Checking whether the caller has MANAGE_ONGOING_CALLS permission")
+                                == PermissionChecker.PERMISSION_GRANTED;
+            } finally {
+                Log.endSession();
+            }
+        }
+
+        /**
          * @see android.telecom.TelecomManager#isInManagedCall
          */
         @Override
@@ -881,12 +917,48 @@
         }
 
         /**
-         * @see TelecomManager#getCallState
+         * @see TelecomManager#getCallState()
+         * @deprecated this is only being kept due to an @UnsupportedAppUsage tag. Apps targeting
+         * API 31+ must use {@link #getCallStateUsingPackage(String, String)} below.
          */
+        @Deprecated
         @Override
         public int getCallState() {
             try {
-                Log.startSession("TSI.getCallState");
+                Log.startSession("TSI.getCallState(DEPRECATED)");
+                if (CompatChanges.isChangeEnabled(
+                        TelecomManager.ENABLE_GET_CALL_STATE_PERMISSION_PROTECTION,
+                        Binder.getCallingUid())) {
+                    // Do not allow this API to be called on API version 31+, it should only be
+                    // called on old apps using this Binder call directly.
+                    throw new SecurityException("This method can only be used for applications "
+                            + "targeting API version 30 or less.");
+                }
+                synchronized (mLock) {
+                    return mCallsManager.getCallState();
+                }
+            } finally {
+                Log.endSession();
+            }
+        }
+
+        /**
+         * @see TelecomManager#getCallState()
+         */
+        @Override
+        public int getCallStateUsingPackage(String callingPackage, String callingFeatureId) {
+            try {
+                Log.startSession("TSI.getCallStateUsingPackage");
+                if (CompatChanges.isChangeEnabled(
+                        TelecomManager.ENABLE_GET_CALL_STATE_PERMISSION_PROTECTION, callingPackage,
+                        Binder.getCallingUserHandle())) {
+                    // Bypass canReadPhoneState check if this is being called from SHELL UID
+                    if (Binder.getCallingUid() != Process.SHELL_UID && !canReadPhoneState(
+                            callingPackage, callingFeatureId, "getCallState")) {
+                        throw new SecurityException("getCallState API requires READ_PHONE_STATE"
+                                + " for API version 31+");
+                    }
+                }
                 synchronized (mLock) {
                     return mCallsManager.getCallState();
                 }
@@ -931,7 +1003,7 @@
 
                     long token = Binder.clearCallingIdentity();
                     try {
-                        acceptRingingCallInternal(DEFAULT_VIDEO_STATE);
+                        acceptRingingCallInternal(DEFAULT_VIDEO_STATE, packageName);
                     } finally {
                         Binder.restoreCallingIdentity(token);
                     }
@@ -954,7 +1026,7 @@
 
                     long token = Binder.clearCallingIdentity();
                     try {
-                        acceptRingingCallInternal(videoState);
+                        acceptRingingCallInternal(videoState, packageName);
                     } finally {
                         Binder.restoreCallingIdentity(token);
                     }
@@ -1754,6 +1826,58 @@
             }
         }
 
+        /**
+         * A method intended for use in testing to clean up any calls that get stuck in the
+         * {@link CallState#DISCONNECTED} or {@link CallState#DISCONNECTING} states. Stuck calls
+         * during CTS cause cascading failures, so if the CTS test detects such a state, it should
+         * call this method via a shell command to clean up before moving on to the next test.
+         * Also cleans up any pending futures related to
+         * {@link android.telecom.CallDiagnosticService}s.
+         */
+        @Override
+        public void cleanupStuckCalls() {
+            Log.startSession("TCI.cSC");
+            try {
+                synchronized (mLock) {
+                    enforceShellOnly(Binder.getCallingUid(), "cleanupStuckCalls");
+                    Binder.withCleanCallingIdentity(() -> {
+                        for (Call call : mCallsManager.getCalls()) {
+                            call.cleanup();
+                            if (call.getState() == CallState.DISCONNECTED
+                                    || call.getState() == CallState.DISCONNECTING) {
+                                mCallsManager.markCallAsRemoved(call);
+                            }
+                        }
+                        mCallsManager.getInCallController().unbindFromServices();
+                    });
+                }
+            } finally {
+                Log.endSession();
+            }
+        }
+
+        /**
+         * A method intended for use in testing to reset car mode at all priorities.
+         *
+         * Runs during setup to avoid cascading failures from failing car mode CTS.
+         */
+        @Override
+        public void resetCarMode() {
+            Log.startSession("TCI.rCM");
+            try {
+                synchronized (mLock) {
+                    enforceShellOnly(Binder.getCallingUid(), "resetCarMode");
+                    Binder.withCleanCallingIdentity(() -> {
+                        UiModeManager uiModeManager =
+                                mContext.getSystemService(UiModeManager.class);
+                        uiModeManager.disableCarMode(UiModeManager.DISABLE_CAR_MODE_ALL_PRIORITIES);
+                    });
+                }
+            } finally {
+                Log.endSession();
+            }
+        }
+
         @Override
         public void setTestDefaultCallRedirectionApp(String packageName) {
             try {
@@ -1856,6 +1980,30 @@
                 Log.endSession();
             }
         }
+
+        @Override
+        public void setTestCallDiagnosticService(String packageName) {
+            try {
+                Log.startSession("TSI.sTCDS");
+                enforceModifyPermission();
+                enforceShellOnly(Binder.getCallingUid(), "setTestCallDiagnosticService is for "
+                        + "shell use only.");
+                synchronized (mLock) {
+                    long token = Binder.clearCallingIdentity();
+                    try {
+                        CallDiagnosticServiceController controller =
+                                mCallsManager.getCallDiagnosticServiceController();
+                        if (controller != null) {
+                            controller.setTestCallDiagnosticService(packageName);
+                        }
+                    } finally {
+                        Binder.restoreCallingIdentity(token);
+                    }
+                }
+            } finally {
+                Log.endSession();
+            }
+        }
     };
 
     /**
@@ -1985,9 +2133,16 @@
         return false;
     }
 
-    private void acceptRingingCallInternal(int videoState) {
-        Call call = mCallsManager.getFirstCallWithState(CallState.RINGING, CallState.SIMULATED_RINGING);
+    private void acceptRingingCallInternal(int videoState, String packageName) {
+        Call call = mCallsManager.getFirstCallWithState(CallState.RINGING,
+                CallState.SIMULATED_RINGING);
         if (call != null) {
+            if (call.isSelfManaged()) {
+                Log.addEvent(call, LogUtils.Events.REQUEST_ACCEPT,
+                        "self-mgd accept ignored from " + packageName);
+                return;
+            }
+
             if (videoState == DEFAULT_VIDEO_STATE || !isValidAcceptVideoState(videoState)) {
                 videoState = call.getVideoState();
             }
@@ -2015,6 +2170,12 @@
                 return false;
             }
 
+            if (call.isSelfManaged()) {
+                Log.addEvent(call, LogUtils.Events.REQUEST_DISCONNECT,
+                        "self-mgd disconnect ignored from " + callingPackage);
+                return false;
+            }
+
             if (call.getState() == CallState.RINGING
                     || call.getState() == CallState.SIMULATED_RINGING) {
                 mCallsManager.rejectCall(call, false /* rejectWithMessage */, null);
@@ -2274,6 +2435,28 @@
                 == AppOpsManager.MODE_ALLOWED;
     }
 
+    private boolean canGetPhoneAccount(String callingPackage, PhoneAccountHandle accountHandle) {
+        // Allow default dialer, system dialer and sim call manager to be able to do this without
+        // extra permission
+        try {
+            if (isPrivilegedDialerCalling(callingPackage) || isCallerSimCallManager(
+                    accountHandle)) {
+                return true;
+            }
+        } catch (SecurityException e) {
+            // ignore
+        }
+
+        try {
+            mContext.enforceCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE, null);
+            return true;
+        } catch (SecurityException e) {
+            // Accessing phone state is gated by a special permission.
+            mContext.enforceCallingOrSelfPermission(READ_PHONE_NUMBERS, null);
+            return true;
+        }
+    }
+
     private boolean isCallerSimCallManager(PhoneAccountHandle targetPhoneAccount) {
         long token = Binder.clearCallingIdentity();
         PhoneAccountHandle accountHandle = null;
diff --git a/src/com/android/server/telecom/TelecomSystem.java b/src/com/android/server/telecom/TelecomSystem.java
index 08389b9..c824a65 100644
--- a/src/com/android/server/telecom/TelecomSystem.java
+++ b/src/com/android/server/telecom/TelecomSystem.java
@@ -20,14 +20,12 @@
 import com.android.server.telecom.bluetooth.BluetoothDeviceManager;
 import com.android.server.telecom.bluetooth.BluetoothRouteManager;
 import com.android.server.telecom.bluetooth.BluetoothStateReceiver;
-import com.android.server.telecom.callfiltering.IncomingCallFilter;
 import com.android.server.telecom.components.UserCallIntentProcessor;
 import com.android.server.telecom.components.UserCallIntentProcessorFactory;
 import com.android.server.telecom.ui.AudioProcessingNotification;
 import com.android.server.telecom.ui.DisconnectedCallNotifier;
 import com.android.server.telecom.ui.IncomingCallNotifier;
 import com.android.server.telecom.ui.MissedCallNotifierImpl.MissedCallNotifierImplFactory;
-import com.android.server.telecom.BluetoothPhoneServiceImpl.BluetoothPhoneServiceImplFactory;
 import com.android.server.telecom.CallAudioManager.AudioServiceFactory;
 import com.android.server.telecom.DefaultDialerCache.DefaultDialerManagerAdapter;
 import com.android.server.telecom.ui.ToastFactory;
@@ -38,8 +36,10 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.ServiceConnection;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
 import android.net.Uri;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -47,8 +47,11 @@
 import android.telecom.PhoneAccountHandle;
 import android.widget.Toast;
 
+import androidx.annotation.NonNull;
+
 import java.io.FileNotFoundException;
 import java.io.InputStream;
+import java.util.List;
 
 /**
  * Top-level Application class for Telecom.
@@ -116,7 +119,6 @@
     private final CallsManager mCallsManager;
     private final RespondViaSmsManager mRespondViaSmsManager;
     private final Context mContext;
-    private final BluetoothPhoneServiceImpl mBluetoothPhoneServiceImpl;
     private final CallIntentProcessor mCallIntentProcessor;
     private final TelecomBroadcastIntentProcessor mTelecomBroadcastIntentProcessor;
     private final TelecomServiceImpl mTelecomServiceImpl;
@@ -193,8 +195,6 @@
             ProximitySensorManagerFactory proximitySensorManagerFactory,
             InCallWakeLockControllerFactory inCallWakeLockControllerFactory,
             AudioServiceFactory audioServiceFactory,
-            BluetoothPhoneServiceImplFactory
-                    bluetoothPhoneServiceImplFactory,
             ConnectionServiceFocusManager.ConnectionServiceFocusManagerFactory
                     connectionServiceFocusManagerFactory,
             Timeouts.Adapter timeoutsAdapter,
@@ -206,7 +206,6 @@
             CallAudioModeStateMachine.Factory callAudioModeStateMachineFactory,
             ClockProxy clockProxy,
             RoleManagerAdapter roleManagerAdapter,
-            IncomingCallFilter.Factory incomingCallFilterFactory,
             ContactsAsyncHelper.Factory contactsAsyncHelperFactory,
             DeviceIdleControllerAdapter deviceIdleControllerAdapter) {
         mContext = context.getApplicationContext();
@@ -218,168 +217,208 @@
                 defaultDialerAdapter, roleManagerAdapter, mLock);
 
         Log.startSession("TS.init");
-        mPhoneAccountRegistrar = new PhoneAccountRegistrar(mContext, defaultDialerCache,
-                packageName -> AppLabelProxy.Util.getAppLabel(
-                        mContext.getPackageManager(), packageName));
+        // Wrap this in a try block to ensure session cleanup occurs in the case of error.
+        try {
+            mPhoneAccountRegistrar = new PhoneAccountRegistrar(mContext, defaultDialerCache,
+                    packageName -> AppLabelProxy.Util.getAppLabel(
+                            mContext.getPackageManager(), packageName));
 
-        mContactsAsyncHelper = contactsAsyncHelperFactory.create(
-                new ContactsAsyncHelper.ContentResolverAdapter() {
-                    @Override
-                    public InputStream openInputStream(Context context, Uri uri)
-                            throws FileNotFoundException {
-                        return context.getContentResolver().openInputStream(uri);
-                    }
-                });
-        BluetoothDeviceManager bluetoothDeviceManager = new BluetoothDeviceManager(mContext,
-                new BluetoothAdapterProxy());
-        BluetoothRouteManager bluetoothRouteManager = new BluetoothRouteManager(mContext, mLock,
-                bluetoothDeviceManager, new Timeouts.Adapter());
-        BluetoothStateReceiver bluetoothStateReceiver = new BluetoothStateReceiver(
-                bluetoothDeviceManager, bluetoothRouteManager);
-        mContext.registerReceiver(bluetoothStateReceiver, BluetoothStateReceiver.INTENT_FILTER);
+            mContactsAsyncHelper = contactsAsyncHelperFactory.create(
+                    new ContactsAsyncHelper.ContentResolverAdapter() {
+                        @Override
+                        public InputStream openInputStream(Context context, Uri uri)
+                                throws FileNotFoundException {
+                            return context.getContentResolver().openInputStream(uri);
+                        }
+                    });
+            BluetoothDeviceManager bluetoothDeviceManager = new BluetoothDeviceManager(mContext,
+                    new BluetoothAdapterProxy());
+            BluetoothRouteManager bluetoothRouteManager = new BluetoothRouteManager(mContext, mLock,
+                    bluetoothDeviceManager, new Timeouts.Adapter());
+            BluetoothStateReceiver bluetoothStateReceiver = new BluetoothStateReceiver(
+                    bluetoothDeviceManager, bluetoothRouteManager);
+            mContext.registerReceiver(bluetoothStateReceiver, BluetoothStateReceiver.INTENT_FILTER);
 
-        WiredHeadsetManager wiredHeadsetManager = new WiredHeadsetManager(mContext);
-        SystemStateHelper systemStateHelper = new SystemStateHelper(mContext);
+            WiredHeadsetManager wiredHeadsetManager = new WiredHeadsetManager(mContext);
+            SystemStateHelper systemStateHelper = new SystemStateHelper(mContext, mLock);
 
-        mMissedCallNotifier = missedCallNotifierImplFactory
-                .makeMissedCallNotifierImpl(mContext, mPhoneAccountRegistrar, defaultDialerCache,
-                        deviceIdleControllerAdapter);
-        DisconnectedCallNotifier.Factory disconnectedCallNotifierFactory =
-                new DisconnectedCallNotifier.Default();
+            mMissedCallNotifier = missedCallNotifierImplFactory
+                    .makeMissedCallNotifierImpl(mContext, mPhoneAccountRegistrar,
+                            defaultDialerCache,
+                            deviceIdleControllerAdapter);
+            DisconnectedCallNotifier.Factory disconnectedCallNotifierFactory =
+                    new DisconnectedCallNotifier.Default();
 
-        CallerInfoLookupHelper callerInfoLookupHelper =
-                new CallerInfoLookupHelper(context, callerInfoAsyncQueryFactory,
-                        mContactsAsyncHelper, mLock);
+            CallerInfoLookupHelper callerInfoLookupHelper =
+                    new CallerInfoLookupHelper(context, callerInfoAsyncQueryFactory,
+                            mContactsAsyncHelper, mLock);
 
-        EmergencyCallHelper emergencyCallHelper = new EmergencyCallHelper(mContext,
-                defaultDialerCache, timeoutsAdapter);
+            EmergencyCallHelper emergencyCallHelper = new EmergencyCallHelper(mContext,
+                    defaultDialerCache, timeoutsAdapter);
 
-        InCallControllerFactory inCallControllerFactory = new InCallControllerFactory() {
-            @Override
-            public InCallController create(Context context, SyncRoot lock,
-                    CallsManager callsManager, SystemStateHelper systemStateProvider,
-                    DefaultDialerCache defaultDialerCache, Timeouts.Adapter timeoutsAdapter,
-                    EmergencyCallHelper emergencyCallHelper) {
-                return new InCallController(context, lock, callsManager, systemStateProvider,
-                        defaultDialerCache, timeoutsAdapter, emergencyCallHelper,
-                        new CarModeTracker(), clockProxy);
-            }
-        };
+            InCallControllerFactory inCallControllerFactory = new InCallControllerFactory() {
+                @Override
+                public InCallController create(Context context, SyncRoot lock,
+                        CallsManager callsManager, SystemStateHelper systemStateProvider,
+                        DefaultDialerCache defaultDialerCache, Timeouts.Adapter timeoutsAdapter,
+                        EmergencyCallHelper emergencyCallHelper) {
+                    return new InCallController(context, lock, callsManager, systemStateProvider,
+                            defaultDialerCache, timeoutsAdapter, emergencyCallHelper,
+                            new CarModeTracker(), clockProxy);
+                }
+            };
 
-        AudioProcessingNotification audioProcessingNotification =
-                new AudioProcessingNotification(mContext);
+            CallDiagnosticServiceController callDiagnosticServiceController =
+                    new CallDiagnosticServiceController(
+                            new CallDiagnosticServiceController.ContextProxy() {
+                                @Override
+                                public List<ResolveInfo> queryIntentServicesAsUser(
+                                        @NonNull Intent intent, int flags, int userId) {
+                                    return mContext.getPackageManager().queryIntentServicesAsUser(
+                                            intent, flags, userId);
+                                }
 
-        ToastFactory toastFactory = new ToastFactory() {
-            @Override
-            public Toast makeText(Context context, int resId, int duration) {
-                return Toast.makeText(context, context.getMainLooper(), context.getString(resId),
-                        duration);
+                                @Override
+                                public boolean bindServiceAsUser(@NonNull Intent service,
+                                        @NonNull ServiceConnection conn, int flags,
+                                        @NonNull UserHandle user) {
+                                    return mContext.bindServiceAsUser(service, conn, flags, user);
+                                }
+
+                                @Override
+                                public void unbindService(@NonNull ServiceConnection conn) {
+                                    mContext.unbindService(conn);
+                                }
+
+                                @Override
+                                public UserHandle getCurrentUserHandle() {
+                                    return mCallsManager.getCurrentUserHandle();
+                                }
+                            },
+                            mContext.getResources().getString(
+                                    com.android.server.telecom.R.string
+                                            .call_diagnostic_service_package_name),
+                            mLock
+                    );
+
+            AudioProcessingNotification audioProcessingNotification =
+                    new AudioProcessingNotification(mContext);
+
+            ToastFactory toastFactory = new ToastFactory() {
+                @Override
+                public Toast makeText(Context context, int resId, int duration) {
+                    return Toast.makeText(context, context.getMainLooper(),
+                            context.getString(resId),
+                            duration);
+                }
+
+                @Override
+                public Toast makeText(Context context, CharSequence text, int duration) {
+                    return Toast.makeText(context, context.getMainLooper(), text, duration);
+                }
+            };
+
+            mCallsManager = new CallsManager(
+                    mContext,
+                    mLock,
+                    callerInfoLookupHelper,
+                    mMissedCallNotifier,
+                    disconnectedCallNotifierFactory,
+                    mPhoneAccountRegistrar,
+                    headsetMediaButtonFactory,
+                    proximitySensorManagerFactory,
+                    inCallWakeLockControllerFactory,
+                    connectionServiceFocusManagerFactory,
+                    audioServiceFactory,
+                    bluetoothRouteManager,
+                    wiredHeadsetManager,
+                    systemStateHelper,
+                    defaultDialerCache,
+                    timeoutsAdapter,
+                    asyncRingtonePlayer,
+                    phoneNumberUtilsAdapter,
+                    emergencyCallHelper,
+                    toneGeneratorFactory,
+                    clockProxy,
+                    audioProcessingNotification,
+                    bluetoothStateReceiver,
+                    callAudioRouteStateMachineFactory,
+                    callAudioModeStateMachineFactory,
+                    inCallControllerFactory,
+                    callDiagnosticServiceController,
+                    roleManagerAdapter,
+                    toastFactory);
+
+            mIncomingCallNotifier = incomingCallNotifier;
+            incomingCallNotifier.setCallsManagerProxy(new IncomingCallNotifier.CallsManagerProxy() {
+                @Override
+                public boolean hasUnholdableCallsForOtherConnectionService(
+                        PhoneAccountHandle phoneAccountHandle) {
+                    return mCallsManager.hasUnholdableCallsForOtherConnectionService(
+                            phoneAccountHandle);
+                }
+
+                @Override
+                public int getNumUnholdableCallsForOtherConnectionService(
+                        PhoneAccountHandle phoneAccountHandle) {
+                    return mCallsManager.getNumUnholdableCallsForOtherConnectionService(
+                            phoneAccountHandle);
+                }
+
+                @Override
+                public Call getActiveCall() {
+                    return mCallsManager.getActiveCall();
+                }
+            });
+            mCallsManager.setIncomingCallNotifier(mIncomingCallNotifier);
+
+            mRespondViaSmsManager = new RespondViaSmsManager(mCallsManager, mLock);
+            mCallsManager.setRespondViaSmsManager(mRespondViaSmsManager);
+
+            mContext.registerReceiverAsUser(mUserSwitchedReceiver, UserHandle.ALL,
+                    USER_SWITCHED_FILTER, null, null);
+            mContext.registerReceiverAsUser(mUserStartingReceiver, UserHandle.ALL,
+                    USER_STARTING_FILTER, null, null);
+            mContext.registerReceiverAsUser(mBootCompletedReceiver, UserHandle.ALL,
+                    BOOT_COMPLETE_FILTER, null, null);
+
+            // Set current user explicitly since USER_SWITCHED_FILTER intent can be missed at
+            // startup
+            synchronized (mLock) {
+                UserHandle currentUserHandle = UserHandle.of(ActivityManager.getCurrentUser());
+                mPhoneAccountRegistrar.setCurrentUserHandle(currentUserHandle);
+                mCallsManager.onUserSwitch(currentUserHandle);
             }
 
-            @Override
-            public Toast makeText(Context context, CharSequence text, int duration) {
-                return Toast.makeText(context, context.getMainLooper(), text, duration);
-            }
-        };
+            mCallIntentProcessor = new CallIntentProcessor(mContext, mCallsManager,
+                    defaultDialerCache);
+            mTelecomBroadcastIntentProcessor = new TelecomBroadcastIntentProcessor(
+                    mContext, mCallsManager);
 
-        mCallsManager = new CallsManager(
-                mContext,
-                mLock,
-                callerInfoLookupHelper,
-                mMissedCallNotifier,
-                disconnectedCallNotifierFactory,
-                mPhoneAccountRegistrar,
-                headsetMediaButtonFactory,
-                proximitySensorManagerFactory,
-                inCallWakeLockControllerFactory,
-                connectionServiceFocusManagerFactory,
-                audioServiceFactory,
-                bluetoothRouteManager,
-                wiredHeadsetManager,
-                systemStateHelper,
-                defaultDialerCache,
-                timeoutsAdapter,
-                asyncRingtonePlayer,
-                phoneNumberUtilsAdapter,
-                emergencyCallHelper,
-                toneGeneratorFactory,
-                clockProxy,
-                audioProcessingNotification,
-                bluetoothStateReceiver,
-                callAudioRouteStateMachineFactory,
-                callAudioModeStateMachineFactory,
-                inCallControllerFactory,
-                roleManagerAdapter,
-                incomingCallFilterFactory,
-                toastFactory);
+            // Register the receiver for the dialer secret codes, used to enable extended logging.
+            mDialerCodeReceiver = new DialerCodeReceiver(mCallsManager);
+            mContext.registerReceiver(mDialerCodeReceiver, DIALER_SECRET_CODE_FILTER,
+                    Manifest.permission.CONTROL_INCALL_EXPERIENCE, null);
 
-        mIncomingCallNotifier = incomingCallNotifier;
-        incomingCallNotifier.setCallsManagerProxy(new IncomingCallNotifier.CallsManagerProxy() {
-            @Override
-            public boolean hasUnholdableCallsForOtherConnectionService(
-                    PhoneAccountHandle phoneAccountHandle) {
-                return mCallsManager.hasUnholdableCallsForOtherConnectionService(
-                        phoneAccountHandle);
-            }
-
-            @Override
-            public int getNumUnholdableCallsForOtherConnectionService(
-                    PhoneAccountHandle phoneAccountHandle) {
-                return mCallsManager.getNumUnholdableCallsForOtherConnectionService(
-                        phoneAccountHandle);
-            }
-
-            @Override
-            public Call getActiveCall() {
-                return mCallsManager.getActiveCall();
-            }
-        });
-        mCallsManager.setIncomingCallNotifier(mIncomingCallNotifier);
-
-        mRespondViaSmsManager = new RespondViaSmsManager(mCallsManager, mLock);
-        mCallsManager.setRespondViaSmsManager(mRespondViaSmsManager);
-
-        mContext.registerReceiverAsUser(mUserSwitchedReceiver, UserHandle.ALL,
-                USER_SWITCHED_FILTER, null, null);
-        mContext.registerReceiverAsUser(mUserStartingReceiver, UserHandle.ALL,
-                USER_STARTING_FILTER, null, null);
-        mContext.registerReceiverAsUser(mBootCompletedReceiver, UserHandle.ALL,
-                BOOT_COMPLETE_FILTER, null, null);
-
-        // Set current user explicitly since USER_SWITCHED_FILTER intent can be missed at startup
-        synchronized(mLock) {
-            UserHandle currentUserHandle = UserHandle.of(ActivityManager.getCurrentUser());
-            mPhoneAccountRegistrar.setCurrentUserHandle(currentUserHandle);
-            mCallsManager.onUserSwitch(currentUserHandle);
+            // There is no USER_SWITCHED broadcast for user 0, handle it here explicitly.
+            final UserManager userManager = UserManager.get(mContext);
+            mTelecomServiceImpl = new TelecomServiceImpl(
+                    mContext, mCallsManager, mPhoneAccountRegistrar,
+                    new CallIntentProcessor.AdapterImpl(defaultDialerCache),
+                    new UserCallIntentProcessorFactory() {
+                        @Override
+                        public UserCallIntentProcessor create(Context context,
+                                UserHandle userHandle) {
+                            return new UserCallIntentProcessor(context, userHandle);
+                        }
+                    },
+                    defaultDialerCache,
+                    new TelecomServiceImpl.SubscriptionManagerAdapterImpl(),
+                    new TelecomServiceImpl.SettingsSecureAdapterImpl(),
+                    mLock);
+        } finally {
+            Log.endSession();
         }
-
-        mBluetoothPhoneServiceImpl = bluetoothPhoneServiceImplFactory.makeBluetoothPhoneServiceImpl(
-                mContext, mLock, mCallsManager, mPhoneAccountRegistrar);
-        mCallIntentProcessor = new CallIntentProcessor(mContext, mCallsManager, defaultDialerCache);
-        mTelecomBroadcastIntentProcessor = new TelecomBroadcastIntentProcessor(
-                mContext, mCallsManager);
-
-        // Register the receiver for the dialer secret codes, used to enable extended logging.
-        mDialerCodeReceiver = new DialerCodeReceiver(mCallsManager);
-        mContext.registerReceiver(mDialerCodeReceiver, DIALER_SECRET_CODE_FILTER,
-                Manifest.permission.CONTROL_INCALL_EXPERIENCE, null);
-
-        // There is no USER_SWITCHED broadcast for user 0, handle it here explicitly.
-        final UserManager userManager = UserManager.get(mContext);
-        mTelecomServiceImpl = new TelecomServiceImpl(
-                mContext, mCallsManager, mPhoneAccountRegistrar,
-                new CallIntentProcessor.AdapterImpl(defaultDialerCache),
-                new UserCallIntentProcessorFactory() {
-                    @Override
-                    public UserCallIntentProcessor create(Context context, UserHandle userHandle) {
-                        return new UserCallIntentProcessor(context, userHandle);
-                    }
-                },
-                defaultDialerCache,
-                new TelecomServiceImpl.SubscriptionManagerAdapterImpl(),
-                new TelecomServiceImpl.SettingsSecureAdapterImpl(),
-                mLock);
-        Log.endSession();
     }
 
     @VisibleForTesting
@@ -392,10 +431,6 @@
         return mCallsManager;
     }
 
-    public BluetoothPhoneServiceImpl getBluetoothPhoneServiceImpl() {
-        return mBluetoothPhoneServiceImpl;
-    }
-
     public CallIntentProcessor getCallIntentProcessor() {
         return mCallIntentProcessor;
     }
diff --git a/src/com/android/server/telecom/Timeouts.java b/src/com/android/server/telecom/Timeouts.java
index 2309596..36caa25 100644
--- a/src/com/android/server/telecom/Timeouts.java
+++ b/src/com/android/server/telecom/Timeouts.java
@@ -17,8 +17,13 @@
 package com.android.server.telecom;
 
 import android.content.ContentResolver;
+import android.provider.DeviceConfig;
 import android.provider.Settings;
+import android.telecom.CallDiagnosticService;
 import android.telecom.CallRedirectionService;
+import android.telecom.CallDiagnostics;
+import android.telephony.ims.ImsReasonInfo;
+
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -67,6 +72,14 @@
         public long getCallRecordingToneRepeatIntervalMillis(ContentResolver cr) {
             return Timeouts.getCallRecordingToneRepeatIntervalMillis(cr);
         }
+
+        public long getCallDiagnosticServiceTimeoutMillis(ContentResolver cr) {
+            return Timeouts.getCallDiagnosticServiceTimeoutMillis(cr);
+        }
+
+        public long getCallStartAppOpDebounceIntervalMillis() {
+            return  Timeouts.getCallStartAppOpDebounceIntervalMillis();
+        }
     }
 
     /** A prefix to use for all keys so to not clobber the global namespace. */
@@ -85,7 +98,8 @@
      * @return The timeout value from Settings or the default value if it hasn't been changed.
      */
     private static long get(ContentResolver contentResolver, String key, long defaultValue) {
-        return Settings.Secure.getLong(contentResolver, PREFIX + key, defaultValue);
+        return Settings.Secure.getLongForUser(contentResolver, PREFIX + key, defaultValue,
+                        contentResolver.getUserId());
     }
 
     /**
@@ -189,7 +203,7 @@
     /**
      * Returns the amount of time for an user-defined {@link CallRedirectionService}.
      *
-     * @param contentResolver The content resolved.
+     * @param contentResolver The content resolver.
      */
     public static long getUserDefinedCallRedirectionTimeoutMillis(ContentResolver contentResolver) {
         return get(contentResolver, "user_defined_call_redirection_timeout",
@@ -199,7 +213,7 @@
     /**
      * Returns the amount of time for a carrier {@link CallRedirectionService}.
      *
-     * @param contentResolver The content resolved.
+     * @param contentResolver The content resolver.
      */
     public static long getCarrierCallRedirectionTimeoutMillis(ContentResolver contentResolver) {
         return get(contentResolver, "carrier_call_redirection_timeout", 5000L /* 5 seconds */);
@@ -213,6 +227,21 @@
     }
 
     /**
+     * Returns the maximum amount of time a {@link CallDiagnosticService} is permitted to take to
+     * return back from {@link CallDiagnostics#onCallDisconnected(ImsReasonInfo)} and
+     * {@link CallDiagnostics#onCallDisconnected(int, int)}.
+     * @param contentResolver The resolver for the config option.
+     * @return The timeout in millis.
+     */
+    public static long getCallDiagnosticServiceTimeoutMillis(ContentResolver contentResolver) {
+        return get(contentResolver, "call_diagnostic_service_timeout", 2000L /* 2 sec */);
+    }
+
+    public static long getCallStartAppOpDebounceIntervalMillis() {
+        return DeviceConfig.getLong(DeviceConfig.NAMESPACE_PRIVACY, "app_op_debounce_time", 250L);
+    }
+
+    /**
      * Returns the number of milliseconds for which the system should exempt the default dialer from
      * power save restrictions due to the dialer needing to handle a missed call notification
      * (update call log, check VVM, etc...).
diff --git a/src/com/android/server/telecom/TtyManager.java b/src/com/android/server/telecom/TtyManager.java
index dfddb8f..457ba36 100644
--- a/src/com/android/server/telecom/TtyManager.java
+++ b/src/com/android/server/telecom/TtyManager.java
@@ -42,10 +42,11 @@
         mWiredHeadsetManager = wiredHeadsetManager;
         mWiredHeadsetManager.addListener(this);
 
-        mPreferredTtyMode = Settings.Secure.getInt(
+        mPreferredTtyMode = Settings.Secure.getIntForUser(
                 mContext.getContentResolver(),
                 Settings.Secure.PREFERRED_TTY_MODE,
-                TelecomManager.TTY_MODE_OFF);
+                TelecomManager.TTY_MODE_OFF,
+                mContext.getUserId());
 
         IntentFilter intentFilter = new IntentFilter(
                 TelecomManager.ACTION_TTY_PREFERRED_MODE_CHANGED);
diff --git a/src/com/android/server/telecom/callfiltering/CallFilterResultCallback.java b/src/com/android/server/telecom/callfiltering/CallFilterResultCallback.java
index 052ce57..1043774 100644
--- a/src/com/android/server/telecom/callfiltering/CallFilterResultCallback.java
+++ b/src/com/android/server/telecom/callfiltering/CallFilterResultCallback.java
@@ -18,5 +18,5 @@
 import com.android.server.telecom.Call;
 
 public interface CallFilterResultCallback {
-    void onCallFilteringComplete(Call call, CallFilteringResult result);
+    void onCallFilteringComplete(Call call, CallFilteringResult result, boolean timeout);
 }
diff --git a/src/com/android/server/telecom/callfiltering/CallFilteringResult.java b/src/com/android/server/telecom/callfiltering/CallFilteringResult.java
index d95d578..84ce4d4 100644
--- a/src/com/android/server/telecom/callfiltering/CallFilteringResult.java
+++ b/src/com/android/server/telecom/callfiltering/CallFilteringResult.java
@@ -18,6 +18,7 @@
 
 import android.provider.CallLog;
 import android.provider.CallLog.Calls;
+import android.telecom.CallScreeningService;
 import android.text.TextUtils;
 
 import java.util.Objects;
@@ -34,6 +35,8 @@
         private int mCallBlockReason = Calls.BLOCK_REASON_NOT_BLOCKED;
         private CharSequence mCallScreeningAppName = null;
         private String mCallScreeningComponentName = null;
+        private CallScreeningService.ParcelableCallResponse mCallScreeningResponse = null;
+        private boolean mIsResponseFromSystemDialer = false;
 
         public Builder setShouldAllowCall(boolean shouldAllowCall) {
             mShouldAllowCall = shouldAllowCall;
@@ -80,6 +83,13 @@
             return this;
         }
 
+        public Builder setCallScreeningResponse(
+                CallScreeningService.ParcelableCallResponse response, boolean isFromSystemDialer) {
+            mCallScreeningResponse = response;
+            mIsResponseFromSystemDialer = isFromSystemDialer;
+            return this;
+        }
+
         public Builder setContactExists(boolean contactExists) {
             mContactExists = contactExists;
             return this;
@@ -96,14 +106,16 @@
                     .setShouldScreenViaAudio(result.shouldScreenViaAudio)
                     .setCallScreeningAppName(result.mCallScreeningAppName)
                     .setCallScreeningComponentName(result.mCallScreeningComponentName)
+                    .setCallScreeningResponse(result.mCallScreeningResponse,
+                            result.mIsResponseFromSystemDialer)
                     .setContactExists(result.contactExists);
         }
 
         public CallFilteringResult build() {
             return new CallFilteringResult(mShouldAllowCall, mShouldReject, mShouldSilence,
                     mShouldAddToCallLog, mShouldShowNotification, mCallBlockReason,
-                    mCallScreeningAppName, mCallScreeningComponentName, mShouldScreenViaAudio,
-                    mContactExists);
+                    mCallScreeningAppName, mCallScreeningComponentName, mCallScreeningResponse,
+                    mIsResponseFromSystemDialer, mShouldScreenViaAudio, mContactExists);
         }
     }
 
@@ -116,11 +128,15 @@
     public int mCallBlockReason;
     public CharSequence mCallScreeningAppName;
     public String mCallScreeningComponentName;
+    public CallScreeningService.ParcelableCallResponse mCallScreeningResponse;
+    public boolean mIsResponseFromSystemDialer;
     public boolean contactExists;
 
     private CallFilteringResult(boolean shouldAllowCall, boolean shouldReject, boolean
             shouldSilence, boolean shouldAddToCallLog, boolean shouldShowNotification, int
             callBlockReason, CharSequence callScreeningAppName, String callScreeningComponentName,
+            CallScreeningService.ParcelableCallResponse callScreeningResponse,
+            boolean isResponseFromSystemDialer,
             boolean shouldScreenViaAudio, boolean contactExists) {
         this.shouldAllowCall = shouldAllowCall;
         this.shouldReject = shouldReject;
@@ -131,6 +147,8 @@
         this.mCallBlockReason = callBlockReason;
         this.mCallScreeningAppName = callScreeningAppName;
         this.mCallScreeningComponentName = callScreeningComponentName;
+        this.mCallScreeningResponse = callScreeningResponse;
+        this.mIsResponseFromSystemDialer = isResponseFromSystemDialer;
         this.contactExists = contactExists;
     }
 
@@ -148,25 +166,25 @@
 
         if (isBlockedByProvider(mCallBlockReason)) {
             return getCombinedCallFilteringResult(other, mCallBlockReason,
-                null /*callScreeningAppName*/, null /*callScreeningComponentName*/);
+                    null /*callScreeningAppName*/, null /*callScreeningComponentName*/);
         } else if (isBlockedByProvider(other.mCallBlockReason)) {
             return getCombinedCallFilteringResult(other, other.mCallBlockReason,
-                null /*callScreeningAppName*/, null /*callScreeningComponentName*/);
+                    null /*callScreeningAppName*/, null /*callScreeningComponentName*/);
         }
 
         if (mCallBlockReason == Calls.BLOCK_REASON_DIRECT_TO_VOICEMAIL
-            || other.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*/);
+                    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);
+                    mCallScreeningAppName, mCallScreeningComponentName);
         } else if (other.shouldReject && other.mCallBlockReason == CallLog.Calls
-            .BLOCK_REASON_CALL_SCREENING_SERVICE) {
+                .BLOCK_REASON_CALL_SCREENING_SERVICE) {
             return getCombinedCallFilteringResult(other, Calls.BLOCK_REASON_CALL_SCREENING_SERVICE,
-                other.mCallScreeningAppName, other.mCallScreeningComponentName);
+                    other.mCallScreeningAppName, other.mCallScreeningComponentName);
         }
 
         if (shouldScreenViaAudio) {
@@ -177,15 +195,16 @@
                     other.mCallScreeningAppName, other.mCallScreeningComponentName);
         }
 
-        return new Builder()
+        Builder b = new Builder()
                 .setShouldAllowCall(shouldAllowCall && other.shouldAllowCall)
                 .setShouldReject(shouldReject || other.shouldReject)
                 .setShouldSilence(shouldSilence || other.shouldSilence)
                 .setShouldAddToCallLog(shouldAddToCallLog && other.shouldAddToCallLog)
                 .setShouldShowNotification(shouldShowNotification && other.shouldShowNotification)
                 .setShouldScreenViaAudio(shouldScreenViaAudio || other.shouldScreenViaAudio)
-                .setContactExists(contactExists || other.contactExists)
-                .build();
+                .setContactExists(contactExists || other.contactExists);
+        combineScreeningResponses(b, this, other);
+        return b.build();
     }
 
     private boolean isBlockedByProvider(int blockReason) {
@@ -201,8 +220,9 @@
     }
 
     private CallFilteringResult getCombinedCallFilteringResult(CallFilteringResult other,
-        int callBlockReason, CharSequence callScreeningAppName, String callScreeningComponentName) {
-        return new Builder()
+            int callBlockReason, CharSequence callScreeningAppName,
+            String callScreeningComponentName) {
+        Builder b = new Builder()
                 .setShouldAllowCall(shouldAllowCall && other.shouldAllowCall)
                 .setShouldReject(shouldReject || other.shouldReject)
                 .setShouldSilence(shouldSilence || other.shouldSilence)
@@ -212,10 +232,33 @@
                 .setCallBlockReason(callBlockReason)
                 .setCallScreeningAppName(callScreeningAppName)
                 .setCallScreeningComponentName(callScreeningComponentName)
-                .setContactExists(contactExists || other.contactExists)
-                .build();
+                .setContactExists(contactExists || other.contactExists);
+        combineScreeningResponses(b, this, other);
+        return b.build();
     }
 
+    private static void combineScreeningResponses(Builder builder, CallFilteringResult r1,
+            CallFilteringResult r2) {
+        if (r1.mIsResponseFromSystemDialer) {
+            builder.setCallScreeningResponse(r1.mCallScreeningResponse, true);
+            builder.setCallScreeningComponentName(r1.mCallScreeningComponentName);
+            builder.setCallScreeningAppName(r1.mCallScreeningAppName);
+        } else if (r2.mIsResponseFromSystemDialer) {
+            builder.setCallScreeningResponse(r2.mCallScreeningResponse, true);
+            builder.setCallScreeningComponentName(r2.mCallScreeningComponentName);
+            builder.setCallScreeningAppName(r2.mCallScreeningAppName);
+        } else {
+            if (r1.mCallScreeningResponse != null) {
+                builder.setCallScreeningResponse(r1.mCallScreeningResponse, false);
+                builder.setCallScreeningComponentName(r1.mCallScreeningComponentName);
+                builder.setCallScreeningAppName(r1.mCallScreeningAppName);
+            } else {
+                builder.setCallScreeningResponse(r2.mCallScreeningResponse, false);
+                builder.setCallScreeningComponentName(r2.mCallScreeningComponentName);
+                builder.setCallScreeningAppName(r2.mCallScreeningAppName);
+            }
+        }
+    }
 
     @Override
     public boolean equals(Object o) {
diff --git a/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java b/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java
index 486cd8d..4a308e0 100644
--- a/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java
+++ b/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java
@@ -25,6 +25,7 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.provider.CallLog;
+import android.telecom.CallScreeningService;
 import android.telecom.Log;
 import android.telecom.TelecomManager;
 
@@ -64,15 +65,44 @@
         }
 
         @Override
-        public void allowCall(String callId) {
-            Long token = Binder.clearCallingIdentity();
+        public void onScreeningResponse(String callId, ComponentName componentName,
+                CallScreeningService.ParcelableCallResponse callResponse) {
+            if (callResponse == null) {
+                Log.w(this, "Null responses are only supposed to happen for outgoing calls");
+                return;
+            }
+            if (callResponse.shouldDisallowCall()) {
+                disallowCall(callId, componentName, callResponse);
+            } else if (callResponse.shouldSilenceCall()) {
+                silenceCall(callId, componentName, callResponse);
+            } else if (callResponse.shouldScreenCallViaAudioProcessing()) {
+                screenCallFurther(callId, componentName, callResponse);
+            } else {
+                allowCall(callId, componentName, callResponse);
+            }
+        }
+
+        public void allowCall(String callId, ComponentName componentName,
+                CallScreeningService.ParcelableCallResponse response) {
+            long token = Binder.clearCallingIdentity();
             Log.startSession("NCSSF.aC");
             try {
                 if (mCall == null || (!mCall.getId().equals(callId))) {
                     Log.w(this, "allowCall, unknown call id: %s", callId);
                 }
-                Log.addEvent(mCall, LogUtils.Events.SCREENING_COMPLETED, mPriorStageResult);
-                mResultFuture.complete(mPriorStageResult);
+                CallFilteringResult result = new CallFilteringResult.Builder()
+                        .setShouldAllowCall(true)
+                        .setShouldReject(false)
+                        .setShouldSilence(false)
+                        .setShouldAddToCallLog(mPriorStageResult.shouldAddToCallLog)
+                        .setShouldShowNotification(mPriorStageResult.shouldShowNotification)
+                        .setCallScreeningAppName(mAppName)
+                        .setCallScreeningComponentName(componentName.flattenToString())
+                        .setCallScreeningResponse(response, isSystemDialer())
+                        .setContactExists(mPriorStageResult.contactExists)
+                        .build();
+                Log.addEvent(mCall, LogUtils.Events.SCREENING_COMPLETED, result);
+                mResultFuture.complete(result);
             } finally {
                 unbindCallScreeningService();
                 Binder.restoreCallingIdentity(token);
@@ -80,24 +110,23 @@
             }
         }
 
-        @Override
-        public void disallowCall(String callId, boolean shouldReject,
-                boolean shouldAddToCallLog, boolean shouldShowNotification,
-                ComponentName componentName) {
+        public void disallowCall(String callId, ComponentName componentName,
+                CallScreeningService.ParcelableCallResponse response) {
             long token = Binder.clearCallingIdentity();
             Log.startSession("NCSSF.dC");
             try {
                 if (mCall != null && mCall.getId().equals(callId)) {
                     CallFilteringResult result = new CallFilteringResult.Builder()
                             .setShouldAllowCall(false)
-                            .setShouldReject(shouldReject)
+                            .setShouldReject(response.shouldRejectCall())
                             .setShouldSilence(false)
-                            .setShouldAddToCallLog(shouldAddToCallLog
+                            .setShouldAddToCallLog(!response.shouldSkipCallLog()
                                     || packageTypeShouldAdd(mPackagetype))
-                            .setShouldShowNotification(shouldShowNotification)
+                            .setShouldShowNotification(!response.shouldSkipNotification())
                             .setCallBlockReason(CallLog.Calls.BLOCK_REASON_CALL_SCREENING_SERVICE)
                             .setCallScreeningAppName(mAppName)
                             .setCallScreeningComponentName(componentName.flattenToString())
+                            .setCallScreeningResponse(response, isSystemDialer())
                             .setContactExists(mPriorStageResult.contactExists)
                             .build();
                     Log.addEvent(mCall, LogUtils.Events.SCREENING_COMPLETED, result);
@@ -113,8 +142,8 @@
             }
         }
 
-        @Override
-        public void silenceCall(String callId) {
+        public void silenceCall(String callId, ComponentName componentName,
+                CallScreeningService.ParcelableCallResponse response) {
             long token = Binder.clearCallingIdentity();
             Log.startSession("NCSSF.sC");
             try {
@@ -125,6 +154,9 @@
                             .setShouldSilence(true)
                             .setShouldAddToCallLog(true)
                             .setShouldShowNotification(true)
+                            .setCallScreeningResponse(response, isSystemDialer())
+                            .setCallScreeningAppName(mAppName)
+                            .setCallScreeningComponentName(componentName.flattenToString())
                             .setContactExists(mPriorStageResult.contactExists)
                             .build();
                     Log.addEvent(mCall, LogUtils.Events.SCREENING_COMPLETED, result);
@@ -140,8 +172,8 @@
             }
         }
 
-        @Override
-        public void screenCallFurther(String callId) {
+        public void screenCallFurther(String callId, ComponentName componentName,
+                CallScreeningService.ParcelableCallResponse response) {
             if (mPackagetype != PACKAGE_TYPE_DEFAULT_DIALER) {
                 throw new SecurityException("Only the default/system dialer may request screen via"
                     + "background call audio");
@@ -158,6 +190,8 @@
                             .setShouldSilence(false)
                             .setShouldScreenViaAudio(true)
                             .setCallScreeningAppName(mAppName)
+                            .setCallScreeningComponentName(componentName.flattenToString())
+                            .setCallScreeningResponse(response, isSystemDialer())
                             .setContactExists(mPriorStageResult.contactExists)
                             .build();
                     Log.addEvent(mCall, LogUtils.Events.SCREENING_COMPLETED, result);
diff --git a/src/com/android/server/telecom/callfiltering/IncomingCallFilter.java b/src/com/android/server/telecom/callfiltering/IncomingCallFilter.java
deleted file mode 100644
index 860de1f..0000000
--- a/src/com/android/server/telecom/callfiltering/IncomingCallFilter.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.telecom.callfiltering;
-
-import android.content.Context;
-import android.os.Handler;
-import android.os.Looper;
-import android.telecom.Log;
-import android.telecom.Logging.Runnable;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.telecom.Call;
-import com.android.server.telecom.LogUtils;
-import com.android.server.telecom.TelecomSystem;
-import com.android.server.telecom.Timeouts;
-import com.android.server.telecom.callfiltering.CallFilteringResult.Builder;
-
-import java.util.List;
-
-public class IncomingCallFilter implements CallFilterResultCallback {
-
-    public static class Factory {
-        public IncomingCallFilter create(Context context, CallFilterResultCallback listener,
-                Call call, TelecomSystem.SyncRoot lock, Timeouts.Adapter timeoutsAdapter,
-                List<CallFilter> filters) {
-            return new IncomingCallFilter(context, listener, call, lock, timeoutsAdapter, filters,
-                    new Handler(Looper.getMainLooper()));
-        }
-    }
-
-    public interface CallFilter {
-        void startFilterLookup(Call call, CallFilterResultCallback listener);
-    }
-
-    private final TelecomSystem.SyncRoot mTelecomLock;
-    private final Context mContext;
-    private final Handler mHandler;
-    private final List<CallFilter> mFilters;
-    private final Call mCall;
-    private final CallFilterResultCallback mListener;
-    private final Timeouts.Adapter mTimeoutsAdapter;
-
-    private CallFilteringResult mResult = new Builder()
-            .setShouldAllowCall(true)
-            .setShouldReject(false)
-            .setShouldAddToCallLog(true)
-            .setShouldShowNotification(true)
-            .build();
-
-    private boolean mIsPending = true;
-    private int mNumPendingFilters;
-
-    public IncomingCallFilter(Context context, CallFilterResultCallback listener, Call call,
-            TelecomSystem.SyncRoot lock, Timeouts.Adapter timeoutsAdapter,
-            List<CallFilter> filters, Handler handler) {
-        mContext = context;
-        mListener = listener;
-        mCall = call;
-        mTelecomLock = lock;
-        mFilters = filters;
-        mNumPendingFilters = filters.size();
-        mTimeoutsAdapter = timeoutsAdapter;
-        mHandler = handler;
-    }
-
-    public void performFiltering() {
-        Log.addEvent(mCall, LogUtils.Events.FILTERING_INITIATED);
-        for (CallFilter filter : mFilters) {
-            filter.startFilterLookup(mCall, this);
-        }
-        // synchronized to prevent a race on mResult and to enter into Telecom.
-        mHandler.postDelayed(new Runnable("ICF.pFTO", mTelecomLock) { // performFiltering time-out
-            @Override
-            public void loggedRun() {
-                if (mIsPending) {
-                    Log.i(IncomingCallFilter.this, "Call filtering has timed out.");
-                    Log.addEvent(mCall, LogUtils.Events.FILTERING_TIMED_OUT);
-                    mListener.onCallFilteringComplete(mCall, mResult);
-                    mIsPending = false;
-                }
-            }
-        }.prepare(), mTimeoutsAdapter.getCallScreeningTimeoutMillis(mContext.getContentResolver()));
-    }
-
-    public void onCallFilteringComplete(Call call, CallFilteringResult result) {
-        synchronized (mTelecomLock) { // synchronizing to prevent race on mResult
-            mNumPendingFilters--;
-            mResult = result.combine(mResult);
-            if (mNumPendingFilters == 0) {
-                // synchronized on mTelecomLock to enter into Telecom.
-                mHandler.post(new Runnable("ICF.oCFC", mTelecomLock) {
-                    @Override
-                    public void loggedRun() {
-                        if (mIsPending) {
-                            Log.addEvent(mCall, LogUtils.Events.FILTERING_COMPLETED, mResult);
-                            mListener.onCallFilteringComplete(mCall, mResult);
-                            mIsPending = false;
-                        }
-                    }
-                }.prepare());
-            }
-        }
-    }
-
-    /**
-     * Returns the handler, for testing purposes.
-     */
-    @VisibleForTesting
-    public Handler getHandler() {
-        return mHandler;
-    }
-}
diff --git a/src/com/android/server/telecom/callfiltering/IncomingCallFilterGraph.java b/src/com/android/server/telecom/callfiltering/IncomingCallFilterGraph.java
index 1543270..9fa864e 100644
--- a/src/com/android/server/telecom/callfiltering/IncomingCallFilterGraph.java
+++ b/src/com/android/server/telecom/callfiltering/IncomingCallFilterGraph.java
@@ -49,7 +49,7 @@
     private final HandlerThread mHandlerThread;
     private final TelecomSystem.SyncRoot mLock;
     private List<CallFilter> mFiltersList;
-    private CallFilter mDummyComplete;
+    private CallFilter mCompletionSentinel;
     private boolean mFinished;
     private CallFilteringResult mCurrentResult;
     private Context mContext;
@@ -70,10 +70,10 @@
                     scheduleFilter(filter);
                 }
             }
-            if (mFilter.equals(mDummyComplete)) {
+            if (mFilter.equals(mCompletionSentinel)) {
                 synchronized (mLock) {
                     mFinished = true;
-                    mListener.onCallFilteringComplete(mCall, result);
+                    mListener.onCallFilteringComplete(mCall, result, false);
                     Log.addEvent(mCall, LogUtils.Events.FILTERING_COMPLETED, result);
                 }
                 mHandlerThread.quit();
@@ -105,15 +105,15 @@
     public void performFiltering() {
         Log.addEvent(mCall, LogUtils.Events.FILTERING_INITIATED);
         CallFilter dummyStart = new CallFilter();
-        mDummyComplete = new CallFilter();
+        mCompletionSentinel = new CallFilter();
 
         for (CallFilter filter : mFiltersList) {
             addEdge(dummyStart, filter);
         }
         for (CallFilter filter : mFiltersList) {
-            addEdge(filter, mDummyComplete);
+            addEdge(filter, mCompletionSentinel);
         }
-        addEdge(dummyStart, mDummyComplete);
+        addEdge(dummyStart, mCompletionSentinel);
 
         scheduleFilter(dummyStart);
         mHandler.postDelayed(new Runnable("ICFG.pF", mLock) {
@@ -122,7 +122,7 @@
                 if (!mFinished) {
                     Log.i(this, "Graph timed out when performing filtering.");
                     Log.addEvent(mCall, LogUtils.Events.FILTERING_TIMED_OUT);
-                    mListener.onCallFilteringComplete(mCall, mCurrentResult);
+                    mListener.onCallFilteringComplete(mCall, mCurrentResult, true);
                     mFinished = true;
                     mHandlerThread.quit();
                 }
@@ -159,7 +159,11 @@
         startFuture.thenComposeAsync(filter::startFilterLookup,
                 new LoggedHandlerExecutor(mHandler, "ICFG.sF", null))
                 .thenApplyAsync(postFilterTask::whenDone,
-                        new LoggedHandlerExecutor(mHandler, "ICFG.sF", null));
+                        new LoggedHandlerExecutor(mHandler, "ICFG.sF", null))
+                .exceptionally((t) -> {
+                    Log.e(filter, t, "Encountered exception running filter");
+                    return null;
+                });
         Log.i(TAG, "Filter %s scheduled.", filter);
     }
 
diff --git a/src/com/android/server/telecom/callredirection/CallRedirectionProcessor.java b/src/com/android/server/telecom/callredirection/CallRedirectionProcessor.java
index e93ef22..a1f357b 100644
--- a/src/com/android/server/telecom/callredirection/CallRedirectionProcessor.java
+++ b/src/com/android/server/telecom/callredirection/CallRedirectionProcessor.java
@@ -294,7 +294,7 @@
          * 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();
+        mAllowInteractiveResponse = !callsManager.getSystemStateHelper().isCarModeOrProjectionActive();
         mCallRedirectionProcessorHelper = new CallRedirectionProcessorHelper(
                 context, callsManager, phoneAccountRegistrar);
         mProcessedDestinationUri = mCallRedirectionProcessorHelper.formatNumberForRedirection(
diff --git a/src/com/android/server/telecom/components/BluetoothPhoneService.java b/src/com/android/server/telecom/components/BluetoothPhoneService.java
deleted file mode 100644
index c5e195c..0000000
--- a/src/com/android/server/telecom/components/BluetoothPhoneService.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.telecom.components;
-
-import com.android.server.telecom.TelecomSystem;
-
-import android.app.Service;
-import android.content.Intent;
-import android.os.IBinder;
-
-/**
- * Bluetooth headset manager for Telecom. This class shares the call state with the bluetooth device
- * and accepts call-related commands to perform on behalf of the BT device.
- */
-public final class BluetoothPhoneService extends Service implements TelecomSystem.Component {
-
-    @Override
-    public IBinder onBind(Intent intent) {
-        synchronized (getTelecomSystem().getLock()) {
-            return getTelecomSystem().getBluetoothPhoneServiceImpl().getBinder();
-        }
-    }
-
-    @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 b6705f4..9ad0da4 100644
--- a/src/com/android/server/telecom/components/TelecomService.java
+++ b/src/com/android/server/telecom/components/TelecomService.java
@@ -18,7 +18,6 @@
 
 import android.app.Service;
 import android.app.role.RoleManager;
-import android.bluetooth.BluetoothAdapter;
 import android.content.Context;
 import android.content.Intent;
 import android.media.IAudioService;
@@ -35,8 +34,6 @@
 import com.android.internal.telecom.ITelecomLoader;
 import com.android.internal.telecom.ITelecomService;
 import com.android.server.telecom.AsyncRingtonePlayer;
-import com.android.server.telecom.BluetoothAdapterProxy;
-import com.android.server.telecom.BluetoothPhoneServiceImpl;
 import com.android.server.telecom.CallAudioModeStateMachine;
 import com.android.server.telecom.CallAudioRouteStateMachine;
 import com.android.server.telecom.CallerInfoAsyncQueryFactory;
@@ -60,7 +57,6 @@
 import com.android.server.telecom.TelecomSystem;
 import com.android.server.telecom.TelecomWakeLock;
 import com.android.server.telecom.Timeouts;
-import com.android.server.telecom.callfiltering.IncomingCallFilter;
 import com.android.server.telecom.ui.IncomingCallNotifier;
 import com.android.server.telecom.ui.MissedCallNotifierImpl;
 import com.android.server.telecom.ui.NotificationChannelManager;
@@ -173,17 +169,6 @@
                                             ServiceManager.getService(Context.AUDIO_SERVICE));
                                 }
                             },
-                            new BluetoothPhoneServiceImpl.BluetoothPhoneServiceImplFactory() {
-                                @Override
-                                public BluetoothPhoneServiceImpl makeBluetoothPhoneServiceImpl(
-                                        Context context, TelecomSystem.SyncRoot lock,
-                                        CallsManager callsManager,
-                                        PhoneAccountRegistrar phoneAccountRegistrar) {
-                                    return new BluetoothPhoneServiceImpl(context, lock,
-                                            callsManager, new BluetoothAdapterProxy(),
-                                            phoneAccountRegistrar);
-                                }
-                            },
                             ConnectionServiceFocusManager::new,
                             new Timeouts.Adapter(),
                             new AsyncRingtonePlayer(),
@@ -205,13 +190,9 @@
                             },
                             new RoleManagerAdapterImpl(context,
                                     (RoleManager) context.getSystemService(Context.ROLE_SERVICE)),
-                            new IncomingCallFilter.Factory(),
                             new ContactsAsyncHelper.Factory(),
                             internalServiceRetriever.getDeviceIdleController()));
         }
-        if (BluetoothAdapter.getDefaultAdapter() != null) {
-            context.startService(new Intent(context, BluetoothPhoneService.class));
-        }
     }
 
     @Override
diff --git a/src/com/android/server/telecom/settings/BlockedNumbersActivity.java b/src/com/android/server/telecom/settings/BlockedNumbersActivity.java
index 3549db5..1fe7c5f 100644
--- a/src/com/android/server/telecom/settings/BlockedNumbersActivity.java
+++ b/src/com/android/server/telecom/settings/BlockedNumbersActivity.java
@@ -38,6 +38,7 @@
 import android.provider.ContactsContract;
 import android.telephony.PhoneNumberFormattingTextWatcher;
 import android.telephony.PhoneNumberUtils;
+import android.telephony.TelephonyManager;
 import android.text.Editable;
 import android.text.InputType;
 import android.text.TextUtils;
@@ -285,7 +286,7 @@
      * Add blocked number if it does not exist.
      */
     private void addBlockedNumber(String number) {
-        if (PhoneNumberUtils.isEmergencyNumber(number)) {
+        if (isEmergencyNumber(this, number)) {
             Toast.makeText(
                     this,
                     getString(R.string.blocked_numbers_block_emergency_number_message),
@@ -298,6 +299,16 @@
         }
     }
 
+    private boolean isEmergencyNumber(Context context, String number) {
+        try {
+            TelephonyManager tm = (TelephonyManager) context.getSystemService(
+                    Context.TELEPHONY_SERVICE);
+            return tm.isEmergencyNumber(number);
+        } catch (IllegalStateException ise) {
+            return false;
+        }
+    }
+
     @Override
     public void beforeTextChanged(CharSequence s, int start, int count, int after) {
         // no-op
diff --git a/src/com/android/server/telecom/settings/BlockedNumbersUtil.java b/src/com/android/server/telecom/settings/BlockedNumbersUtil.java
index 67634e4..4be75f8 100644
--- a/src/com/android/server/telecom/settings/BlockedNumbersUtil.java
+++ b/src/com/android/server/telecom/settings/BlockedNumbersUtil.java
@@ -95,7 +95,8 @@
         if (showNotification) {
             Intent intent = new Intent(context, CallBlockDisabledActivity.class);
             PendingIntent pendingIntent = PendingIntent.getActivity(
-                    context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
+                    context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT
+                            | PendingIntent.FLAG_IMMUTABLE);
 
             String title = context.getString(
                     R.string.phone_strings_call_blocking_turned_off_notification_title_txt);
diff --git a/src/com/android/server/telecom/ui/DisconnectedCallNotifier.java b/src/com/android/server/telecom/ui/DisconnectedCallNotifier.java
index 3f54689..66f9fe4 100644
--- a/src/com/android/server/telecom/ui/DisconnectedCallNotifier.java
+++ b/src/com/android/server/telecom/ui/DisconnectedCallNotifier.java
@@ -333,7 +333,8 @@
             UserHandle userHandle) {
         Intent intent = new Intent(action, data, mContext, TelecomBroadcastReceiver.class);
         intent.putExtra(TelecomBroadcastIntentProcessor.EXTRA_USERHANDLE, userHandle);
-        return PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+        return PendingIntent.getBroadcast(mContext, 0, intent,
+                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
     }
 
     private boolean canRespondViaSms(@NonNull CallInfo call) {
@@ -354,7 +355,7 @@
         TaskStackBuilder taskStackBuilder = TaskStackBuilder.create(mContext);
         taskStackBuilder.addNextIntent(intent);
 
-        return taskStackBuilder.getPendingIntent(0, 0, null, userHandle);
+        return taskStackBuilder.getPendingIntent(0, PendingIntent.FLAG_IMMUTABLE, null, userHandle);
     }
 
     /**
diff --git a/src/com/android/server/telecom/ui/IncomingCallNotifier.java b/src/com/android/server/telecom/ui/IncomingCallNotifier.java
index 6e203aa..0c1c5a3 100644
--- a/src/com/android/server/telecom/ui/IncomingCallNotifier.java
+++ b/src/com/android/server/telecom/ui/IncomingCallNotifier.java
@@ -41,6 +41,7 @@
 import com.android.server.telecom.TelecomBroadcastIntentProcessor;
 import com.android.server.telecom.components.TelecomBroadcastReceiver;
 
+import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
 
@@ -68,7 +69,7 @@
     public static final int NOTIFICATION_INCOMING_CALL = 1;
     @VisibleForTesting
     public static final String NOTIFICATION_TAG = IncomingCallNotifier.class.getSimpleName();
-
+    private final Object mLock = new Object();
 
     public final Call.ListenerBase mCallListener = new Call.ListenerBase() {
         @Override
@@ -104,8 +105,10 @@
 
     @Override
     public void onCallAdded(Call call) {
-        if (!mCalls.contains(call)) {
-            mCalls.add(call);
+        synchronized (mLock) {
+            if (!mCalls.contains(call)) {
+                mCalls.add(call);
+            }
         }
 
         updateIncomingCall();
@@ -113,10 +116,11 @@
 
     @Override
     public void onCallRemoved(Call call) {
-        if (mCalls.contains(call)) {
-            mCalls.remove(call);
+        synchronized (mLock) {
+            if (mCalls.contains(call)) {
+                mCalls.remove(call);
+            }
         }
-
         updateIncomingCall();
     }
 
@@ -130,11 +134,16 @@
      * UI.
      */
     private void updateIncomingCall() {
-        Optional<Call> incomingCallOp = mCalls.stream()
-                .filter(call -> call.isSelfManaged() && call.isIncoming() &&
-                        call.getState() == CallState.RINGING &&
-                        call.getHandoverState() == HandoverState.HANDOVER_NONE)
-                .findFirst();
+        Optional<Call> incomingCallOp;
+        synchronized (mLock) {
+            incomingCallOp = mCalls.stream()
+                    .filter(Objects::nonNull)
+                    .filter(call -> call.isSelfManaged() && call.isIncoming() &&
+                            call.getState() == CallState.RINGING &&
+                            call.getHandoverState() == HandoverState.HANDOVER_NONE)
+                    .findFirst();
+        }
+
         Call incomingCall = incomingCallOp.orElse(null);
         if (incomingCall != null && mCallsManagerProxy != null &&
                 !mCallsManagerProxy.hasUnholdableCallsForOtherConnectionService(
@@ -273,12 +282,12 @@
                 R.anim.on_going_call,
                 getActionText(R.string.answer_incoming_call, R.color.notification_action_answer),
                 PendingIntent.getBroadcast(mContext, 0, answerIntent,
-                        PendingIntent.FLAG_CANCEL_CURRENT));
+                        PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE));
         builder.addAction(
                 R.drawable.ic_close_dk,
                 getActionText(R.string.decline_incoming_call, R.color.notification_action_decline),
                 PendingIntent.getBroadcast(mContext, 0, rejectIntent,
-                        PendingIntent.FLAG_CANCEL_CURRENT));
+                        PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE));
         return builder;
     }
 
diff --git a/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java b/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java
index e37fe6b..12d3820 100644
--- a/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java
+++ b/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java
@@ -67,18 +67,15 @@
 import android.text.TextUtils;
 
 import android.telecom.CallerInfo;
+import android.util.ArrayMap;
 
 import java.lang.Override;
 import java.lang.String;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
+import java.util.Map;
 import java.util.Objects;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.atomic.AtomicInteger;
-
-// TODO: Needed for move to system service: import com.android.internal.R;
 
 /**
  * Creates a notification for calls that the user missed (neither answered nor rejected).
@@ -139,8 +136,10 @@
     private final DeviceIdleControllerAdapter mDeviceIdleControllerAdapter;
     private UserHandle mCurrentUserHandle;
 
+    // Used to guard access to mMissedCallCounts
+    private final Object mMissedCallCountsLock = new Object();
     // Used to track the number of missed calls.
-    private ConcurrentMap<UserHandle, AtomicInteger> mMissedCallCounts;
+    private final Map<UserHandle, Integer> mMissedCallCounts;
 
     private List<UserHandle> mUsersToLoadAfterBootComplete = new ArrayList<>();
 
@@ -164,7 +163,7 @@
         mDefaultDialerCache = defaultDialerCache;
 
         mNotificationBuilderFactory = notificationBuilderFactory;
-        mMissedCallCounts = new ConcurrentHashMap<>();
+        mMissedCallCounts = new ArrayMap<>();
     }
 
     /** Clears missed call notification and marks the call log's missed calls as read. */
@@ -263,17 +262,16 @@
     }
 
     private void sendNotificationThroughDefaultDialer(String dialerPackage, CallInfo callInfo,
-            UserHandle userHandle) {
-        int count = mMissedCallCounts.get(userHandle).get();
+            UserHandle userHandle, int missedCallCount) {
         Intent intent = getShowMissedCallIntentForDefaultDialer(dialerPackage)
             .setFlags(Intent.FLAG_RECEIVER_FOREGROUND)
             .putExtra(TelecomManager.EXTRA_CLEAR_MISSED_CALLS_INTENT,
                     createClearMissedCallsPendingIntent(userHandle))
-            .putExtra(TelecomManager.EXTRA_NOTIFICATION_COUNT, count)
+            .putExtra(TelecomManager.EXTRA_NOTIFICATION_COUNT, missedCallCount)
             .putExtra(TelecomManager.EXTRA_NOTIFICATION_PHONE_NUMBER,
                     callInfo == null ? null : callInfo.getPhoneNumber());
 
-        if (count == 1 && callInfo != null) {
+        if (missedCallCount == 1 && callInfo != null) {
             final Uri handleUri = callInfo.getHandle();
             String handle = handleUri == null ? null : handleUri.getSchemeSpecificPart();
 
@@ -284,8 +282,8 @@
             }
         }
 
-
-        Log.w(this, "Showing missed calls through default dialer.");
+        Log.i(this, "sendNotificationThroughDefaultDialer; count=%d, dialerPackage=%s",
+                missedCallCount, intent.getPackage());
         Bundle options = exemptFromPowerSavingTemporarily(dialerPackage, userHandle);
         mContext.sendBroadcastAsUser(intent, userHandle, READ_PHONE_STATE, options);
     }
@@ -293,7 +291,7 @@
     /**
      * Create a system notification for the missed call.
      *
-     * @param call The missed call.
+     * @param callInfo The missed call.
      */
     @Override
     public void showMissedCallNotification(@NonNull CallInfo callInfo) {
@@ -311,13 +309,21 @@
     }
 
     private void showMissedCallNotification(@NonNull CallInfo callInfo, UserHandle userHandle) {
-        Log.i(this, "showMissedCallNotification: userHandle=%d", userHandle.getIdentifier());
-        mMissedCallCounts.putIfAbsent(userHandle, new AtomicInteger(0));
-        int missCallCounts = mMissedCallCounts.get(userHandle).incrementAndGet();
+        int missedCallCounts;
+        synchronized (mMissedCallCountsLock) {
+            Integer currentCount = mMissedCallCounts.get(userHandle);
+            missedCallCounts = currentCount == null ? 0 : currentCount;
+            missedCallCounts++;
+            mMissedCallCounts.put(userHandle, missedCallCounts);
+        }
+
+        Log.i(this, "showMissedCallNotification: userHandle=%d, missedCallCount=%d",
+                userHandle.getIdentifier(), missedCallCounts);
 
         String dialerPackage = getDefaultDialerPackage(userHandle);
         if (shouldManageNotificationThroughDefaultDialer(dialerPackage, userHandle)) {
-            sendNotificationThroughDefaultDialer(dialerPackage, callInfo, userHandle);
+            sendNotificationThroughDefaultDialer(dialerPackage, callInfo, userHandle,
+                    missedCallCounts);
             return;
         }
 
@@ -327,7 +333,7 @@
         // Display the first line of the notification:
         // 1 missed call: <caller name || handle>
         // More than 1 missed call: <number of calls> + "missed calls"
-        if (missCallCounts == 1) {
+        if (missedCallCounts == 1) {
             expandedText = getNameForMissedCallNotification(callInfo);
 
             CallerInfo ci = callInfo.getCallerInfo();
@@ -339,7 +345,7 @@
         } else {
             titleResId = R.string.notification_missedCallsTitle;
             expandedText =
-                    mContext.getString(R.string.notification_missedCallsMsg, missCallCounts);
+                    mContext.getString(R.string.notification_missedCallsMsg, missedCallCounts);
         }
 
         // Create a public viewable version of the notification, suitable for display when sensitive
@@ -381,7 +387,7 @@
         String handle = callInfo.getHandleSchemeSpecificPart();
 
         // Add additional actions when there is only 1 missed call, like call-back and SMS.
-        if (missCallCounts == 1) {
+        if (missedCallCounts == 1) {
             Log.d(this, "Add actions with number %s.", Log.piiHandle(handle));
 
             if (!TextUtils.isEmpty(handle)
@@ -410,7 +416,7 @@
             }
         } else {
             Log.d(this, "Suppress actions. handle: %s, missedCalls: %d.", Log.piiHandle(handle),
-                    missCallCounts);
+                    missedCallCounts);
         }
 
         Notification notification = builder.build();
@@ -430,12 +436,14 @@
     /** Cancels the "missed call" notification. */
     private void cancelMissedCallNotification(UserHandle userHandle) {
         // Reset the number of missed calls to 0.
-        mMissedCallCounts.putIfAbsent(userHandle, new AtomicInteger(0));
-        mMissedCallCounts.get(userHandle).set(0);
+        synchronized(mMissedCallCountsLock) {
+            mMissedCallCounts.put(userHandle, 0);
+        }
 
         String dialerPackage = getDefaultDialerPackage(userHandle);
         if (shouldManageNotificationThroughDefaultDialer(dialerPackage, userHandle)) {
-            sendNotificationThroughDefaultDialer(dialerPackage, null, userHandle);
+            sendNotificationThroughDefaultDialer(dialerPackage, null, userHandle,
+                    0 /* missedCallCount */);
             return;
         }
 
@@ -559,7 +567,8 @@
             UserHandle userHandle) {
         Intent intent = new Intent(action, data, mContext, TelecomBroadcastReceiver.class);
         intent.putExtra(TelecomBroadcastIntentProcessor.EXTRA_USERHANDLE, userHandle);
-        return PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+        return PendingIntent.getBroadcast(mContext, 0, intent,
+                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
     }
 
     /**
@@ -611,7 +620,9 @@
                 Log.d(MissedCallNotifierImpl.this, "onQueryComplete()...");
                 if (cursor != null) {
                     try {
-                        mMissedCallCounts.remove(userHandle);
+                        synchronized(mMissedCallCountsLock) {
+                            mMissedCallCounts.remove(userHandle);
+                        }
                         while (cursor.moveToNext()) {
                             // Get data about the missed call from the cursor
                             final String handleString = cursor.getString(CALL_LOG_COLUMN_NUMBER);
diff --git a/testapps/Android.bp b/testapps/Android.bp
index 26347fe..11ea474 100644
--- a/testapps/Android.bp
+++ b/testapps/Android.bp
@@ -14,6 +14,10 @@
 // limitations under the License.
 //
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 android_test {
     name: "TelecomTestApps",
     static_libs: [
diff --git a/testapps/AndroidManifest.xml b/testapps/AndroidManifest.xml
index 4238191..dd8258a 100644
--- a/testapps/AndroidManifest.xml
+++ b/testapps/AndroidManifest.xml
@@ -15,40 +15,48 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          coreApp="true"
-          package="com.android.server.telecom.testapps">
+     coreApp="true"
+     package="com.android.server.telecom.testapps">
 
-    <uses-sdk
-        android:minSdkVersion="28"
-        android:targetSdkVersion="30" />
+    <uses-sdk android:minSdkVersion="28"
+         android:targetSdkVersion="30"/>
 
-    <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" />
-    <uses-permission android:name="android.permission.CONTROL_INCALL_EXPERIENCE" />
-    <uses-permission android:name="android.permission.INTERNET" />
-    <uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
-    <uses-permission android:name="android.permission.READ_CALL_LOG" />
-    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
-    <uses-permission android:name="android.permission.REGISTER_CALL_PROVIDER" />
-    <uses-permission android:name="android.permission.REGISTER_CONNECTION_MANAGER" />
-    <uses-permission android:name="android.permission.REGISTER_SIM_SUBSCRIPTION" />
-    <uses-permission android:name="android.permission.WRITE_CALL_LOG" />
+    <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"/>
+    <uses-permission android:name="android.permission.CONTROL_INCALL_EXPERIENCE"/>
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.MANAGE_OWN_CALLS"/>
+    <uses-permission android:name="android.permission.READ_CALL_LOG"/>
+    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
+    <uses-permission android:name="android.permission.REGISTER_CALL_PROVIDER"/>
+    <uses-permission android:name="android.permission.REGISTER_CONNECTION_MANAGER"/>
+    <uses-permission android:name="android.permission.REGISTER_SIM_SUBSCRIPTION"/>
+    <uses-permission android:name="android.permission.WRITE_CALL_LOG"/>
 
     <application android:label="@string/app_name">
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
         <!-- Miscellaneous telecom app-related test activities. -->
-
-        <service android:name="com.android.server.telecom.testapps.TestConnectionService"
-                 android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE" >
+        <service android:name="com.android.server.telecom.testapps.TestCallDiagnosticService"
+            android:permission="android.permission.BIND_CALL_DIAGNOSTIC_SERVICE"
+            android:exported="true">
             <intent-filter>
-                <action android:name="android.telecom.ConnectionService" />
+                <action android:name="android.telecom.CallDiagnosticService"/>
             </intent-filter>
         </service>
 
-        <receiver android:name=".TestConnectionServiceReceiver">
+        <service android:name="com.android.server.telecom.testapps.TestConnectionService"
+             android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.telecom.ConnectionService"/>
+            </intent-filter>
+        </service>
+
+        <receiver android:name=".TestConnectionServiceReceiver"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.server.telecom.testapps.ACTION_SWITCH_PHONE_ACCOUNT"/>
                 <action android:name="android.server.telecom.testapps.ACTION_SWITCH_PHONE_ACCOUNT_WRONG"/>
@@ -56,23 +64,29 @@
         </receiver>
 
         <service android:name="com.android.server.telecom.testapps.TestConnectionManager"
-                 android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE" >
+             android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.telecom.ConnectionService" />
+                <action android:name="android.telecom.ConnectionService"/>
             </intent-filter>
         </service>
 
         <service android:name="com.android.server.telecom.testapps.TestInCallServiceImpl"
-                 android:process="com.android.server.telecom.testapps.TestInCallService"
-                 android:permission="android.permission.BIND_INCALL_SERVICE" >
-            <meta-data android:name="android.telecom.IN_CALL_SERVICE_UI" android:value="true"/>
+             android:process="com.android.server.telecom.testapps.TestInCallService"
+             android:permission="android.permission.BIND_INCALL_SERVICE"
+             android:exported="true">
+            <meta-data android:name="android.telecom.IN_CALL_SERVICE_UI"
+                 android:value="true"/>
+            <meta-data android:name="android.telecom.INCLUDE_SELF_MANAGED_CALLS"
+                 android:value="true" />
             <intent-filter>
                 <action android:name="android.telecom.InCallService"/>
             </intent-filter>
         </service>
 
         <receiver android:name="com.android.server.telecom.testapps.TestInCallServiceBroadcastReceiver"
-                 android:process="com.android.server.telecom.testapps.TestInCallService" >
+             android:process="com.android.server.telecom.testapps.TestInCallService"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.server.telecom.testapps.ACTION_SEND_UPDATE_REQUEST_FROM_TEST_INCALL_SERVICE"/>
                 <action android:name="android.server.telecom.testapps.ACTION_SEND_UPGRADE_RESPONSE"/>
@@ -84,201 +98,212 @@
 
 
         <activity android:name="com.android.server.telecom.testapps.TestInCallUI"
-                android:process="com.android.server.telecom.testapps.TestInCallService"
-                android:label="@string/inCallUiAppLabel"
-                android:launchMode="singleInstance">
+             android:process="com.android.server.telecom.testapps.TestInCallService"
+             android:label="@string/inCallUiAppLabel"
+             android:launchMode="singleInstance"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
         <activity android:name="com.android.server.telecom.testapps.TestRttActivity"
-                  android:process="com.android.server.telecom.testapps.TestInCallService"
-                  android:label="@string/rttUiLabel"
-                  android:launchMode="singleInstance">
+             android:process="com.android.server.telecom.testapps.TestInCallService"
+             android:label="@string/rttUiLabel"
+             android:launchMode="singleInstance"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
 
         <activity android:name="com.android.server.telecom.testapps.TestCallActivity"
-                  android:theme="@android:style/Theme.NoDisplay"
-                  android:label="@string/testCallActivityLabel">
+             android:theme="@android:style/Theme.NoDisplay"
+             android:label="@string/testCallActivityLabel"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.telecom.testapps.ACTION_START_INCOMING_CALL" />
-                <action android:name="android.telecom.testapps.ACTION_NEW_UNKNOWN_CALL" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:scheme="tel" />
-                <data android:scheme="sip" />
+                <action android:name="android.telecom.testapps.ACTION_START_INCOMING_CALL"/>
+                <action android:name="android.telecom.testapps.ACTION_NEW_UNKNOWN_CALL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:scheme="tel"/>
+                <data android:scheme="sip"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.telecom.testapps.ACTION_HANGUP_CALLS" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.telecom.testapps.ACTION_HANGUP_CALLS"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.telecom.testapps.ACTION_SEND_UPGRADE_REQUEST" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:scheme="int" />
+                <action android:name="android.telecom.testapps.ACTION_SEND_UPGRADE_REQUEST"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:scheme="int"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.telecom.testapps.ACTION_RTT_CALL" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <data android:scheme="tel" />
+                <action android:name="android.telecom.testapps.ACTION_RTT_CALL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:scheme="tel"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.telecom.testapps.ACTION_REMOTE_RTT_UPGRADE" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.telecom.testapps.ACTION_REMOTE_RTT_UPGRADE"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
 
         <receiver android:name="com.android.server.telecom.testapps.CallNotificationReceiver"
-                  android:exported="false">
+             android:exported="false">
             <intent-filter>
-                <action android:name="com.android.server.telecom.testapps.ACTION_CALL_SERVICE_EXIT" />
+                <action android:name="com.android.server.telecom.testapps.ACTION_CALL_SERVICE_EXIT"/>
             </intent-filter>
         </receiver>
 
         <activity android:name="com.android.server.telecom.testapps.TestDialerActivity"
-                  android:label="@string/testDialerActivityLabel"
-                  android:process="com.android.server.telecom.testapps.TestInCallService">
+             android:label="@string/testDialerActivityLabel"
+             android:process="com.android.server.telecom.testapps.TestInCallService"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.DIAL" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:mimeType="vnd.android.cursor.item/phone" />
-                <data android:mimeType="vnd.android.cursor.item/person" />
+                <action android:name="android.intent.action.DIAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:mimeType="vnd.android.cursor.item/phone"/>
+                <data android:mimeType="vnd.android.cursor.item/person"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.DIAL" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="voicemail" />
+                <action android:name="android.intent.action.DIAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="voicemail"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.DIAL" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.intent.action.DIAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.VIEW" />
-                <action android:name="android.intent.action.DIAL" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="tel" />
+                <action android:name="android.intent.action.VIEW"/>
+                <action android:name="android.intent.action.DIAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="tel"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
         <activity android:name="com.android.server.telecom.testapps.TestUssdActivity"
-                android:label="@string/UssdUiAppLabel"
-                android:launchMode="singleInstance">
+             android:label="@string/UssdUiAppLabel"
+             android:launchMode="singleInstance"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
           </activity>
 
         <activity android:name="com.android.server.telecom.testapps.TestCertActivity"
-                android:label="@string/KeyUiAppLabel"
-                android:launchMode="singleInstance">
+             android:label="@string/KeyUiAppLabel"
+             android:launchMode="singleInstance"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
           </activity>
 
         <activity android:name="com.android.server.telecom.testapps.SelfManagedCallingActivity"
-                  android:label="@string/selfManagedCallingActivityLabel"
-                  android:process="com.android.server.telecom.testapps.SelfMangingCallingApp"
-                  android:theme="@android:style/Theme.Material.Light">
+             android:label="@string/selfManagedCallingActivityLabel"
+             android:process="com.android.server.telecom.testapps.SelfMangingCallingApp"
+             android:theme="@android:style/Theme.Material.Light"
+             android:exported="true">
           <intent-filter>
-              <action android:name="android.intent.action.MAIN" />
-              <category android:name="android.intent.category.DEFAULT" />
-              <category android:name="android.intent.category.LAUNCHER" />
+              <action android:name="android.intent.action.MAIN"/>
+              <category android:name="android.intent.category.DEFAULT"/>
+              <category android:name="android.intent.category.LAUNCHER"/>
           </intent-filter>
         </activity>
 
         <activity android:name="com.android.server.telecom.testapps.IncomingSelfManagedCallActivity"
-                  android:label="@string/selfManagedCallingActivityLabel"
-                  android:process="com.android.server.telecom.testapps.SelfMangingCallingApp">
+             android:label="@string/selfManagedCallingActivityLabel"
+             android:process="com.android.server.telecom.testapps.SelfMangingCallingApp"
+             android:exported="true">
           <intent-filter>
-              <action android:name="android.intent.action.MAIN" />
+              <action android:name="android.intent.action.MAIN"/>
           </intent-filter>
         </activity>
 
         <activity android:name="com.android.server.telecom.testapps.HandoverActivity"
-                  android:label="@string/selfManagedCallingActivityLabel"
-                  android:process="com.android.server.telecom.testapps.SelfMangingCallingApp">
+             android:label="@string/selfManagedCallingActivityLabel"
+             android:process="com.android.server.telecom.testapps.SelfMangingCallingApp"
+             android:exported="true">
           <intent-filter>
-              <action android:name="android.intent.action.MAIN" />
+              <action android:name="android.intent.action.MAIN"/>
           </intent-filter>
         </activity>
 
         <service android:name="com.android.server.telecom.testapps.SelfManagedConnectionService"
-                 android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"
-                 android:process="com.android.server.telecom.testapps.SelfMangingCallingApp">
+             android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"
+             android:process="com.android.server.telecom.testapps.SelfMangingCallingApp"
+             android:exported="true">
           <intent-filter>
-              <action android:name="android.telecom.ConnectionService" />
+              <action android:name="android.telecom.ConnectionService"/>
           </intent-filter>
         </service>
 
         <receiver android:exported="false"
-            android:process="com.android.server.telecom.testapps.SelfMangingCallingApp"
-            android:name="com.android.server.telecom.testapps.SelfManagedCallNotificationReceiver" />
+             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">
+             android:name="com.android.server.telecom.testapps.NuisanceReportReceiver">
             <intent-filter>
-                <action android:name="android.telecom.action.NUISANCE_CALL_STATUS_CHANGED" />
+                <action android:name="android.telecom.action.NUISANCE_CALL_STATUS_CHANGED"/>
             </intent-filter>
         </receiver>
 
-        <service
-            android:name=".TestCallScreeningService"
-            android:permission="android.permission.BIND_SCREENING_SERVICE">
+        <service android:name=".TestCallScreeningService"
+             android:permission="android.permission.BIND_SCREENING_SERVICE"
+             android:exported="true">
             <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">
+             android:configChanges="orientation|screenSize|keyboardHidden"
+             android:excludeFromRecents="true"
+             android:launchMode="singleInstance">
         </activity>
 
-        <service
-                android:name=".TestCallRedirectionService"
-                android:permission="android.permission.BIND_CALL_REDIRECTION_SERVICE">
+        <service android:name=".TestCallRedirectionService"
+             android:permission="android.permission.BIND_CALL_REDIRECTION_SERVICE"
+             android:exported="true">
             <intent-filter>
                 <action android:name="android.telecom.CallRedirectionService"/>
             </intent-filter>
         </service>
 
         <activity android:name=".CallRedirectionActivity"
-                  android:configChanges="orientation|screenSize|keyboardHidden"
-                  android:excludeFromRecents="true"
-                  android:launchMode="singleInstance">
+             android:configChanges="orientation|screenSize|keyboardHidden"
+             android:excludeFromRecents="true"
+             android:launchMode="singleInstance">
         </activity>
 
         <activity android:name=".PostCallActivity"
-                  android:label="@string/postCallActivityLabel">
+             android:label="@string/postCallActivityLabel"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.telecom.action.POST_CALL" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.telecom.action.POST_CALL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/testapps/callaudiotest/Android.bp b/testapps/callaudiotest/Android.bp
new file mode 100644
index 0000000..81164e6
--- /dev/null
+++ b/testapps/callaudiotest/Android.bp
@@ -0,0 +1,31 @@
+//
+// Copyright (C) 2021 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+    name: "TelecomCallAudioTestApp",
+    static_libs: [
+        "androidx.legacy_legacy-support-v4",
+        "guava",
+    ],
+    srcs: ["src/**/*.java"],
+    platform_apis: true,
+    certificate: "platform",
+    privileged: true,
+}
diff --git a/testapps/callaudiotest/AndroidManifest.xml b/testapps/callaudiotest/AndroidManifest.xml
new file mode 100644
index 0000000..014ab29
--- /dev/null
+++ b/testapps/callaudiotest/AndroidManifest.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     coreApp="true"
+     package="com.android.server.telecom.callaudiotest">
+
+    <uses-sdk android:minSdkVersion="28"
+         android:targetSdkVersion="29"/>
+    <uses-permission android:name="android.permission.BLUETOOTH"/>
+    <uses-permission android:name="android.permission.CALL_PHONE"/>
+    <uses-permission android:name="android.permission.CONTROL_INCALL_EXPERIENCE"/>
+    <uses-permission android:name="android.permission.READ_CALL_LOG"/>
+    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
+    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
+    <uses-permission android:name="android.permission.MODIFY_AUDIO_ROUTING"/>
+    <uses-permission android:name="android.permission.MODIFY_PHONE_STATE"/>
+    <uses-permission android:name="android.permission.WRITE_CALL_LOG"/>
+
+    <application android:label="Telecom Call Audio Test">
+        <uses-library android:name="android.test.runner"/>
+
+        <service android:name="com.android.server.telecom.callaudiotest.CallAudioTestInCallService"
+             android:permission="android.permission.BIND_INCALL_SERVICE"
+             android:exported="true">
+            <meta-data android:name="android.telecom.IN_CALL_SERVICE_CAR_MODE_UI"
+                       android:value="false"/>
+            <meta-data android:name="android.telecom.INCLUDE_SELF_MANAGED_CALLS"
+                       android:value="false"/>
+          <intent-filter>
+              <action android:name="android.telecom.InCallService"/>
+          </intent-filter>
+        </service>
+
+        <activity android:name="com.android.server.telecom.callaudiotest.CallAudioTestActivity"
+             android:label="Call Audio Test"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/testapps/callaudiotest/res/layout/call_audio_test_activity.xml b/testapps/callaudiotest/res/layout/call_audio_test_activity.xml
new file mode 100644
index 0000000..0d847f0
--- /dev/null
+++ b/testapps/callaudiotest/res/layout/call_audio_test_activity.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical" >
+    <TextView
+        android:id="@+id/appLabel"
+        android:layout_gravity="left"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textSize="25dp"
+        android:text="Call Audio Testing" />
+    <TextView
+        android:id="@+id/appDescription"
+        android:layout_gravity="left"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textSize="12dp"
+        android:text="You can enabled auto answer and calls will be answered and some awesome audio will be looped to the caller." />
+    <CheckBox
+        android:id="@+id/enableAutoAnswer"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Answer calls and loop audio" />
+</LinearLayout>
diff --git a/testapps/callaudiotest/res/raw/speech.m4a b/testapps/callaudiotest/res/raw/speech.m4a
new file mode 100644
index 0000000..4161ad6
--- /dev/null
+++ b/testapps/callaudiotest/res/raw/speech.m4a
Binary files differ
diff --git a/testapps/callaudiotest/src/com/android/server/telecom/callaudiotest/CallAudioTestActivity.java b/testapps/callaudiotest/src/com/android/server/telecom/callaudiotest/CallAudioTestActivity.java
new file mode 100644
index 0000000..b75a498
--- /dev/null
+++ b/testapps/callaudiotest/src/com/android/server/telecom/callaudiotest/CallAudioTestActivity.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2021 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.callaudiotest;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.EditText;
+
+/**
+ * Activity to configure the auto answer behavior.
+ */
+public class CallAudioTestActivity extends Activity {
+    private static final int RESULT_PICK_FILE = 1;
+    public static final String AUDIO_TEST_PREFS = "audio_test_prefs";
+    public static final String AUTO_ANSWER_ENABLED = "auto_answer_enabled";
+    private CheckBox mEnable;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.call_audio_test_activity);
+        mEnable = findViewById(R.id.enableAutoAnswer);
+        mEnable.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+            @Override
+            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+                SharedPreferences prefs = getSharedPreferences(AUDIO_TEST_PREFS, MODE_PRIVATE);
+                SharedPreferences.Editor edit = prefs.edit();
+                edit.putBoolean(AUTO_ANSWER_ENABLED, isChecked);
+                edit.apply();
+            }
+        });
+        loadPreferences();
+    }
+
+    private void loadPreferences() {
+        SharedPreferences prefs = getSharedPreferences(AUDIO_TEST_PREFS, MODE_PRIVATE);
+        mEnable.setChecked(prefs.getBoolean(AUTO_ANSWER_ENABLED, false));
+    }
+}
diff --git a/testapps/callaudiotest/src/com/android/server/telecom/callaudiotest/CallAudioTestInCallService.java b/testapps/callaudiotest/src/com/android/server/telecom/callaudiotest/CallAudioTestInCallService.java
new file mode 100644
index 0000000..bd98a7f
--- /dev/null
+++ b/testapps/callaudiotest/src/com/android/server/telecom/callaudiotest/CallAudioTestInCallService.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2021 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.callaudiotest;
+
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.media.AudioAttributes;
+import android.media.AudioDeviceInfo;
+import android.media.AudioManager;
+import android.media.MediaPlayer;
+import android.net.Uri;
+import android.os.IBinder;
+import android.telecom.Call;
+import android.telecom.InCallService;
+import android.telecom.Log;
+import android.telecom.VideoProfile;
+
+import java.io.IOException;
+import java.lang.String;
+
+/**
+ * Simple test InCallService which answers a call automatically and plays back some looping audio.
+ * Intended for testing call audio between devices.
+ */
+public class CallAudioTestInCallService extends InCallService {
+    private Call mIncomingCall;
+    private boolean mIsAutoAnswerEnabled = false;
+    private MediaPlayer mMediaPlayer;
+    private Call.Callback mCallback = new Call.Callback() {
+        @Override
+        public void onStateChanged(Call call, int state) {
+            if (state == Call.STATE_ACTIVE) {
+                startPlaying();
+            }
+        }
+    };
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        SharedPreferences prefs = getSharedPreferences(CallAudioTestActivity.AUDIO_TEST_PREFS,
+                MODE_PRIVATE);
+        mIsAutoAnswerEnabled = prefs.getBoolean(CallAudioTestActivity.AUTO_ANSWER_ENABLED, false);
+        return super.onBind(intent);
+    }
+
+    @Override
+    public void onCallAdded(Call call) {
+        if (!mIsAutoAnswerEnabled) {
+            Log.i(this, "onCallAdded - autoanswer disabled, skip");
+            return;
+        }
+        if (call.getDetails().getState() == Call.STATE_RINGING) {
+            mIncomingCall = call;
+            mIncomingCall.registerCallback(mCallback);
+            mIncomingCall.answer(VideoProfile.STATE_AUDIO_ONLY);
+
+            Log.i(this, "onCallAdded - ringing call");
+        } else {
+            Log.i(this, "onCallAdded - nonringing call");
+        }
+    }
+
+    @Override
+    public void onCallRemoved(Call call) {
+        if (mIncomingCall == call) {
+            mIncomingCall = null;
+            if (mMediaPlayer != null) {
+                mMediaPlayer.stop();
+                mMediaPlayer.release();
+            }
+        }
+    }
+
+    private void startPlaying() {
+        AudioDeviceInfo telephonyDevice = getTelephonyDevice(getSystemService(AudioManager.class));
+        if (telephonyDevice != null) {
+            Log.i(this, "startPlaying: create player for speech");
+            mMediaPlayer = new MediaPlayer();
+            mMediaPlayer.setOnCompletionListener(mediaPlayer -> Log.w(this, "startPlaying: done"));
+            mMediaPlayer.setOnErrorListener(
+                (mediaPlayer, what, extra) -> {
+                        Log.w(this, "startPlaying: playback failed!");
+                        return true; // Error handled
+                        });
+            mMediaPlayer.setLooping(true);
+            mMediaPlayer.setVolume(1.0f);
+            AudioAttributes audioAttributes = new AudioAttributes.Builder()
+                    .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION).build();
+            mMediaPlayer.setAudioAttributes(audioAttributes);
+            try {
+                mMediaPlayer.setDataSource(getResources().openRawResourceFd(R.raw.speech));
+                mMediaPlayer.prepare();
+            } catch (IOException e) {
+                mMediaPlayer.release();
+                throw new IllegalStateException(e);
+            }
+            if (!mMediaPlayer.setPreferredDevice(telephonyDevice)) {
+                Log.w(this, "startPlaying: setPreferredDevice failed");
+            }
+            mMediaPlayer.start();
+
+        }
+    }
+
+    private AudioDeviceInfo getTelephonyDevice(AudioManager audioManager) {
+        AudioDeviceInfo[] deviceList = audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
+        for (AudioDeviceInfo device: deviceList) {
+            if (device.getType() == AudioDeviceInfo.TYPE_TELEPHONY) {
+                return device;
+            }
+        }
+        return null;
+    }
+}
diff --git a/testapps/carmodedialer/Android.bp b/testapps/carmodedialer/Android.bp
index 7179b1f..9f65b8c 100644
--- a/testapps/carmodedialer/Android.bp
+++ b/testapps/carmodedialer/Android.bp
@@ -14,6 +14,10 @@
 // limitations under the License.
 //
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 android_test {
     name: "TelecomCarModeApp",
     static_libs: [
diff --git a/testapps/carmodedialer/AndroidManifest.xml b/testapps/carmodedialer/AndroidManifest.xml
index 7f55f7e..239726c 100644
--- a/testapps/carmodedialer/AndroidManifest.xml
+++ b/testapps/carmodedialer/AndroidManifest.xml
@@ -15,75 +15,79 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          coreApp="true"
-          package="com.android.server.telecom.carmodedialer">
+     coreApp="true"
+     package="com.android.server.telecom.carmodedialer">
 
-    <uses-sdk
-        android:minSdkVersion="28"
-        android:targetSdkVersion="29" />
+    <uses-sdk android:minSdkVersion="28"
+         android:targetSdkVersion="29"/>
 
-    <uses-permission android:name="android.permission.ACCEPT_HANDOVER" />
-    <uses-permission android:name="android.permission.BLUETOOTH" />
-    <uses-permission android:name="android.permission.CALL_PHONE" />
-    <uses-permission android:name="android.permission.CONTROL_INCALL_EXPERIENCE" />
-    <uses-permission android:name="android.permission.ENTER_CAR_MODE_PRIORITIZED" />
-    <uses-permission android:name="android.permission.INTERNET" />
-    <uses-permission android:name="android.permission.READ_CALL_LOG" />
-    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
-    <uses-permission android:name="android.permission.WRITE_CALL_LOG" />
+    <uses-permission android:name="android.permission.ACCEPT_HANDOVER"/>
+    <uses-permission android:name="android.permission.BLUETOOTH"/>
+    <uses-permission android:name="android.permission.CALL_PHONE"/>
+    <uses-permission android:name="android.permission.CONTROL_INCALL_EXPERIENCE"/>
+    <uses-permission android:name="android.permission.ENTER_CAR_MODE_PRIORITIZED"/>
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.READ_CALL_LOG"/>
+    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
+    <uses-permission android:name="android.permission.WRITE_CALL_LOG"/>
 
     <application android:label="Telecom CarMode">
-        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.test.runner"/>
 
         <service android:name="com.android.server.telecom.carmodedialer.CarModeInCallServiceImpl"
-                 android:permission="android.permission.BIND_INCALL_SERVICE" >
-          <meta-data android:name="android.telecom.IN_CALL_SERVICE_CAR_MODE_UI"
-                     android:value="true"/>
+             android:permission="android.permission.BIND_INCALL_SERVICE"
+             android:exported="true">
+            <meta-data android:name="android.telecom.IN_CALL_SERVICE_CAR_MODE_UI"
+                       android:value="true"/>
+            <meta-data android:name="android.telecom.INCLUDE_SELF_MANAGED_CALLS"
+                       android:value="true"/>
           <intent-filter>
               <action android:name="android.telecom.InCallService"/>
           </intent-filter>
         </service>
 
         <activity android:name="com.android.server.telecom.carmodedialer.CarModeInCallUI"
-                android:label="CarMode Dialer"
-                android:launchMode="singleInstance">
+             android:label="CarMode Dialer"
+             android:launchMode="singleInstance"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
         <activity android:name="com.android.server.telecom.carmodedialer.CarModeDialerActivity"
-                  android:label="CarMode Dialer">
+             android:label="CarMode Dialer"
+             android:exported="true">
             <intent-filter>
-                <action android:name="android.intent.action.DIAL" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:mimeType="vnd.android.cursor.item/phone" />
-                <data android:mimeType="vnd.android.cursor.item/person" />
+                <action android:name="android.intent.action.DIAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:mimeType="vnd.android.cursor.item/phone"/>
+                <data android:mimeType="vnd.android.cursor.item/person"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.DIAL" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="voicemail" />
+                <action android:name="android.intent.action.DIAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="voicemail"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.DIAL" />
-                <category android:name="android.intent.category.DEFAULT" />
+                <action android:name="android.intent.action.DIAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.VIEW" />
-                <action android:name="android.intent.action.DIAL" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.BROWSABLE" />
-                <data android:scheme="tel" />
+                <action android:name="android.intent.action.VIEW"/>
+                <action android:name="android.intent.action.DIAL"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="tel"/>
             </intent-filter>
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.DEFAULT" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
     </application>
diff --git a/testapps/companionapp/Android.bp b/testapps/companionapp/Android.bp
new file mode 100644
index 0000000..8718b37
--- /dev/null
+++ b/testapps/companionapp/Android.bp
@@ -0,0 +1,28 @@
+//
+// 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+    name: "TelecomCompanionApp",
+    static_libs: [
+        "androidx.legacy_legacy-support-v4",
+        "guava",
+    ],
+    srcs: ["src/**/*.java"],
+}
diff --git a/testapps/companionapp/AndroidManifest.xml b/testapps/companionapp/AndroidManifest.xml
new file mode 100644
index 0000000..7569d8f
--- /dev/null
+++ b/testapps/companionapp/AndroidManifest.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     coreApp="true"
+     package="com.android.server.telecom.companionapp">
+
+    <uses-sdk android:minSdkVersion="28"
+         android:targetSdkVersion="29"/>
+
+    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
+    <uses-permission android:name="android.permission.MANAGE_ONGOING_CALLS" />
+    <uses-feature android:name="android.software.companion_device_setup"/>
+
+    <application android:label="Telecom Companion">
+        <uses-library android:name="android.test.runner"/>
+
+        <activity android:name="com.android.server.telecom.companionapp.CompanionTestApp"
+                  android:label="CompanionTestApp">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <service android:name="com.android.server.telecom.companionapp.CompanionInCallServiceImpl"
+             android:permission="android.permission.BIND_INCALL_SERVICE"
+             android:exported="true">
+          <intent-filter>
+              <action android:name="android.telecom.InCallService"/>
+          </intent-filter>
+        </service>
+    </application>
+</manifest>
diff --git a/testapps/companionapp/res/drawable-hdpi/ic_android_black_24dp.png b/testapps/companionapp/res/drawable-hdpi/ic_android_black_24dp.png
new file mode 100644
index 0000000..ed3ee45
--- /dev/null
+++ b/testapps/companionapp/res/drawable-hdpi/ic_android_black_24dp.png
Binary files differ
diff --git a/testapps/companionapp/res/drawable-mdpi/ic_android_black_24dp.png b/testapps/companionapp/res/drawable-mdpi/ic_android_black_24dp.png
new file mode 100644
index 0000000..a4add51
--- /dev/null
+++ b/testapps/companionapp/res/drawable-mdpi/ic_android_black_24dp.png
Binary files differ
diff --git a/testapps/companionapp/res/drawable-xhdpi/ic_android_black_24dp.png b/testapps/companionapp/res/drawable-xhdpi/ic_android_black_24dp.png
new file mode 100644
index 0000000..41558f2
--- /dev/null
+++ b/testapps/companionapp/res/drawable-xhdpi/ic_android_black_24dp.png
Binary files differ
diff --git a/testapps/companionapp/res/drawable-xxhdpi/ic_android_black_24dp.png b/testapps/companionapp/res/drawable-xxhdpi/ic_android_black_24dp.png
new file mode 100644
index 0000000..6006b12
--- /dev/null
+++ b/testapps/companionapp/res/drawable-xxhdpi/ic_android_black_24dp.png
Binary files differ
diff --git a/testapps/companionapp/res/drawable-xxxhdpi/ic_android_black_24dp.png b/testapps/companionapp/res/drawable-xxxhdpi/ic_android_black_24dp.png
new file mode 100644
index 0000000..4f935bf
--- /dev/null
+++ b/testapps/companionapp/res/drawable-xxxhdpi/ic_android_black_24dp.png
Binary files differ
diff --git a/testapps/companionapp/res/layout/main.xml b/testapps/companionapp/res/layout/main.xml
new file mode 100644
index 0000000..e553754
--- /dev/null
+++ b/testapps/companionapp/res/layout/main.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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">
+    <TextView
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:text="This is a test app for companion in call service."
+        android:padding="10dp"
+        android:textColor="#FFFFFF"/>
+    <Button
+        android:id="@+id/buttonStart"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:text="Start Association"/>
+    <Button
+        android:id="@+id/buttonStop"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:text="Stop Association"/>
+</LinearLayout>
diff --git a/testapps/companionapp/src/com/android/server/telecom/companionapp/CompanionInCallServiceImpl.java b/testapps/companionapp/src/com/android/server/telecom/companionapp/CompanionInCallServiceImpl.java
new file mode 100644
index 0000000..890cd77
--- /dev/null
+++ b/testapps/companionapp/src/com/android/server/telecom/companionapp/CompanionInCallServiceImpl.java
@@ -0,0 +1,78 @@
+/*
+ * 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.companionapp;
+
+import android.content.Intent;
+import android.telecom.Call;
+import android.telecom.InCallService;
+import android.telecom.Phone;
+import android.util.Log;
+import android.widget.Toast;
+
+import java.lang.Override;
+import java.lang.String;
+
+/**
+ * Test Companion In-Call service implementation.
+ */
+public class CompanionInCallServiceImpl extends InCallService {
+    private static final String TAG = "CompanionInCallServiceImpl";
+
+    private Phone mPhone;
+
+    private Phone.Listener mPhoneListener = new Phone.Listener() {
+        @Override
+        public void onCallAdded(Phone phone, Call call) {
+            Log.i(TAG, "onCallAdded: " + call.toString());
+            Toast.makeText(
+                    CompanionInCallServiceImpl.this, "onCallAdded", Toast.LENGTH_LONG).show();
+        }
+
+        @Override
+        public void onCallRemoved(Phone phone, Call call) {
+            Log.i(TAG, "onCallRemoved: " + call.toString());
+            Toast.makeText(
+                    CompanionInCallServiceImpl.this, "onCallRemoved", Toast.LENGTH_LONG).show();
+        }
+    };
+
+    @Override
+    public void onPhoneCreated(Phone phone) {
+        Log.i(TAG, "onPhoneCreated");
+        mPhone = phone;
+        mPhone.addListener(mPhoneListener);
+    }
+
+    @Override
+    public boolean onUnbind(Intent intent) {
+        Log.i(TAG, "Companion TestApp InCallService unbind");
+        mPhone.removeListener(mPhoneListener);
+        mPhone = null;
+        return super.onUnbind(intent);
+    }
+
+    /**
+     * Used to bind a call
+     * @param intent
+     * @return
+     */
+    @Override
+    public android.os.IBinder onBind(Intent intent) {
+        Log.d(TAG, "Companion TestApp InCallService bind");
+        return super.onBind(intent);
+    }
+}
diff --git a/testapps/companionapp/src/com/android/server/telecom/companionapp/CompanionTestApp.java b/testapps/companionapp/src/com/android/server/telecom/companionapp/CompanionTestApp.java
new file mode 100644
index 0000000..7ed1e1e
--- /dev/null
+++ b/testapps/companionapp/src/com/android/server/telecom/companionapp/CompanionTestApp.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2020 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.companionapp;
+
+import android.app.Activity;
+import android.bluetooth.BluetoothDevice;
+import android.companion.AssociationRequest;
+import android.companion.BluetoothDeviceFilter;
+import android.companion.CompanionDeviceManager;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.os.Bundle;
+import android.os.ParcelUuid;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.Toast;
+
+import java.util.UUID;
+import java.util.regex.Pattern;
+
+/**
+ * Companion Test App in Telecom
+ */
+public class CompanionTestApp extends Activity {
+
+    private static String logtag = "CompanionTestApp";
+    private CompanionDeviceManager mDeviceManager;
+    private AssociationRequest mPairingRequest;
+    private BluetoothDeviceFilter mDeviceFilter;
+
+    private static final int SELECT_DEVICE_REQUEST_CODE = 42;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        setupDeviceAssociation();
+
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.main);
+
+        Button buttonStart = (Button)findViewById(R.id.buttonStart);
+        buttonStart.setOnClickListener(startListener);
+
+        Button buttonStop = (Button)findViewById(R.id.buttonStop);
+        buttonStop.setOnClickListener(stopListener);
+    }
+
+    private OnClickListener startListener = new OnClickListener() {
+        public void onClick(View v) {
+            Log.d(logtag,"onClick() called - start button");
+            Toast.makeText(CompanionTestApp.this, "Start Association", Toast.LENGTH_LONG).show();
+            assosicate();
+            Log.d(logtag,"onClick() ended - start button");
+        }
+    };
+
+    private OnClickListener stopListener = new OnClickListener() {
+        public void onClick(View v) {
+            Log.d(logtag,"onClick() called - stop button");
+            Toast.makeText(CompanionTestApp.this, "Stop Association", Toast.LENGTH_LONG).show();
+            disassosicate();
+            Log.d(logtag,"onClick() ended - stop button");
+        }
+    };
+
+    // Setup Device Association Preparation, per
+    // https://developer.android.com/guide/topics/connectivity/companion-device-pairing
+    private void setupDeviceAssociation() {
+        mDeviceManager = getSystemService(CompanionDeviceManager.class);
+
+        mDeviceFilter = new BluetoothDeviceFilter.Builder()
+                .build();
+
+        mPairingRequest = new AssociationRequest.Builder()
+                .addDeviceFilter(mDeviceFilter)
+                .setDeviceProfile(AssociationRequest.DEVICE_PROFILE_WATCH)
+                .build();
+
+    }
+
+    // Associate bluetooth device, per
+    // https://developer.android.com/guide/topics/connectivity/companion-device-pairing
+    private void assosicate() {
+        // When the app tries to pair with the Bluetooth device, show the
+        // appropriate pairing request dialog to the user.
+        mDeviceManager.associate(mPairingRequest,
+                new CompanionDeviceManager.Callback() {
+                    @Override
+                    public void onDeviceFound(IntentSender chooserLauncher) {
+                        try {
+                            startIntentSenderForResult(chooserLauncher,
+                                SELECT_DEVICE_REQUEST_CODE, null, 0, 0, 0);
+                        } catch (Exception ex) {
+                            Log.d(logtag, "Callback onDeviceFound() Exception: " + ex);
+                        }
+                    }
+
+                    @Override
+                    public void onFailure(CharSequence error) {
+                        Log.d(logtag, "Callback onFailure() called - error: " + error);
+                    }
+                },
+                null);
+    }
+
+    // Disassociate the associated bluetooth device
+    private void disassosicate() {
+        for (String macAddress : mDeviceManager.getAssociations()) {
+            mDeviceManager.disassociate(macAddress);
+        }
+    }
+
+    @Override
+    public void onActivityResult(int requestCode, int resultCode, Intent data) {
+        if (requestCode == SELECT_DEVICE_REQUEST_CODE &&
+                resultCode == Activity.RESULT_OK) {
+            // User has chosen to pair with the Bluetooth device.
+            BluetoothDevice deviceToPair =
+                    data.getParcelableExtra(CompanionDeviceManager.EXTRA_DEVICE);
+            deviceToPair.createBond();
+        }
+    }
+
+    @Override
+    protected void onStart() {
+        Log.d(logtag,"onStart() called");
+        super.onStart();
+    }
+
+    @Override
+    protected void onResume() {
+        Log.d(logtag,"onResume() called");
+        super.onResume();
+    }
+
+    @Override
+    protected void onPause() {
+        Log.d(logtag,"onPause() called");
+        super.onPause();
+    }
+
+    @Override
+    protected void onStop() {
+        Log.d(logtag,"onStop() called");
+        super.onStop();
+    }
+
+    @Override
+    protected void onDestroy() {
+        Log.d(logtag,"onDestroy() called");
+        super.onDestroy();
+    }
+}
diff --git a/testapps/res/layout/incall_screen.xml b/testapps/res/layout/incall_screen.xml
index f8f919b..22d3574 100644
--- a/testapps/res/layout/incall_screen.xml
+++ b/testapps/res/layout/incall_screen.xml
@@ -131,6 +131,11 @@
             android:layout_height="wrap_content"
             android:layout_marginLeft="10dp"/>
     </LinearLayout>
+    <TextView
+        android:id="@+id/incoming_composer_attachments"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginLeft="10dp"/>
     <Button
         android:id="@+id/disable_incallservice"
         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 28f4473..d26d629 100644
--- a/testapps/res/layout/self_managed_sample_main.xml
+++ b/testapps/res/layout/self_managed_sample_main.xml
@@ -90,7 +90,6 @@
     <LinearLayout android:orientation="horizontal"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content">
-
         <Button
             android:id="@+id/placeOutgoingCallButton"
             android:layout_width="wrap_content"
@@ -113,6 +112,35 @@
             android:text="Req CallScreen Role"/>
     </LinearLayout>
 
+    <LinearLayout android:orientation="horizontal"
+                  android:layout_width="wrap_content"
+                  android:layout_height="wrap_content">
+        <Button
+            android:id="@+id/placeSelfManagedOutgoingCallButton"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="SelfManagedOutgoing"/>
+        <Button
+            android:id="@+id/placeSelfManagedIncomingCallButton"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="SelfManagedIncoming"/>
+    </LinearLayout>
+
+    <LinearLayout android:orientation="horizontal"
+                  android:layout_width="wrap_content"
+                  android:layout_height="wrap_content">
+        <Button
+            android:id="@+id/enableCarMode"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Enable car mode"/>
+        <Button
+            android:id="@+id/disableCarMode"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="Disable car mode"/>
+    </LinearLayout>
     <ListView
         android:id="@+id/callList"
         android:layout_width="match_parent"
diff --git a/testapps/res/layout/testdialer_main.xml b/testapps/res/layout/testdialer_main.xml
index 3f397b8..749d236 100644
--- a/testapps/res/layout/testdialer_main.xml
+++ b/testapps/res/layout/testdialer_main.xml
@@ -59,6 +59,11 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:text="@string/startCallWithRtt"/>
+    <CheckBox
+        android:id="@+id/add_composer_attachments_checkbox"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/addComposerAttachments"/>
     <LinearLayout
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
diff --git a/testapps/res/values/donottranslate_strings.xml b/testapps/res/values/donottranslate_strings.xml
index 7331d5a..b1a1f80 100644
--- a/testapps/res/values/donottranslate_strings.xml
+++ b/testapps/res/values/donottranslate_strings.xml
@@ -44,6 +44,8 @@
 
     <string name="startCallWithRtt">Start call with RTT</string>
 
+    <string name="addComposerAttachments">Add call composer attachments</string>
+
     <string name="rttIfaceButton">RTT</string>
 
     <string name="endRttButton">End RTT</string>
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallList.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallList.java
index 4b5fa57..d4661ff 100644
--- a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallList.java
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallList.java
@@ -46,8 +46,10 @@
 
     public static String SELF_MANAGED_ACCOUNT_1 = "1";
     public static String SELF_MANAGED_ACCOUNT_2 = "2";
+    public static String SELF_MANAGED_ACCOUNT_3 = "3";
     public static String SELF_MANAGED_NAME_1 = "SuperCall";
     public static String SELF_MANAGED_NAME_2 = "Mega Call";
+    public static String SELF_MANAGED_NAME_3 = "SM Call";
     public static String CUSTOM_URI_SCHEME = "custom";
 
     private static SelfManagedCallList sInstance;
@@ -99,6 +101,8 @@
                 SELF_MANAGED_NAME_1, true /* areCallsLogged */);
         registerPhoneAccount(context, SELF_MANAGED_ACCOUNT_2, SELF_MANAGED_ADDRESS_2,
                 SELF_MANAGED_NAME_2, false /* areCallsLogged */);
+        registerPhoneAccount(context, SELF_MANAGED_ACCOUNT_3, SELF_MANAGED_ADDRESS_1,
+                SELF_MANAGED_NAME_3, true /* areCallsLogged */);
     }
 
     public void registerPhoneAccount(Context context, String id, Uri address, String name,
@@ -110,6 +114,9 @@
         if (areCallsLogged) {
             extras.putBoolean(PhoneAccount.EXTRA_LOG_SELF_MANAGED_CALLS, true);
         }
+        if (id.equals(SELF_MANAGED_ACCOUNT_3)) {
+            extras.putBoolean(PhoneAccount.EXTRA_ADD_SELF_MANAGED_CALLS_TO_INCALLSERVICE, true);
+        }
         PhoneAccount.Builder builder = PhoneAccount.builder(handle, name)
                 .addSupportedUriScheme(PhoneAccount.SCHEME_TEL)
                 .addSupportedUriScheme(PhoneAccount.SCHEME_SIP)
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java
index fd12a2e..44410d2 100644
--- a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java
@@ -16,9 +16,12 @@
 
 package com.android.server.telecom.testapps;
 
+import static android.app.UiModeManager.DEFAULT_PRIORITY;
+
 import android.app.Activity;
 import android.app.NotificationChannel;
 import android.app.NotificationManager;
+import android.app.UiModeManager;
 import android.app.role.RoleManager;
 import android.content.Intent;
 import android.media.AudioAttributes;
@@ -54,9 +57,13 @@
     private SelfManagedCallList mCallList = SelfManagedCallList.getInstance();
     private CheckBox mCheckIfPermittedBeforeCalling;
     private Button mPlaceOutgoingCallButton;
+    private Button mPlaceSelfManagedOutgoingCallButton;
+    private Button mPlaceSelfManagedIncomingCallButton;
     private Button mPlaceIncomingCallButton;
     private Button mHandoverFrom;
     private Button mRequestCallScreeningRole;
+    private Button mEnableCarMode;
+    private Button mDisableCarMode;
     private RadioButton mUseAcct1Button;
     private RadioButton mUseAcct2Button;
     private CheckBox mHoldableCheckbox;
@@ -119,6 +126,20 @@
                 placeOutgoingCall();
             }
         });
+        mPlaceSelfManagedOutgoingCallButton = (Button) findViewById(
+                R.id.placeSelfManagedOutgoingCallButton);
+        mPlaceSelfManagedOutgoingCallButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                placeSelfManagedOutgoingCall();
+            }
+        });
+        mPlaceSelfManagedIncomingCallButton = (Button) findViewById(
+                R.id.placeSelfManagedIncomingCallButton);
+        mPlaceSelfManagedIncomingCallButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) { placeSelfManagedIncomingCall(); }
+        });
         mPlaceIncomingCallButton = (Button) findViewById(R.id.placeIncomingCallButton);
         mPlaceIncomingCallButton.setOnClickListener(new View.OnClickListener() {
             @Override
@@ -134,7 +155,14 @@
         mRequestCallScreeningRole.setOnClickListener((v -> {
             requestCallScreeningRole();
         }));
-
+        mEnableCarMode = (Button) findViewById(R.id.enableCarMode);
+        mEnableCarMode.setOnClickListener((v -> {
+            enableCarMode();
+        }));
+        mDisableCarMode = (Button) findViewById(R.id.disableCarMode);
+        mDisableCarMode.setOnClickListener((v -> {
+            disableCarMode();
+        }));
         mUseAcct1Button = findViewById(R.id.useAcct1Button);
         mUseAcct2Button = findViewById(R.id.useAcct2Button);
         mHasFocus = findViewById(R.id.hasFocus);
@@ -184,6 +212,25 @@
         tm.placeCall(Uri.parse(mNumber.getText().toString()), extras);
     }
 
+    private void placeSelfManagedOutgoingCall() {
+        TelecomManager tm = TelecomManager.from(this);
+        PhoneAccountHandle phoneAccountHandle = mCallList.getPhoneAccountHandle(
+                SelfManagedCallList.SELF_MANAGED_ACCOUNT_3);
+
+        if (mCheckIfPermittedBeforeCalling.isChecked()) {
+            Toast.makeText(this, R.string.outgoingCallNotPermitted, Toast.LENGTH_SHORT).show();
+            return;
+        }
+
+        Bundle extras = new Bundle();
+        extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle);
+        if (mVideoCallCheckbox.isChecked()) {
+            extras.putInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
+                    VideoProfile.STATE_BIDIRECTIONAL);
+        }
+        tm.placeCall(Uri.parse(mNumber.getText().toString()), extras);
+    }
+
     private void initiateHandover() {
         TelecomManager tm = TelecomManager.from(this);
         PhoneAccountHandle phoneAccountHandle = getSelectedPhoneAccountHandle();
@@ -214,6 +261,37 @@
         tm.addNewIncomingCall(getSelectedPhoneAccountHandle(), extras);
     }
 
+    private void placeSelfManagedIncomingCall() {
+        TelecomManager tm = TelecomManager.from(this);
+        PhoneAccountHandle phoneAccountHandle = mCallList.getPhoneAccountHandle(
+                SelfManagedCallList.SELF_MANAGED_ACCOUNT_3);
+
+        if (mCheckIfPermittedBeforeCalling.isChecked()) {
+            if (!tm.isIncomingCallPermitted(phoneAccountHandle)) {
+                Toast.makeText(this, R.string.incomingCallNotPermitted , Toast.LENGTH_SHORT).show();
+                return;
+            }
+        }
+
+        Bundle extras = new Bundle();
+        extras.putParcelable(TelecomManager.EXTRA_INCOMING_CALL_ADDRESS,
+                Uri.parse(mNumber.getText().toString()));
+        tm.addNewIncomingCall(phoneAccountHandle, extras);
+    }
+
+    private void enableCarMode() {
+        UiModeManager uiModeManager = getSystemService(UiModeManager.class);
+        uiModeManager.enableCarMode(0);
+        Toast.makeText(this, "Enabling car mode with priority " + DEFAULT_PRIORITY,
+                Toast.LENGTH_LONG).show();
+    }
+
+    private void disableCarMode() {
+        UiModeManager uiModeManager = getSystemService(UiModeManager.class);
+        uiModeManager.disableCarMode(0);
+        Toast.makeText(this, "Disabling car mode", Toast.LENGTH_LONG).show();
+    }
+
     private void configureNotificationChannel() {
         NotificationChannel channel = new NotificationChannel(
                 SelfManagedConnection.INCOMING_CALL_CHANNEL_ID, "Incoming Calls",
diff --git a/testapps/src/com/android/server/telecom/testapps/TestCallDiagnosticService.java b/testapps/src/com/android/server/telecom/testapps/TestCallDiagnosticService.java
new file mode 100644
index 0000000..e1511e7
--- /dev/null
+++ b/testapps/src/com/android/server/telecom/testapps/TestCallDiagnosticService.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2021 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.telecom.BluetoothCallQualityReport;
+import android.telecom.Call;
+import android.telecom.CallAudioState;
+import android.telecom.CallDiagnosticService;
+import android.telecom.CallDiagnostics;
+import android.telecom.Log;
+import android.telephony.CallQuality;
+import android.telephony.ims.ImsReasonInfo;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+public class TestCallDiagnosticService extends CallDiagnosticService {
+
+    public static final class TestCallDiagnostics extends CallDiagnostics {
+        public Call.Details details;
+
+        TestCallDiagnostics(Call.Details details) {
+            this.details = details;
+        }
+
+        @Override
+        public void onCallDetailsChanged(@NonNull Call.Details details) {
+            Log.i(this, "onCallDetailsChanged; %s", details);
+        }
+
+        @Override
+        public void onReceiveDeviceToDeviceMessage(int message, int value) {
+            Log.i(this, "onReceiveDeviceToDeviceMessage; %d/%d", message, value);
+        }
+
+        @Nullable
+        @Override
+        public CharSequence onCallDisconnected(int disconnectCause, int preciseDisconnectCause) {
+            Log.i(this, "onCallDisconnected");
+            return "GSM/CDMA call dropped because " + disconnectCause;
+        }
+
+        @Nullable
+        @Override
+        public CharSequence onCallDisconnected(@NonNull ImsReasonInfo disconnectReason) {
+            Log.i(this, "onCallDisconnected");
+            return "ImsCall dropped because something happened " + disconnectReason.mExtraMessage;
+        }
+
+        @Override
+        public void onCallQualityReceived(@NonNull CallQuality callQuality) {
+            Log.i(this, "onCallQualityReceived %s", callQuality);
+        }
+    }
+
+    @NonNull
+    @Override
+    public CallDiagnostics onInitializeCallDiagnostics(@NonNull Call.Details call) {
+        Log.i(this, "onInitiatlizeDiagnosticCall %s", call);
+        return new TestCallDiagnostics(call);
+    }
+
+    @Override
+    public void onRemoveCallDiagnostics(@NonNull CallDiagnostics call) {
+        Log.i(this, "onRemoveDiagnosticCall %s", call);
+    }
+
+    @Override
+    public void onCallAudioStateChanged(@NonNull CallAudioState audioState) {
+        Log.i(this, "onCallAudioStateChanged %s", audioState);
+    }
+
+    @Override
+    public void onBluetoothCallQualityReportReceived(
+            @NonNull BluetoothCallQualityReport qualityReport) {
+        Log.i(this, "onBluetoothCallQualityReportReceived %s", qualityReport);
+    }
+}
diff --git a/testapps/src/com/android/server/telecom/testapps/TestConnectionManager.java b/testapps/src/com/android/server/telecom/testapps/TestConnectionManager.java
index abb9108..5c78d52 100644
--- a/testapps/src/com/android/server/telecom/testapps/TestConnectionManager.java
+++ b/testapps/src/com/android/server/telecom/testapps/TestConnectionManager.java
@@ -37,7 +37,6 @@
 
 /**
  * Service which acts as a fake ConnectionManager if so configured.
- * TODO(santoscordon): Rename all classes in the directory to Dummy* (e.g., DummyConnectionService).
  */
 public class TestConnectionManager extends ConnectionService {
     public final class TestManagedConnection extends Connection {
diff --git a/testapps/src/com/android/server/telecom/testapps/TestConnectionService.java b/testapps/src/com/android/server/telecom/testapps/TestConnectionService.java
index f6fa116..3d1bc70 100644
--- a/testapps/src/com/android/server/telecom/testapps/TestConnectionService.java
+++ b/testapps/src/com/android/server/telecom/testapps/TestConnectionService.java
@@ -53,7 +53,6 @@
 
 /**
  * Service which provides fake calls to test the ConnectionService interface.
- * TODO: Rename all classes in the directory to Dummy* (e.g., DummyConnectionService).
  */
 public class TestConnectionService extends ConnectionService {
     /**
@@ -430,9 +429,9 @@
             int videoState = extras.getInt(EXTRA_START_VIDEO_STATE, VideoProfile.STATE_AUDIO_ONLY);
             Uri providedHandle = extras.getParcelable(EXTRA_HANDLE);
 
-            // Use dummy number for testing incoming calls.
+            // Use test number for testing incoming calls.
             Uri address = providedHandle == null ?
-                    Uri.fromParts(PhoneAccount.SCHEME_TEL, getDummyNumber(
+                    Uri.fromParts(PhoneAccount.SCHEME_TEL, getRandomNumber(
                             VideoProfile.isVideo(videoState)), null)
                     : providedHandle;
             connection.setVideoState(videoState);
@@ -480,7 +479,7 @@
             final Uri providedHandle = extras.getParcelable(EXTRA_HANDLE);
 
             Uri handle = providedHandle == null ?
-                    Uri.fromParts(PhoneAccount.SCHEME_TEL, getDummyNumber(false), null)
+                    Uri.fromParts(PhoneAccount.SCHEME_TEL, getRandomNumber(false), null)
                     : providedHandle;
 
             connection.setAddress(handle,  TelecomManager.PRESENTATION_ALLOWED);
@@ -613,7 +612,7 @@
      * @param isVideo {@code True} if the call is a video call.
      * @return The phone number.
      */
-    private String getDummyNumber(boolean isVideo) {
+    private String getRandomNumber(boolean isVideo) {
         int videoDigit = isVideo ? 1 : 0;
         int number = mRandom.nextInt(999);
         return String.format("555%s%03d", videoDigit, number);
diff --git a/testapps/src/com/android/server/telecom/testapps/TestDialerActivity.java b/testapps/src/com/android/server/telecom/testapps/TestDialerActivity.java
index 1e06387..010d6ee 100644
--- a/testapps/src/com/android/server/telecom/testapps/TestDialerActivity.java
+++ b/testapps/src/com/android/server/telecom/testapps/TestDialerActivity.java
@@ -9,6 +9,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.location.Location;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.PersistableBundle;
@@ -32,8 +33,18 @@
 
     private EditText mNumberView;
     private CheckBox mRttCheckbox;
+    private CheckBox mComposerCheckbox;
     private EditText mPriorityView;
 
+    private static final String COMPOSER_SUBJECT = "Sample call composer subject";
+    private static final Location COMPOSER_LOCATION;
+    static {
+        // Area 51
+        COMPOSER_LOCATION = new Location("");
+        COMPOSER_LOCATION.setLongitude(-115.806407);
+        COMPOSER_LOCATION.setLatitude(37.236214);
+    }
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -68,6 +79,7 @@
 
         mNumberView = (EditText) findViewById(R.id.number);
         mRttCheckbox = (CheckBox) findViewById(R.id.call_with_rtt_checkbox);
+        mComposerCheckbox = (CheckBox) findViewById(R.id.add_composer_attachments_checkbox);
         findViewById(R.id.enable_car_mode).setOnClickListener(new OnClickListener() {
             @Override
             public void onClick(View v) {
@@ -169,6 +181,11 @@
         if (mRttCheckbox.isChecked()) {
             extras.putBoolean(TelecomManager.EXTRA_START_CALL_WITH_RTT, true);
         }
+        if (mComposerCheckbox.isChecked()) {
+            extras.putInt(TelecomManager.EXTRA_PRIORITY, TelecomManager.PRIORITY_URGENT);
+            extras.putParcelable(TelecomManager.EXTRA_LOCATION, COMPOSER_LOCATION);
+            extras.putString(TelecomManager.EXTRA_CALL_SUBJECT, COMPOSER_SUBJECT);
+        }
 
         Bundle intentExtras = new Bundle();
         intentExtras.putBundle(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, extras);
diff --git a/testapps/src/com/android/server/telecom/testapps/TestInCallUI.java b/testapps/src/com/android/server/telecom/testapps/TestInCallUI.java
index 3f3a2c8..bdd4c1a 100644
--- a/testapps/src/com/android/server/telecom/testapps/TestInCallUI.java
+++ b/testapps/src/com/android/server/telecom/testapps/TestInCallUI.java
@@ -21,6 +21,7 @@
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.location.Location;
 import android.os.Bundle;
 import android.telecom.Call;
 import android.telecom.CallAudioState;
@@ -28,6 +29,7 @@
 import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
 import android.telecom.VideoProfile;
+import android.telephony.ims.ImsCallProfile;
 import android.util.Log;
 import android.view.View;
 import android.view.View.OnClickListener;
@@ -248,6 +250,47 @@
                 enableInCallService();
             }
         });
+
+        // Find the ringing call and populate the composer extras
+        for (int i = 0; i < TestCallList.getInstance().size(); i++) {
+            Call call = TestCallList.getInstance().getCall(i);
+            if (call.getState() == Call.STATE_RINGING) {
+                int priority = call.getDetails()
+                        .getIntentExtras().getInt(TelecomManager.EXTRA_PRIORITY, -1);
+                Location location = call.getDetails()
+                        .getIntentExtras().getParcelable(TelecomManager.EXTRA_LOCATION);
+                String subject = call.getDetails()
+                        .getIntentExtras().getString(TelecomManager.EXTRA_CALL_SUBJECT);
+                boolean isBusiness = call.getDetails()
+                        .getExtras().getBoolean(ImsCallProfile.EXTRA_IS_BUSINESS_CALL);
+
+                StringBuilder display = new StringBuilder();
+                display.append("priority=");
+                switch (priority) {
+                    case TelecomManager.PRIORITY_NORMAL:
+                        display.append("normal");
+                        break;
+                    case TelecomManager.PRIORITY_URGENT:
+                        display.append("urgent");
+                        break;
+                    default:
+                        display.append("unset");
+                }
+                display.append(";");
+                if (location != null) {
+                    display.append("lat=" + location.getLatitude());
+                    display.append("lon=" + location.getLongitude());
+                } else {
+                    display.append("loc=null");
+                }
+
+                display.append(" subject=" + subject);
+                display.append(" isBusiness=" + isBusiness);
+                TextView attachmentsTextView = findViewById(R.id.incoming_composer_attachments);
+                attachmentsTextView.setText(display.toString());
+                break;
+            }
+        }
     }
 
     public void updateCallAudioState(CallAudioState cas) {
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index 22f5348..60d38af 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -36,6 +36,13 @@
     <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
 
+    <!-- Used to access Projection State APIs -->
+    <uses-permission android:name="android.permission.READ_PROJECTION_STATE"/>
+
+    <!-- Used to access PlatformCompat APIs -->
+    <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG" />
+    <uses-permission android:name="android.permission.LOG_COMPAT_CHANGE" />
+
     <application android:label="@string/app_name"
                  android:debuggable="true">
         <uses-library android:name="android.test.runner" />
diff --git a/tests/AndroidTest.xml b/tests/AndroidTest.xml
index 3fb2a84..02b35a4 100644
--- a/tests/AndroidTest.xml
+++ b/tests/AndroidTest.xml
@@ -27,4 +27,10 @@
         <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
         <option name="hidden-api-checks" value="false"/>
     </test>
+    <object type="module_controller"
+            class="com.android.tradefed.testtype.suite.module.TestFailureModuleController">
+        <option name="screenshot-on-failure" value="false" />
+        <option name="bugreportz-on-failure" value="false" />
+        <option name="logcat-on-failure" value="true" />
+  </object>
 </configuration>
diff --git a/tests/src/com/android/server/telecom/tests/AnalyticsTests.java b/tests/src/com/android/server/telecom/tests/AnalyticsTests.java
index ec5f7ba..2a304a4 100644
--- a/tests/src/com/android/server/telecom/tests/AnalyticsTests.java
+++ b/tests/src/com/android/server/telecom/tests/AnalyticsTests.java
@@ -16,6 +16,9 @@
 
 package com.android.server.telecom.tests;
 
+import static android.provider.CallLog.Calls.MISSED_REASON_NOT_MISSED;
+import static android.provider.CallLog.Calls.USER_MISSED_CALL_FILTERS_TIMEOUT;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -27,6 +30,7 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import android.content.ContentResolver;
 import android.content.Context;
 import android.os.Build;
 import android.telecom.CallAudioState;
@@ -127,7 +131,8 @@
         Analytics.dump(ip);
         String dumpResult = sr.toString();
         String[] expectedFields = {"startTime", "endTime", "direction", "isAdditionalCall",
-                "isInterrupted", "callTechnologies", "callTerminationReason", "connectionService"};
+                "isInterrupted", "callTechnologies", "callTerminationReason", "connectionService",
+                "missedReason"};
         for (String field : expectedFields) {
             assertTrue(dumpResult.contains(field));
         }
@@ -181,6 +186,8 @@
     @MediumTest
     @Test
     public void testAnalyticsTwoCalls() throws Exception {
+        when(mTimeoutsAdapter.getCallScreeningTimeoutMillis(any(ContentResolver.class)))
+                .thenReturn((long) TEST_TIMEOUT);
         IdPair testCall1 = startAndMakeActiveIncomingCall(
                 "650-555-1212",
                 mPhoneAccountA0.getAccountHandle(),
@@ -200,6 +207,10 @@
         assertTrue(callAnalytics2.startTime > 0);
         assertEquals(0, callAnalytics1.endTime);
         assertEquals(0, callAnalytics2.endTime);
+        long missedReason1 = callAnalytics1.missedReason;
+        assertTrue(missedReason1 == MISSED_REASON_NOT_MISSED
+                || missedReason1 == USER_MISSED_CALL_FILTERS_TIMEOUT);
+        assertEquals(MISSED_REASON_NOT_MISSED, callAnalytics2.missedReason);
 
         assertEquals(Analytics.INCOMING_DIRECTION, callAnalytics1.callDirection);
         assertEquals(Analytics.OUTGOING_DIRECTION, callAnalytics2.callDirection);
diff --git a/tests/src/com/android/server/telecom/tests/BasicCallTests.java b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
index 6fd53eb..a3b8654 100644
--- a/tests/src/com/android/server/telecom/tests/BasicCallTests.java
+++ b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
@@ -63,6 +63,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -163,6 +164,9 @@
                 .getApplicationContext().getSystemService(Context.TELECOM_SERVICE);
         telecomManager.acceptRingingCall();
 
+        waitForHandlerAction(mTelecomSystem.getCallsManager()
+                .getConnectionServiceFocusManager().getHandler(), TEST_TIMEOUT);
+
         verify(mConnectionServiceFixtureA.getTestDouble(), timeout(TEST_TIMEOUT))
                 .answer(eq(ids.mConnectionId), any());
         mConnectionServiceFixtureA.sendSetActive(ids.mConnectionId);
@@ -219,6 +223,9 @@
                 .getApplicationContext().getSystemService(Context.TELECOM_SERVICE);
         telecomManager.acceptRingingCall(VideoProfile.STATE_AUDIO_ONLY);
 
+        waitForHandlerAction(mTelecomSystem.getCallsManager()
+                .getConnectionServiceFocusManager().getHandler(), TEST_TIMEOUT);
+
         // The generic answer method on the ConnectionService is used to answer audio-only calls.
         verify(mConnectionServiceFixtureA.getTestDouble(), timeout(TEST_TIMEOUT))
                 .answer(eq(ids.mConnectionId), any());
@@ -248,6 +255,9 @@
                 .getApplicationContext().getSystemService(Context.TELECOM_SERVICE);
         telecomManager.acceptRingingCall(999 /* invalid videostate */);
 
+        waitForHandlerAction(mTelecomSystem.getCallsManager()
+                .getConnectionServiceFocusManager().getHandler(), TEST_TIMEOUT);
+
         // Answer video API should be called
         verify(mConnectionServiceFixtureA.getTestDouble(), timeout(TEST_TIMEOUT))
                 .answerVideo(eq(ids.mConnectionId), eq(VideoProfile.STATE_BIDIRECTIONAL), any());
@@ -464,6 +474,7 @@
     @LargeTest
     @Test
     @FlakyTest
+    @Ignore("b/189904580")
     public void testIncomingCallFromBlockedNumberIsRejected() throws Exception {
         String phoneNumber = "650-555-1212";
         blockNumber(phoneNumber);
@@ -483,6 +494,7 @@
 
         waitForHandlerAction(mConnectionServiceFixtureA.mConnectionServiceDelegate.getHandler(),
                 TEST_TIMEOUT);
+
         assertEquals(1, mCallerInfoAsyncQueryFactoryFixture.mRequests.size());
         for (CallerInfoAsyncQueryFactoryFixture.Request request :
                 mCallerInfoAsyncQueryFactoryFixture.mRequests) {
@@ -626,6 +638,10 @@
         mConnectionServiceFixtureA.
                 sendSetDisconnected(outgoing.mConnectionId, DisconnectCause.REMOTE);
 
+        waitForHandlerAction(mTelecomSystem.getCallsManager().getCallAudioManager()
+                .getCallAudioModeStateMachine().getHandler(), TEST_TIMEOUT);
+        waitForHandlerAction(mTelecomSystem.getCallsManager().getCallAudioManager()
+                .getCallAudioRouteStateMachine().getHandler(), TEST_TIMEOUT);
         verify(audioManager, timeout(TEST_TIMEOUT))
                 .abandonAudioFocusForCall();
         verify(audioManager, timeout(TEST_TIMEOUT).atLeastOnce())
@@ -814,8 +830,7 @@
 
     private void blockNumberWithAnswer(String phoneNumber, Answer answer) throws Exception {
         when(getBlockedNumberProvider().call(
-                anyString(),
-                nullable(String.class),
+                any(),
                 anyString(),
                 eq(BlockedNumberContract.SystemContract.METHOD_SHOULD_SYSTEM_BLOCK_NUMBER),
                 eq(phoneNumber),
diff --git a/tests/src/com/android/server/telecom/tests/BlockedNumbersUtilTests.java b/tests/src/com/android/server/telecom/tests/BlockedNumbersUtilTests.java
new file mode 100644
index 0000000..56cb735
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/BlockedNumbersUtilTests.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.telecom.tests;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.verify;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.os.UserHandle;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.telecom.settings.BlockedNumbersUtil;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class BlockedNumbersUtilTests extends TelecomTestCase {
+    @Override
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+    }
+
+    @SmallTest
+    @Test
+    public void testPostNotification() {
+        BlockedNumbersUtil.updateEmergencyCallNotification(mContext, true);
+        NotificationManager mgr = mComponentContextFixture.getNotificationManager();
+        verify(mgr).notifyAsUser(isNull(), anyInt(), any(Notification.class),
+                any(UserHandle.class));
+    }
+
+    @SmallTest
+    @Test
+    public void testDismissNotification() {
+        BlockedNumbersUtil.updateEmergencyCallNotification(mContext, false);
+        NotificationManager mgr = mComponentContextFixture.getNotificationManager();
+        verify(mgr).cancelAsUser(isNull(), anyInt(), any(UserHandle.class));
+    }
+}
diff --git a/tests/src/com/android/server/telecom/tests/BluetoothPhoneServiceTest.java b/tests/src/com/android/server/telecom/tests/BluetoothPhoneServiceTest.java
deleted file mode 100644
index 532cc7e..0000000
--- a/tests/src/com/android/server/telecom/tests/BluetoothPhoneServiceTest.java
+++ /dev/null
@@ -1,1136 +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 android.bluetooth.BluetoothAdapter;
-import android.content.ComponentName;
-import android.content.Intent;
-import android.graphics.drawable.Icon;
-import android.net.Uri;
-import android.os.Binder;
-import android.telecom.Connection;
-import android.telecom.GatewayInfo;
-import android.telecom.PhoneAccount;
-import android.telecom.PhoneAccountHandle;
-import android.telephony.PhoneNumberUtils;
-import android.telephony.TelephonyManager;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import com.android.server.telecom.BluetoothAdapterProxy;
-import com.android.server.telecom.BluetoothHeadsetProxy;
-import com.android.server.telecom.BluetoothPhoneServiceImpl;
-import com.android.server.telecom.Call;
-import com.android.server.telecom.CallState;
-import com.android.server.telecom.CallsManager;
-import com.android.server.telecom.PhoneAccountRegistrar;
-import com.android.server.telecom.TelecomSystem;
-
-import org.junit.After;
-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;
-
-import java.util.ArrayList;
-import java.util.LinkedList;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.ArgumentMatchers.nullable;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyBoolean;
-import static org.mockito.Matchers.anyChar;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Matchers.isNull;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.when;
-import static org.mockito.Mockito.verify;
-
-@RunWith(JUnit4.class)
-public class BluetoothPhoneServiceTest extends TelecomTestCase {
-
-    private static final int TEST_DTMF_TONE = 0;
-    private static final String TEST_ACCOUNT_ADDRESS = "//foo.com/";
-    private static final int TEST_ACCOUNT_INDEX = 0;
-
-    // match up with BluetoothPhoneServiceImpl
-    private static final int CALL_STATE_ACTIVE = 0;
-    private static final int CALL_STATE_HELD = 1;
-    private static final int CALL_STATE_DIALING = 2;
-    private static final int CALL_STATE_ALERTING = 3;
-    private static final int CALL_STATE_INCOMING = 4;
-    private static final int CALL_STATE_WAITING = 5;
-    private static final int CALL_STATE_IDLE = 6;
-    private static final int CALL_STATE_DISCONNECTED = 7;
-    // Terminate all held or set UDUB("busy") to a waiting call
-    private static final int CHLD_TYPE_RELEASEHELD = 0;
-    // Terminate all active calls and accepts a waiting/held call
-    private static final int CHLD_TYPE_RELEASEACTIVE_ACCEPTHELD = 1;
-    // Hold all active calls and accepts a waiting/held call
-    private static final int CHLD_TYPE_HOLDACTIVE_ACCEPTHELD = 2;
-    // Add all held calls to a conference
-    private static final int CHLD_TYPE_ADDHELDTOCONF = 3;
-
-    private BluetoothPhoneServiceImpl mBluetoothPhoneService;
-    private final TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() {
-    };
-
-    @Mock CallsManager mMockCallsManager;
-    @Mock PhoneAccountRegistrar mMockPhoneAccountRegistrar;
-    @Mock BluetoothHeadsetProxy mMockBluetoothHeadset;
-
-    @Override
-    @Before
-    public void setUp() throws Exception {
-        super.setUp();
-        MockitoAnnotations.initMocks(this);
-        mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
-
-        // Ensure initialization does not actually try to access any of the CallsManager fields.
-        // This also works to return null if it is not overwritten later in the test.
-        doNothing().when(mMockCallsManager).addListener(any(
-                CallsManager.CallsManagerListener.class));
-        doReturn(null).when(mMockCallsManager).getActiveCall();
-        doReturn(null).when(mMockCallsManager).getRingingOrSimulatedRingingCall();
-        doReturn(null).when(mMockCallsManager).getHeldCall();
-        doReturn(null).when(mMockCallsManager).getOutgoingCall();
-        doReturn(0).when(mMockCallsManager).getNumHeldCalls();
-        doReturn(false).when(mMockCallsManager).hasOnlyDisconnectedCalls();
-        mBluetoothPhoneService = new BluetoothPhoneServiceImpl(mContext, mLock, mMockCallsManager,
-                mock(BluetoothAdapterProxy.class), mMockPhoneAccountRegistrar);
-
-        // Bring in test Bluetooth Headset
-        mBluetoothPhoneService.setBluetoothHeadset(mMockBluetoothHeadset);
-    }
-
-    @Override
-    @After
-    public void tearDown() throws Exception {
-
-        mBluetoothPhoneService = null;
-        super.tearDown();
-    }
-
-    @SmallTest
-    @Test
-    public void testHeadsetAnswerCall() throws Exception {
-        Call mockCall = createRingingCall();
-
-        boolean callAnswered = mBluetoothPhoneService.mBinder.answerCall();
-
-        verify(mMockCallsManager).answerCall(eq(mockCall), any(int.class));
-        assertEquals(callAnswered, true);
-    }
-
-    @SmallTest
-    @Test
-    public void testHeadsetAnswerCallNull() throws Exception {
-        when(mMockCallsManager.getRingingOrSimulatedRingingCall()).thenReturn(null);
-
-        boolean callAnswered = mBluetoothPhoneService.mBinder.answerCall();
-
-        verify(mMockCallsManager,never()).answerCall(any(Call.class), any(int.class));
-        assertEquals(callAnswered, false);
-    }
-
-    @SmallTest
-    @Test
-    public void testHeadsetHangupCall() throws Exception {
-        Call mockCall = createForegroundCall();
-
-        boolean callHungup = mBluetoothPhoneService.mBinder.hangupCall();
-
-        verify(mMockCallsManager).disconnectCall(eq(mockCall));
-        assertEquals(callHungup, true);
-    }
-
-    @SmallTest
-    @Test
-    public void testHeadsetHangupCallNull() throws Exception {
-        when(mMockCallsManager.getForegroundCall()).thenReturn(null);
-
-        boolean callHungup = mBluetoothPhoneService.mBinder.hangupCall();
-
-        verify(mMockCallsManager,never()).disconnectCall(any(Call.class));
-        assertEquals(callHungup, false);
-    }
-
-    @SmallTest
-    @Test
-    public void testHeadsetSendDTMF() throws Exception {
-        Call mockCall = createForegroundCall();
-
-        boolean sentDtmf = mBluetoothPhoneService.mBinder.sendDtmf(TEST_DTMF_TONE);
-
-        verify(mMockCallsManager).playDtmfTone(eq(mockCall), eq((char) TEST_DTMF_TONE));
-        verify(mMockCallsManager).stopDtmfTone(eq(mockCall));
-        assertEquals(sentDtmf, true);
-    }
-
-    @SmallTest
-    @Test
-    public void testHeadsetSendDTMFNull() throws Exception {
-        when(mMockCallsManager.getForegroundCall()).thenReturn(null);
-
-        boolean sentDtmf = mBluetoothPhoneService.mBinder.sendDtmf(TEST_DTMF_TONE);
-
-        verify(mMockCallsManager,never()).playDtmfTone(any(Call.class), anyChar());
-        verify(mMockCallsManager,never()).stopDtmfTone(any(Call.class));
-        assertEquals(sentDtmf, false);
-    }
-
-    @SmallTest
-    @Test
-    public void testGetNetworkOperator() throws Exception {
-        Call mockCall = createForegroundCall();
-        PhoneAccount fakePhoneAccount = makeQuickAccount("id0", TEST_ACCOUNT_INDEX);
-        when(mMockPhoneAccountRegistrar.getPhoneAccountOfCurrentUser(
-                nullable(PhoneAccountHandle.class))).thenReturn(fakePhoneAccount);
-
-        String networkOperator = mBluetoothPhoneService.mBinder.getNetworkOperator();
-
-        assertEquals(networkOperator, "label0");
-    }
-
-    @SmallTest
-    @Test
-    public void testGetNetworkOperatorNoPhoneAccount() throws Exception {
-        when(mMockCallsManager.getForegroundCall()).thenReturn(null);
-
-        String networkOperator = mBluetoothPhoneService.mBinder.getNetworkOperator();
-
-        assertEquals(networkOperator, "label1");
-    }
-
-    @SmallTest
-    @Test
-    public void testGetSubscriberNumber() throws Exception {
-        Call mockCall = createForegroundCall();
-        PhoneAccount fakePhoneAccount = makeQuickAccount("id0", TEST_ACCOUNT_INDEX);
-        when(mMockPhoneAccountRegistrar.getPhoneAccountOfCurrentUser(
-                nullable(PhoneAccountHandle.class))).thenReturn(fakePhoneAccount);
-
-        String subscriberNumber = mBluetoothPhoneService.mBinder.getSubscriberNumber();
-
-        assertEquals(subscriberNumber, TEST_ACCOUNT_ADDRESS + TEST_ACCOUNT_INDEX);
-    }
-
-    @SmallTest
-    @Test
-    public void testGetSubscriberNumberFallbackToTelephony() throws Exception {
-        Call mockCall = createForegroundCall();
-        String fakeNumber = "8675309";
-        when(mMockPhoneAccountRegistrar.getPhoneAccountOfCurrentUser(
-                nullable(PhoneAccountHandle.class))).thenReturn(null);
-        when(mMockPhoneAccountRegistrar.getPhoneAccountUnchecked(
-                nullable(PhoneAccountHandle.class))).thenReturn(null);
-        when(mComponentContextFixture.getTelephonyManager().getLine1Number())
-                .thenReturn(fakeNumber);
-
-        String subscriberNumber = mBluetoothPhoneService.mBinder.getSubscriberNumber();
-
-        assertEquals(subscriberNumber, fakeNumber);
-    }
-
-    @MediumTest
-    @Test
-    public void testListCurrentCallsOneCall() throws Exception {
-        ArrayList<Call> calls = new ArrayList<>();
-        Call activeCall = createActiveCall();
-        when(activeCall.getState()).thenReturn(CallState.ACTIVE);
-        calls.add(activeCall);
-        when(activeCall.isConference()).thenReturn(false);
-        when(activeCall.getHandle()).thenReturn(Uri.parse("tel:555-000"));
-        when(mMockCallsManager.getCalls()).thenReturn(calls);
-
-        mBluetoothPhoneService.mBinder.listCurrentCalls();
-
-        verify(mMockBluetoothHeadset).clccResponse(eq(1), eq(0), eq(0), eq(0), eq(false),
-                eq("555000"), eq(PhoneNumberUtils.TOA_Unknown));
-        verify(mMockBluetoothHeadset).clccResponse(0, 0, 0, 0, false, null, 0);
-    }
-
-    @MediumTest
-    @Test
-    public void testListCurrentCallsSilentRinging() throws Exception {
-        ArrayList<Call> calls = new ArrayList<>();
-        Call silentRingingCall = createActiveCall();
-        when(silentRingingCall.getState()).thenReturn(CallState.RINGING);
-        when(silentRingingCall.isSilentRingingRequested()).thenReturn(true);
-        calls.add(silentRingingCall);
-        when(silentRingingCall.isConference()).thenReturn(false);
-        when(silentRingingCall.getHandle()).thenReturn(Uri.parse("tel:555-000"));
-        when(mMockCallsManager.getCalls()).thenReturn(calls);
-        when(mMockCallsManager.getRingingOrSimulatedRingingCall()).thenReturn(silentRingingCall);
-
-        mBluetoothPhoneService.mBinder.listCurrentCalls();
-
-        verify(mMockBluetoothHeadset, never()).clccResponse(eq(1), eq(0), eq(0), eq(0), eq(false),
-            eq("555000"), eq(PhoneNumberUtils.TOA_Unknown));
-        verify(mMockBluetoothHeadset).clccResponse(0, 0, 0, 0, false, null, 0);
-    }
-
-    @MediumTest
-    @Test
-    public void testConferenceInProgressCDMA() throws Exception {
-        // If two calls are being conferenced and updateHeadsetWithCallState runs while this is
-        // still occuring, it will look like there is an active and held call still while we are
-        // transitioning into a conference.
-        // Call has been put into a CDMA "conference" with one call on hold.
-        ArrayList<Call> calls = new ArrayList<>();
-        Call parentCall = createActiveCall();
-        final Call confCall1 = mock(Call.class);
-        final Call confCall2 = createHeldCall();
-        calls.add(parentCall);
-        calls.add(confCall1);
-        calls.add(confCall2);
-        when(mMockCallsManager.getCalls()).thenReturn(calls);
-        when(confCall1.getState()).thenReturn(CallState.ACTIVE);
-        when(confCall2.getState()).thenReturn(CallState.ACTIVE);
-        when(confCall1.isIncoming()).thenReturn(false);
-        when(confCall2.isIncoming()).thenReturn(true);
-        when(confCall1.getGatewayInfo()).thenReturn(new GatewayInfo(null, null,
-                Uri.parse("tel:555-0000")));
-        when(confCall2.getGatewayInfo()).thenReturn(new GatewayInfo(null, null,
-                Uri.parse("tel:555-0001")));
-        addCallCapability(parentCall, Connection.CAPABILITY_MERGE_CONFERENCE);
-        addCallCapability(parentCall, Connection.CAPABILITY_SWAP_CONFERENCE);
-        removeCallCapability(parentCall, Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN);
-        when(parentCall.getConferenceLevelActiveCall()).thenReturn(confCall1);
-        when(parentCall.isConference()).thenReturn(true);
-        when(parentCall.getChildCalls()).thenReturn(new LinkedList<Call>() {{
-            add(confCall1);
-            add(confCall2);
-        }});
-        //Add links from child calls to parent
-        when(confCall1.getParentCall()).thenReturn(parentCall);
-        when(confCall2.getParentCall()).thenReturn(parentCall);
-
-        mBluetoothPhoneService.mBinder.queryPhoneState();
-        verify(mMockBluetoothHeadset).phoneStateChanged(eq(1), eq(1), eq(CALL_STATE_IDLE),
-                eq(""), eq(128), 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), 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),
-                nullable(String.class));
-    }
-
-    @MediumTest
-    @Test
-    public void testListCurrentCallsCdmaHold() throws Exception {
-        // Call has been put into a CDMA "conference" with one call on hold.
-        ArrayList<Call> calls = new ArrayList<>();
-        Call parentCall = createActiveCall();
-        final Call foregroundCall = mock(Call.class);
-        final Call heldCall = createHeldCall();
-        calls.add(parentCall);
-        calls.add(foregroundCall);
-        calls.add(heldCall);
-        when(mMockCallsManager.getCalls()).thenReturn(calls);
-        when(foregroundCall.getState()).thenReturn(CallState.ACTIVE);
-        when(heldCall.getState()).thenReturn(CallState.ACTIVE);
-        when(foregroundCall.isIncoming()).thenReturn(false);
-        when(heldCall.isIncoming()).thenReturn(true);
-        when(foregroundCall.getGatewayInfo()).thenReturn(new GatewayInfo(null, null,
-                Uri.parse("tel:555-0000")));
-        when(heldCall.getGatewayInfo()).thenReturn(new GatewayInfo(null, null,
-                Uri.parse("tel:555-0001")));
-        addCallCapability(parentCall, Connection.CAPABILITY_MERGE_CONFERENCE);
-        addCallCapability(parentCall, Connection.CAPABILITY_SWAP_CONFERENCE);
-        removeCallCapability(parentCall, Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN);
-        when(parentCall.getConferenceLevelActiveCall()).thenReturn(foregroundCall);
-        when(parentCall.isConference()).thenReturn(true);
-        when(parentCall.getChildCalls()).thenReturn(new LinkedList<Call>() {{
-            add(foregroundCall);
-            add(heldCall);
-        }});
-        //Add links from child calls to parent
-        when(foregroundCall.getParentCall()).thenReturn(parentCall);
-        when(heldCall.getParentCall()).thenReturn(parentCall);
-
-        mBluetoothPhoneService.mBinder.listCurrentCalls();
-
-        verify(mMockBluetoothHeadset).clccResponse(eq(1), eq(0), eq(CALL_STATE_ACTIVE), eq(0),
-                eq(false), eq("5550000"), eq(PhoneNumberUtils.TOA_Unknown));
-        verify(mMockBluetoothHeadset).clccResponse(eq(2), eq(1), eq(CALL_STATE_HELD), eq(0),
-                eq(false), eq("5550001"), eq(PhoneNumberUtils.TOA_Unknown));
-        verify(mMockBluetoothHeadset).clccResponse(0, 0, 0, 0, false, null, 0);
-    }
-
-    @MediumTest
-    @Test
-    public void testListCurrentCallsCdmaConference() throws Exception {
-        // Call is in a true CDMA conference
-        ArrayList<Call> calls = new ArrayList<>();
-        Call parentCall = createActiveCall();
-        final Call confCall1 = mock(Call.class);
-        final Call confCall2 = createHeldCall();
-        calls.add(parentCall);
-        calls.add(confCall1);
-        calls.add(confCall2);
-        when(mMockCallsManager.getCalls()).thenReturn(calls);
-        when(confCall1.getState()).thenReturn(CallState.ACTIVE);
-        when(confCall2.getState()).thenReturn(CallState.ACTIVE);
-        when(confCall1.isIncoming()).thenReturn(false);
-        when(confCall2.isIncoming()).thenReturn(true);
-        when(confCall1.getGatewayInfo()).thenReturn(new GatewayInfo(null, null,
-                Uri.parse("tel:555-0000")));
-        when(confCall2.getGatewayInfo()).thenReturn(new GatewayInfo(null, null,
-                Uri.parse("tel:555-0001")));
-        removeCallCapability(parentCall, Connection.CAPABILITY_MERGE_CONFERENCE);
-        removeCallCapability(parentCall, Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN);
-        when(parentCall.wasConferencePreviouslyMerged()).thenReturn(true);
-        when(parentCall.getConferenceLevelActiveCall()).thenReturn(confCall1);
-        when(parentCall.isConference()).thenReturn(true);
-        when(parentCall.getChildCalls()).thenReturn(new LinkedList<Call>() {{
-            add(confCall1);
-            add(confCall2);
-        }});
-        //Add links from child calls to parent
-        when(confCall1.getParentCall()).thenReturn(parentCall);
-        when(confCall2.getParentCall()).thenReturn(parentCall);
-
-        mBluetoothPhoneService.mBinder.listCurrentCalls();
-
-        verify(mMockBluetoothHeadset).clccResponse(eq(1), eq(0), eq(CALL_STATE_ACTIVE), eq(0),
-                eq(true), eq("5550000"), eq(PhoneNumberUtils.TOA_Unknown));
-        verify(mMockBluetoothHeadset).clccResponse(eq(2), eq(1), eq(CALL_STATE_ACTIVE), eq(0),
-                eq(true), eq("5550001"), eq(PhoneNumberUtils.TOA_Unknown));
-        verify(mMockBluetoothHeadset).clccResponse(0, 0, 0, 0, false, null, 0);
-    }
-
-    @MediumTest
-    @Test
-    public void testWaitingCallClccResponse() throws Exception {
-        ArrayList<Call> calls = new ArrayList<>();
-        when(mMockCallsManager.getCalls()).thenReturn(calls);
-        // This test does not define a value for getForegroundCall(), so this ringing call will
-        // be treated as if it is a waiting call when listCurrentCalls() is invoked.
-        Call waitingCall = createRingingCall();
-        calls.add(waitingCall);
-        when(waitingCall.isIncoming()).thenReturn(true);
-        when(waitingCall.getGatewayInfo()).thenReturn(new GatewayInfo(null, null,
-                Uri.parse("tel:555-0000")));
-        when(waitingCall.getState()).thenReturn(CallState.RINGING);
-        when(waitingCall.isConference()).thenReturn(false);
-
-        mBluetoothPhoneService.mBinder.listCurrentCalls();
-        verify(mMockBluetoothHeadset).clccResponse(1, 1, CALL_STATE_WAITING, 0, false,
-                "5550000", PhoneNumberUtils.TOA_Unknown);
-        verify(mMockBluetoothHeadset).clccResponse(0, 0, 0, 0, false, null, 0);
-        verify(mMockBluetoothHeadset, times(2)).clccResponse(anyInt(),
-                anyInt(), anyInt(), anyInt(), anyBoolean(), nullable(String.class), anyInt());
-    }
-
-    @MediumTest
-    @Test
-    public void testNewCallClccResponse() throws Exception {
-        ArrayList<Call> calls = new ArrayList<>();
-        when(mMockCallsManager.getCalls()).thenReturn(calls);
-        Call newCall = createForegroundCall();
-        calls.add(newCall);
-        when(newCall.getState()).thenReturn(CallState.NEW);
-        when(newCall.isConference()).thenReturn(false);
-
-        mBluetoothPhoneService.mBinder.listCurrentCalls();
-        verify(mMockBluetoothHeadset).clccResponse(0, 0, 0, 0, false, null, 0);
-        verify(mMockBluetoothHeadset, times(1)).clccResponse(anyInt(),
-                anyInt(), anyInt(), anyInt(), anyBoolean(), nullable(String.class), anyInt());
-    }
-
-    @MediumTest
-    @Test
-    public void testRingingCallClccResponse() throws Exception {
-        ArrayList<Call> calls = new ArrayList<>();
-        when(mMockCallsManager.getCalls()).thenReturn(calls);
-        Call ringingCall = createForegroundCall();
-        calls.add(ringingCall);
-        when(ringingCall.getState()).thenReturn(CallState.RINGING);
-        when(ringingCall.isIncoming()).thenReturn(true);
-        when(ringingCall.isConference()).thenReturn(false);
-        when(ringingCall.getGatewayInfo()).thenReturn(new GatewayInfo(null, null,
-                Uri.parse("tel:555-0000")));
-
-        mBluetoothPhoneService.mBinder.listCurrentCalls();
-        verify(mMockBluetoothHeadset).clccResponse(1, 1, CALL_STATE_INCOMING, 0, false,
-                "5550000", PhoneNumberUtils.TOA_Unknown);
-        verify(mMockBluetoothHeadset).clccResponse(0, 0, 0, 0, false, null, 0);
-        verify(mMockBluetoothHeadset, times(2)).clccResponse(anyInt(),
-                anyInt(), anyInt(), anyInt(), anyBoolean(), nullable(String.class), anyInt());
-    }
-
-    @MediumTest
-    @Test
-    public void testCallClccCache() throws Exception {
-        ArrayList<Call> calls = new ArrayList<>();
-        when(mMockCallsManager.getCalls()).thenReturn(calls);
-        Call ringingCall = createForegroundCall();
-        calls.add(ringingCall);
-        when(ringingCall.getState()).thenReturn(CallState.RINGING);
-        when(ringingCall.isIncoming()).thenReturn(true);
-        when(ringingCall.isConference()).thenReturn(false);
-        when(ringingCall.getGatewayInfo()).thenReturn(new GatewayInfo(null, null,
-                Uri.parse("tel:5550000")));
-
-        mBluetoothPhoneService.mBinder.listCurrentCalls();
-        verify(mMockBluetoothHeadset).clccResponse(1, 1, CALL_STATE_INCOMING, 0, false,
-                "5550000", PhoneNumberUtils.TOA_Unknown);
-
-        // Test Caching of old call indicies in clcc
-        when(ringingCall.getState()).thenReturn(CallState.ACTIVE);
-        Call newHoldingCall = createHeldCall();
-        calls.add(0, newHoldingCall);
-        when(newHoldingCall.getState()).thenReturn(CallState.ON_HOLD);
-        when(newHoldingCall.isIncoming()).thenReturn(true);
-        when(newHoldingCall.isConference()).thenReturn(false);
-        when(newHoldingCall.getGatewayInfo()).thenReturn(new GatewayInfo(null, null,
-                Uri.parse("tel:555-0001")));
-
-        mBluetoothPhoneService.mBinder.listCurrentCalls();
-        verify(mMockBluetoothHeadset).clccResponse(1, 1, CALL_STATE_ACTIVE, 0, false,
-                "5550000", PhoneNumberUtils.TOA_Unknown);
-        verify(mMockBluetoothHeadset).clccResponse(2, 1, CALL_STATE_HELD, 0, false,
-                "5550001", PhoneNumberUtils.TOA_Unknown);
-        verify(mMockBluetoothHeadset, times(2)).clccResponse(0, 0, 0, 0, false, null, 0);
-    }
-
-    @MediumTest
-    @Test
-    public void testAlertingCallClccResponse() throws Exception {
-        ArrayList<Call> calls = new ArrayList<>();
-        when(mMockCallsManager.getCalls()).thenReturn(calls);
-        Call dialingCall = createForegroundCall();
-        calls.add(dialingCall);
-        when(dialingCall.getState()).thenReturn(CallState.DIALING);
-        when(dialingCall.isIncoming()).thenReturn(false);
-        when(dialingCall.isConference()).thenReturn(false);
-        when(dialingCall.getGatewayInfo()).thenReturn(new GatewayInfo(null, null,
-                Uri.parse("tel:555-0000")));
-
-        mBluetoothPhoneService.mBinder.listCurrentCalls();
-        verify(mMockBluetoothHeadset).clccResponse(1, 0, CALL_STATE_ALERTING, 0, false,
-                "5550000", PhoneNumberUtils.TOA_Unknown);
-        verify(mMockBluetoothHeadset).clccResponse(0, 0, 0, 0, false, null, 0);
-        verify(mMockBluetoothHeadset, times(2)).clccResponse(anyInt(),
-                anyInt(), anyInt(), anyInt(), anyBoolean(), nullable(String.class), anyInt());
-    }
-
-    @MediumTest
-    @Test
-    public void testHoldingCallClccResponse() throws Exception {
-        ArrayList<Call> calls = new ArrayList<>();
-        when(mMockCallsManager.getCalls()).thenReturn(calls);
-        Call dialingCall = createForegroundCall();
-        calls.add(dialingCall);
-        when(dialingCall.getState()).thenReturn(CallState.DIALING);
-        when(dialingCall.isIncoming()).thenReturn(false);
-        when(dialingCall.isConference()).thenReturn(false);
-        when(dialingCall.getGatewayInfo()).thenReturn(new GatewayInfo(null, null,
-                Uri.parse("tel:555-0000")));
-        Call holdingCall = createHeldCall();
-        calls.add(holdingCall);
-        when(holdingCall.getState()).thenReturn(CallState.ON_HOLD);
-        when(holdingCall.isIncoming()).thenReturn(true);
-        when(holdingCall.isConference()).thenReturn(false);
-        when(holdingCall.getGatewayInfo()).thenReturn(new GatewayInfo(null, null,
-                Uri.parse("tel:555-0001")));
-
-        mBluetoothPhoneService.mBinder.listCurrentCalls();
-        verify(mMockBluetoothHeadset).clccResponse(1, 0, CALL_STATE_ALERTING, 0, false,
-                "5550000", PhoneNumberUtils.TOA_Unknown);
-        verify(mMockBluetoothHeadset).clccResponse(2, 1, CALL_STATE_HELD, 0, false,
-                "5550001", PhoneNumberUtils.TOA_Unknown);
-        verify(mMockBluetoothHeadset).clccResponse(0, 0, 0, 0, false, null, 0);
-        verify(mMockBluetoothHeadset, times(3)).clccResponse(anyInt(),
-                anyInt(), anyInt(), anyInt(), anyBoolean(), nullable(String.class), anyInt());
-    }
-
-    @MediumTest
-    @Test
-    public void testListCurrentCallsImsConference() throws Exception {
-        ArrayList<Call> calls = new ArrayList<>();
-        Call parentCall = createActiveCall();
-        calls.add(parentCall);
-        addCallCapability(parentCall, Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN);
-        when(parentCall.isConference()).thenReturn(true);
-        when(parentCall.getState()).thenReturn(CallState.ACTIVE);
-        when(parentCall.isIncoming()).thenReturn(true);
-        when(mMockCallsManager.getCalls()).thenReturn(calls);
-
-        mBluetoothPhoneService.mBinder.listCurrentCalls();
-
-        verify(mMockBluetoothHeadset).clccResponse(eq(1), eq(1), eq(CALL_STATE_ACTIVE), eq(0),
-                eq(true), (String) isNull(), eq(-1));
-        verify(mMockBluetoothHeadset).clccResponse(0, 0, 0, 0, false, null, 0);
-    }
-
-    @MediumTest
-    @Test
-    public void testListCurrentCallsHeldImsCepConference() throws Exception {
-        ArrayList<Call> calls = new ArrayList<>();
-        Call parentCall = createHeldCall();
-        Call childCall1 = createActiveCall();
-        Call childCall2 = createActiveCall();
-        calls.add(parentCall);
-        calls.add(childCall1);
-        calls.add(childCall2);
-        addCallCapability(parentCall, Connection.CAPABILITY_MANAGE_CONFERENCE);
-        when(childCall1.getParentCall()).thenReturn(parentCall);
-        when(childCall2.getParentCall()).thenReturn(parentCall);
-
-        when(parentCall.isConference()).thenReturn(true);
-        when(parentCall.getState()).thenReturn(CallState.ON_HOLD);
-        when(childCall1.getState()).thenReturn(CallState.ACTIVE);
-        when(childCall2.getState()).thenReturn(CallState.ACTIVE);
-
-        when(parentCall.isIncoming()).thenReturn(true);
-        when(mMockCallsManager.getCalls()).thenReturn(calls);
-
-        mBluetoothPhoneService.mBinder.listCurrentCalls();
-
-        verify(mMockBluetoothHeadset).clccResponse(eq(1), eq(0), eq(CALL_STATE_HELD), eq(0),
-                eq(true), (String) isNull(), eq(-1));
-        verify(mMockBluetoothHeadset).clccResponse(eq(2), eq(0), eq(CALL_STATE_HELD), eq(0),
-                eq(true), (String) isNull(), eq(-1));
-        verify(mMockBluetoothHeadset).clccResponse(0, 0, 0, 0, false, null, 0);
-    }
-
-    @MediumTest
-    @Test
-    public void testQueryPhoneState() throws Exception {
-        Call ringingCall = createRingingCall();
-        when(ringingCall.getHandle()).thenReturn(Uri.parse("tel:5550000"));
-
-        mBluetoothPhoneService.mBinder.queryPhoneState();
-
-        verify(mMockBluetoothHeadset).phoneStateChanged(eq(0), eq(0), eq(CALL_STATE_INCOMING),
-                eq("5550000"), eq(PhoneNumberUtils.TOA_Unknown), nullable(String.class));
-    }
-
-    @MediumTest
-    @Test
-    public void testCDMAConferenceQueryState() throws Exception {
-        Call parentConfCall = createActiveCall();
-        final Call confCall1 = mock(Call.class);
-        final Call confCall2 = mock(Call.class);
-        when(parentConfCall.getHandle()).thenReturn(Uri.parse("tel:555-0000"));
-        addCallCapability(parentConfCall, Connection.CAPABILITY_SWAP_CONFERENCE);
-        removeCallCapability(parentConfCall, Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN);
-        when(parentConfCall.wasConferencePreviouslyMerged()).thenReturn(true);
-        when(parentConfCall.isConference()).thenReturn(true);
-        when(parentConfCall.getChildCalls()).thenReturn(new LinkedList<Call>() {{
-            add(confCall1);
-            add(confCall2);
-        }});
-
-        mBluetoothPhoneService.mBinder.queryPhoneState();
-        verify(mMockBluetoothHeadset).phoneStateChanged(eq(1), eq(0), eq(CALL_STATE_IDLE),
-                eq(""), eq(128), nullable(String.class));
-    }
-
-    @MediumTest
-    @Test
-    public void testProcessChldTypeReleaseHeldRinging() throws Exception {
-        Call ringingCall = createRingingCall();
-
-        boolean didProcess = mBluetoothPhoneService.mBinder.processChld(CHLD_TYPE_RELEASEHELD);
-
-        verify(mMockCallsManager).rejectCall(eq(ringingCall), eq(false), nullable(String.class));
-        assertEquals(didProcess, true);
-    }
-
-    @MediumTest
-    @Test
-    public void testProcessChldTypeReleaseHeldHold() throws Exception {
-        Call onHoldCall = createHeldCall();
-
-        boolean didProcess = mBluetoothPhoneService.mBinder.processChld(CHLD_TYPE_RELEASEHELD);
-
-        verify(mMockCallsManager).disconnectCall(eq(onHoldCall));
-        assertEquals(didProcess, true);
-    }
-
-    @MediumTest
-    @Test
-    public void testProcessChldReleaseActiveRinging() throws Exception {
-        Call activeCall = createActiveCall();
-        Call ringingCall = createRingingCall();
-
-        boolean didProcess = mBluetoothPhoneService.mBinder.processChld(
-                CHLD_TYPE_RELEASEACTIVE_ACCEPTHELD);
-
-        verify(mMockCallsManager).disconnectCall(eq(activeCall));
-        verify(mMockCallsManager).answerCall(eq(ringingCall), any(int.class));
-        assertEquals(didProcess, true);
-    }
-
-    @MediumTest
-    @Test
-    public void testProcessChldReleaseActiveHold() throws Exception {
-        Call activeCall = createActiveCall();
-        Call heldCall = createHeldCall();
-
-        boolean didProcess = mBluetoothPhoneService.mBinder.processChld(
-                CHLD_TYPE_RELEASEACTIVE_ACCEPTHELD);
-
-        verify(mMockCallsManager).disconnectCall(eq(activeCall));
-        // Call unhold will occur as part of CallsManager auto-unholding the background call on its
-        // own.
-        assertEquals(didProcess, true);
-    }
-
-    @MediumTest
-    @Test
-    public void testProcessChldHoldActiveRinging() throws Exception {
-        Call ringingCall = createRingingCall();
-
-        boolean didProcess = mBluetoothPhoneService.mBinder.processChld(
-                CHLD_TYPE_HOLDACTIVE_ACCEPTHELD);
-
-        verify(mMockCallsManager).answerCall(eq(ringingCall), any(int.class));
-        assertEquals(didProcess, true);
-    }
-
-    @MediumTest
-    @Test
-    public void testProcessChldHoldActiveUnhold() throws Exception {
-        Call heldCall = createHeldCall();
-
-        boolean didProcess = mBluetoothPhoneService.mBinder.processChld(
-                CHLD_TYPE_HOLDACTIVE_ACCEPTHELD);
-
-        verify(mMockCallsManager).unholdCall(eq(heldCall));
-        assertEquals(didProcess, true);
-    }
-
-    @MediumTest
-    @Test
-    public void testProcessChldHoldActiveHold() throws Exception {
-        Call activeCall = createActiveCall();
-        addCallCapability(activeCall, Connection.CAPABILITY_HOLD);
-
-        boolean didProcess = mBluetoothPhoneService.mBinder.processChld(
-                CHLD_TYPE_HOLDACTIVE_ACCEPTHELD);
-
-        verify(mMockCallsManager).holdCall(eq(activeCall));
-        assertEquals(didProcess, true);
-    }
-
-    @MediumTest
-    @Test
-    public void testProcessChldAddHeldToConfHolding() throws Exception {
-        Call activeCall = createActiveCall();
-        addCallCapability(activeCall, Connection.CAPABILITY_MERGE_CONFERENCE);
-
-        boolean didProcess = mBluetoothPhoneService.mBinder.processChld(CHLD_TYPE_ADDHELDTOCONF);
-
-        verify(activeCall).mergeConference();
-        assertEquals(didProcess, true);
-    }
-
-    @MediumTest
-    @Test
-    public void testProcessChldAddHeldToConf() throws Exception {
-        Call activeCall = createActiveCall();
-        removeCallCapability(activeCall, Connection.CAPABILITY_MERGE_CONFERENCE);
-        Call conferenceableCall = mock(Call.class);
-        ArrayList<Call> conferenceableCalls = new ArrayList<>();
-        conferenceableCalls.add(conferenceableCall);
-        when(activeCall.getConferenceableCalls()).thenReturn(conferenceableCalls);
-
-        boolean didProcess = mBluetoothPhoneService.mBinder.processChld(CHLD_TYPE_ADDHELDTOCONF);
-
-        verify(mMockCallsManager).conference(activeCall, conferenceableCall);
-        assertEquals(didProcess, true);
-    }
-
-    @MediumTest
-    @Test
-    public void testProcessChldHoldActiveSwapConference() throws Exception {
-        // Create an active CDMA Call with a call on hold and simulate a swapConference().
-        Call parentCall = createActiveCall();
-        final Call foregroundCall = mock(Call.class);
-        final Call heldCall = createHeldCall();
-        addCallCapability(parentCall, Connection.CAPABILITY_SWAP_CONFERENCE);
-        removeCallCapability(parentCall, Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN);
-        when(parentCall.isConference()).thenReturn(true);
-        when(parentCall.wasConferencePreviouslyMerged()).thenReturn(false);
-        when(parentCall.getChildCalls()).thenReturn(new LinkedList<Call>() {{
-            add(foregroundCall);
-            add(heldCall);
-        }});
-
-        boolean didProcess = mBluetoothPhoneService.mBinder.processChld(
-                CHLD_TYPE_HOLDACTIVE_ACCEPTHELD);
-
-        verify(parentCall).swapConference();
-        verify(mMockBluetoothHeadset).phoneStateChanged(eq(1), eq(1), eq(CALL_STATE_IDLE), eq(""),
-                eq(128), nullable(String.class));
-        assertEquals(didProcess, true);
-    }
-
-    // Testing the CallsManager Listener Functionality on Bluetooth
-    @MediumTest
-    @Test
-    public void testOnCallAddedRinging() throws Exception {
-        Call ringingCall = createRingingCall();
-        when(ringingCall.getHandle()).thenReturn(Uri.parse("tel:555000"));
-
-        mBluetoothPhoneService.mCallsManagerListener.onCallAdded(ringingCall);
-
-        verify(mMockBluetoothHeadset).phoneStateChanged(eq(0), eq(0), eq(CALL_STATE_INCOMING),
-                eq("555000"), eq(PhoneNumberUtils.TOA_Unknown), nullable(String.class));
-    }
-
-    @MediumTest
-    @Test
-    public void testSilentRingingCallState() throws Exception {
-        Call ringingCall = createRingingCall();
-        when(ringingCall.isSilentRingingRequested()).thenReturn(true);
-        when(ringingCall.getHandle()).thenReturn(Uri.parse("tel:555000"));
-
-        mBluetoothPhoneService.mCallsManagerListener.onCallAdded(ringingCall);
-
-        verify(mMockBluetoothHeadset, never()).phoneStateChanged(anyInt(), anyInt(), anyInt(),
-                anyString(), anyInt(), nullable(String.class));
-    }
-
-    @MediumTest
-    @Test
-    public void testOnCallAddedCdmaActiveHold() throws Exception {
-        // Call has been put into a CDMA "conference" with one call on hold.
-        Call parentCall = createActiveCall();
-        final Call foregroundCall = mock(Call.class);
-        final Call heldCall = createHeldCall();
-        addCallCapability(parentCall, Connection.CAPABILITY_MERGE_CONFERENCE);
-        removeCallCapability(parentCall, Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN);
-        when(parentCall.isConference()).thenReturn(true);
-        when(parentCall.getChildCalls()).thenReturn(new LinkedList<Call>() {{
-            add(foregroundCall);
-            add(heldCall);
-        }});
-
-        mBluetoothPhoneService.mCallsManagerListener.onCallAdded(parentCall);
-
-        verify(mMockBluetoothHeadset).phoneStateChanged(eq(1), eq(1), eq(CALL_STATE_IDLE),
-                eq(""), eq(128), nullable(String.class));
-    }
-
-    @MediumTest
-    @Test
-    public void testOnCallRemoved() throws Exception {
-        Call activeCall = createActiveCall();
-        mBluetoothPhoneService.mCallsManagerListener.onCallAdded(activeCall);
-        doReturn(null).when(mMockCallsManager).getActiveCall();
-        mBluetoothPhoneService.mCallsManagerListener.onCallRemoved(activeCall);
-
-        verify(mMockBluetoothHeadset).phoneStateChanged(eq(0), eq(0), eq(CALL_STATE_IDLE),
-                eq(""), eq(128), nullable(String.class));
-    }
-
-    @MediumTest
-    @Test
-    public void testOnCallStateChangedConnectingCall() throws Exception {
-        Call activeCall = mock(Call.class);
-        Call connectingCall = mock(Call.class);
-        when(connectingCall.getState()).thenReturn(CallState.CONNECTING);
-        ArrayList<Call> calls = new ArrayList<>();
-        calls.add(connectingCall);
-        calls.add(activeCall);
-        when(mMockCallsManager.getCalls()).thenReturn(calls);
-
-        mBluetoothPhoneService.mCallsManagerListener.onCallStateChanged(activeCall,
-                CallState.ACTIVE, CallState.ON_HOLD);
-
-        verify(mMockBluetoothHeadset, never()).phoneStateChanged(anyInt(), anyInt(), anyInt(),
-                anyString(), anyInt(), nullable(String.class));
-    }
-
-    @MediumTest
-    @Test
-    public void testOnCallAddedAudioProcessing() throws Exception {
-        Call call = mock(Call.class);
-        when(call.getState()).thenReturn(CallState.AUDIO_PROCESSING);
-        mBluetoothPhoneService.mCallsManagerListener.onCallAdded(call);
-
-        verify(mMockBluetoothHeadset, never()).phoneStateChanged(anyInt(), anyInt(), anyInt(),
-                anyString(), anyInt(), nullable(String.class));
-    }
-
-    @MediumTest
-    @Test
-    public void testOnCallStateChangedRingingToAudioProcessing() throws Exception {
-        Call ringingCall = createRingingCall();
-        when(ringingCall.getHandle()).thenReturn(Uri.parse("tel:555000"));
-
-        mBluetoothPhoneService.mCallsManagerListener.onCallAdded(ringingCall);
-
-        verify(mMockBluetoothHeadset).phoneStateChanged(eq(0), eq(0), eq(CALL_STATE_INCOMING),
-                eq("555000"), eq(PhoneNumberUtils.TOA_Unknown), nullable(String.class));
-
-        when(ringingCall.getState()).thenReturn(CallState.AUDIO_PROCESSING);
-        when(mMockCallsManager.getRingingOrSimulatedRingingCall()).thenReturn(null);
-
-        mBluetoothPhoneService.mCallsManagerListener.onCallStateChanged(ringingCall,
-                CallState.RINGING, CallState.AUDIO_PROCESSING);
-
-        verify(mMockBluetoothHeadset).phoneStateChanged(eq(0), eq(0), eq(CALL_STATE_IDLE),
-                eq(""), eq(128), nullable(String.class));
-    }
-
-    @MediumTest
-    @Test
-    public void testOnCallStateChangedAudioProcessingToSimulatedRinging() throws Exception {
-        Call ringingCall = createRingingCall();
-        when(ringingCall.getHandle()).thenReturn(Uri.parse("tel:555-0000"));
-
-        mBluetoothPhoneService.mCallsManagerListener.onCallStateChanged(ringingCall,
-                CallState.AUDIO_PROCESSING, CallState.SIMULATED_RINGING);
-
-        verify(mMockBluetoothHeadset).phoneStateChanged(eq(0), eq(0), eq(CALL_STATE_INCOMING),
-                eq("555-0000"), eq(PhoneNumberUtils.TOA_Unknown), nullable(String.class));
-    }
-
-    @MediumTest
-    @Test
-    public void testOnCallStateChangedAudioProcessingToActive() throws Exception {
-        Call activeCall = createActiveCall();
-        when(activeCall.getState()).thenReturn(CallState.ACTIVE);
-
-        mBluetoothPhoneService.mCallsManagerListener.onCallStateChanged(activeCall,
-                CallState.AUDIO_PROCESSING, CallState.ACTIVE);
-
-        verify(mMockBluetoothHeadset).phoneStateChanged(eq(1), eq(0), eq(CALL_STATE_IDLE),
-                eq(""), eq(128), nullable(String.class));
-    }
-
-    @MediumTest
-    @Test
-    public void testOnCallStateChangedDialing() throws Exception {
-        Call activeCall = createActiveCall();
-
-        mBluetoothPhoneService.mCallsManagerListener.onCallStateChanged(activeCall,
-                CallState.CONNECTING, CallState.DIALING);
-
-        verify(mMockBluetoothHeadset, never()).phoneStateChanged(anyInt(), anyInt(), anyInt(),
-                anyString(), anyInt(), nullable(String.class));
-    }
-
-    @MediumTest
-    @Test
-    public void testOnCallStateChangedAlerting() throws Exception {
-        Call outgoingCall = createOutgoingCall();
-
-        mBluetoothPhoneService.mCallsManagerListener.onCallStateChanged(outgoingCall,
-                CallState.NEW, CallState.DIALING);
-
-        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
-    @Test
-    public void testOnCallStateChangedDisconnected() throws Exception {
-        Call disconnectedCall = createDisconnectedCall();
-        doReturn(true).when(mMockCallsManager).hasOnlyDisconnectedCalls();
-        mBluetoothPhoneService.mCallsManagerListener.onCallStateChanged(disconnectedCall,
-                CallState.DISCONNECTING, CallState.DISCONNECTED);
-        verify(mMockBluetoothHeadset).phoneStateChanged(eq(0), eq(0), eq(CALL_STATE_DISCONNECTED),
-                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), nullable(String.class));
-
-        mBluetoothPhoneService.mCallsManagerListener.onDisconnectedTonePlaying(false);
-        verify(mMockBluetoothHeadset).phoneStateChanged(eq(0), eq(0), eq(CALL_STATE_IDLE),
-                eq(""), eq(128), nullable(String.class));
-    }
-
-    @MediumTest
-    @Test
-    public void testOnCallStateChanged() throws Exception {
-        Call ringingCall = createRingingCall();
-        when(ringingCall.getHandle()).thenReturn(Uri.parse("tel:555-0000"));
-        mBluetoothPhoneService.mCallsManagerListener.onCallAdded(ringingCall);
-
-        verify(mMockBluetoothHeadset).phoneStateChanged(eq(0), eq(0), eq(CALL_STATE_INCOMING),
-                eq("555-0000"), eq(PhoneNumberUtils.TOA_Unknown), nullable(String.class));
-
-        //Switch to active
-        doReturn(null).when(mMockCallsManager).getRingingOrSimulatedRingingCall();
-        when(mMockCallsManager.getActiveCall()).thenReturn(ringingCall);
-
-        mBluetoothPhoneService.mCallsManagerListener.onCallStateChanged(ringingCall,
-                CallState.RINGING, CallState.ACTIVE);
-
-        verify(mMockBluetoothHeadset).phoneStateChanged(eq(1), eq(0), eq(CALL_STATE_IDLE),
-                eq(""), eq(128), nullable(String.class));
-    }
-
-    @MediumTest
-    @Test
-    public void testOnCallStateChangedGSMSwap() throws Exception {
-        Call heldCall = createHeldCall();
-        when(heldCall.getHandle()).thenReturn(Uri.parse("tel:555-0000"));
-        doReturn(2).when(mMockCallsManager).getNumHeldCalls();
-        mBluetoothPhoneService.mCallsManagerListener.onCallStateChanged(heldCall,
-                CallState.ACTIVE, CallState.ON_HOLD);
-
-        verify(mMockBluetoothHeadset, never()).phoneStateChanged(eq(0), eq(2), eq(CALL_STATE_HELD),
-                eq("5550000"), eq(PhoneNumberUtils.TOA_Unknown), nullable(String.class));
-    }
-
-    @MediumTest
-    @Test
-    public void testOnIsConferencedChanged() throws Exception {
-        // Start with two calls that are being merged into a CDMA conference call. The
-        // onIsConferencedChanged method will be called multiple times during the call. Make sure
-        // that the bluetooth phone state is updated properly.
-        Call parentCall = createActiveCall();
-        Call activeCall = mock(Call.class);
-        Call heldCall = createHeldCall();
-        when(activeCall.getParentCall()).thenReturn(parentCall);
-        when(heldCall.getParentCall()).thenReturn(parentCall);
-        ArrayList<Call> calls = new ArrayList<>();
-        calls.add(activeCall);
-        when(parentCall.getChildCalls()).thenReturn(calls);
-        when(parentCall.isConference()).thenReturn(true);
-        removeCallCapability(parentCall, Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN);
-        addCallCapability(parentCall, Connection.CAPABILITY_SWAP_CONFERENCE);
-        when(parentCall.wasConferencePreviouslyMerged()).thenReturn(false);
-
-        // Be sure that onIsConferencedChanged rejects spurious changes during set up of
-        // CDMA "conference"
-        mBluetoothPhoneService.mCallsManagerListener.onIsConferencedChanged(activeCall);
-        verify(mMockBluetoothHeadset, never()).phoneStateChanged(anyInt(), anyInt(), anyInt(),
-                anyString(), anyInt(), nullable(String.class));
-        mBluetoothPhoneService.mCallsManagerListener.onIsConferencedChanged(heldCall);
-        verify(mMockBluetoothHeadset, never()).phoneStateChanged(anyInt(), anyInt(), anyInt(),
-                anyString(), anyInt(), nullable(String.class));
-        mBluetoothPhoneService.mCallsManagerListener.onIsConferencedChanged(parentCall);
-        verify(mMockBluetoothHeadset, never()).phoneStateChanged(anyInt(), anyInt(), 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), nullable(String.class));
-    }
-
-    @MediumTest
-    @Test
-    public void testBluetoothAdapterReceiver() throws Exception {
-        Call ringingCall = createRingingCall();
-        when(ringingCall.getHandle()).thenReturn(Uri.parse("tel:5550000"));
-
-        Intent intent = new Intent();
-        intent.putExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_ON);
-        mBluetoothPhoneService.mBluetoothAdapterReceiver.onReceive(mContext, intent);
-
-        verify(mMockBluetoothHeadset).phoneStateChanged(eq(0), eq(0), eq(CALL_STATE_INCOMING),
-                eq("5550000"), eq(PhoneNumberUtils.TOA_Unknown), nullable(String.class));
-    }
-
-    private void addCallCapability(Call call, int capability) {
-        when(call.can(capability)).thenReturn(true);
-    }
-
-    private void removeCallCapability(Call call, int capability) {
-        when(call.can(capability)).thenReturn(false);
-    }
-
-    private Call createActiveCall() {
-        Call call = mock(Call.class);
-        when(mMockCallsManager.getActiveCall()).thenReturn(call);
-        return call;
-    }
-
-    private Call createRingingCall() {
-        Call call = mock(Call.class);
-        when(mMockCallsManager.getRingingOrSimulatedRingingCall()).thenReturn(call);
-        return call;
-    }
-
-    private Call createHeldCall() {
-        Call call = mock(Call.class);
-        when(mMockCallsManager.getHeldCall()).thenReturn(call);
-        return call;
-    }
-
-    private Call createOutgoingCall() {
-        Call call = mock(Call.class);
-        when(mMockCallsManager.getOutgoingCall()).thenReturn(call);
-        return call;
-    }
-
-    private Call createDisconnectedCall() {
-        Call call = mock(Call.class);
-        when(mMockCallsManager.getFirstCallWithState(CallState.DISCONNECTED)).thenReturn(call);
-        return call;
-    }
-
-    private Call createForegroundCall() {
-        Call call = mock(Call.class);
-        when(mMockCallsManager.getForegroundCall()).thenReturn(call);
-        return call;
-    }
-
-    private static ComponentName makeQuickConnectionServiceComponentName() {
-        return new ComponentName("com.android.server.telecom.tests",
-                "com.android.server.telecom.tests.MockConnectionService");
-    }
-
-    private static PhoneAccountHandle makeQuickAccountHandle(String id) {
-        return new PhoneAccountHandle(makeQuickConnectionServiceComponentName(), id,
-                Binder.getCallingUserHandle());
-    }
-
-    private PhoneAccount.Builder makeQuickAccountBuilder(String id, int idx) {
-        return new PhoneAccount.Builder(makeQuickAccountHandle(id), "label" + idx);
-    }
-
-    private PhoneAccount makeQuickAccount(String id, int idx) {
-        return makeQuickAccountBuilder(id, idx)
-                .setAddress(Uri.parse(TEST_ACCOUNT_ADDRESS + idx))
-                .setSubscriptionAddress(Uri.parse("tel:555-000" + idx))
-                .setCapabilities(idx)
-                .setIcon(Icon.createWithResource(
-                        "com.android.server.telecom.tests", R.drawable.stat_sys_phone_call))
-                .setShortDescription("desc" + idx)
-                .setIsEnabled(true)
-                .build();
-    }
-}
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
index 24476f0..91ec7f3 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
@@ -17,7 +17,10 @@
 package com.android.server.telecom.tests;
 
 import android.bluetooth.BluetoothDevice;
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.media.AudioManager;
 import android.media.IAudioService;
 import android.os.HandlerThread;
@@ -151,6 +154,49 @@
 
     @MediumTest
     @Test
+    public void testStreamRingMuteChange() {
+        CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
+                mContext,
+                mockCallsManager,
+                mockBluetoothRouteManager,
+                mockWiredHeadsetManager,
+                mockStatusBarNotifier,
+                mAudioServiceFactory,
+                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
+                mThreadHandler.getLooper());
+        stateMachine.setCallAudioManager(mockCallAudioManager);
+        CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_SPEAKER,
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER);
+        stateMachine.initialize(initState);
+
+        // Make sure we register a receiver for the STREAM_MUTE_CHANGED_ACTION so we can see if the
+        // ring stream unmutes.
+        ArgumentCaptor<BroadcastReceiver> brCaptor = ArgumentCaptor.forClass(
+                BroadcastReceiver.class);
+        ArgumentCaptor<IntentFilter> filterCaptor = ArgumentCaptor.forClass(IntentFilter.class);
+        verify(mContext, times(3)).registerReceiver(brCaptor.capture(), filterCaptor.capture());
+        boolean foundValid = false;
+        for (int ix = 0; ix < brCaptor.getAllValues().size(); ix++) {
+            BroadcastReceiver receiver = brCaptor.getAllValues().get(ix);
+            IntentFilter filter = filterCaptor.getAllValues().get(ix);
+            if (!filter.hasAction(AudioManager.STREAM_MUTE_CHANGED_ACTION)) {
+                continue;
+            }
+
+            // Fake out a call to the broadcast receiver and make sure we call into audio manager
+            // to trigger re-evaluation of ringing.
+            Intent intent = new Intent(AudioManager.STREAM_MUTE_CHANGED_ACTION);
+            intent.putExtra(AudioManager.EXTRA_STREAM_VOLUME_MUTED, false);
+            intent.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, AudioManager.STREAM_RING);
+            receiver.onReceive(mContext, intent);
+            verify(mockCallAudioManager).onRingerModeChange();
+            foundValid = true;
+        }
+        assertTrue(foundValid);
+    }
+
+    @MediumTest
+    @Test
     public void testSpeakerPersistence() {
         CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
                 mContext,
@@ -276,6 +322,10 @@
 
         waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
         verifyNewSystemCallAudioState(initState, expectedMidState);
+        // clear out the handler state before resetting mocks in order to avoid introducing a
+        // CallAudioState that has a null list of supported BT devices
+        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
         resetMocks();
 
         // Now, switch back to BT explicitly
@@ -455,6 +505,37 @@
 
     @SmallTest
     @Test
+    public void testFocusChangeFromQuiescentSpeaker() {
+        CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
+                mContext,
+                mockCallsManager,
+                mockBluetoothRouteManager,
+                mockWiredHeadsetManager,
+                mockStatusBarNotifier,
+                mAudioServiceFactory,
+                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
+                mThreadHandler.getLooper());
+        stateMachine.setCallAudioManager(mockCallAudioManager);
+
+        when(mockAudioManager.isSpeakerphoneOn()).thenReturn(false);
+
+        CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_SPEAKER,
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER);
+        stateMachine.initialize(initState);
+
+        // Switch to active, pretending that a call came in.
+        stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
+                CallAudioRouteStateMachine.ACTIVE_FOCUS);
+        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+
+        // Make sure that we've successfully switched to the active speaker route and that we've
+        // called setSpeakerOn
+        assertTrue(stateMachine.isInActiveState());
+        verify(mockAudioManager).setSpeakerphoneOn(true);
+    }
+
+    @SmallTest
+    @Test
     public void testFocusChangeWithAlreadyActiveBtDevice() {
         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 bf105e5..879ed0f 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java
@@ -815,7 +815,7 @@
                 "Speakerphone turned off externally during speaker", // name
                 CallAudioState.ROUTE_SPEAKER, // initialRoute
                 CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, // availableRoutes
-                NONE, // speakerInteraction
+                OFF, // speakerInteraction
                 ON, // bluetoothInteraction
                 CallAudioRouteStateMachine.SPEAKER_OFF, // action
                 CallAudioState.ROUTE_BLUETOOTH, // expectedRoute
diff --git a/tests/src/com/android/server/telecom/tests/CallDiagnosticServiceControllerTest.java b/tests/src/com/android/server/telecom/tests/CallDiagnosticServiceControllerTest.java
new file mode 100644
index 0000000..d420f1d
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/CallDiagnosticServiceControllerTest.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2021 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 junit.framework.Assert.assertEquals;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.Manifest;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.telecom.ParcelableCall;
+
+import com.android.internal.telecom.ICallDiagnosticService;
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallDiagnosticServiceController;
+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.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(JUnit4.class)
+public class CallDiagnosticServiceControllerTest {
+    private static final String TEST_CDS_PACKAGE = "com.test.stuff";
+    private static final String TEST_PACKAGE = "com.android.telecom.calldiagnosticservice";
+    private static final String TEST_CLASS =
+            "com.android.telecom.calldiagnosticservice.TestService";
+    private static final ComponentName TEST_COMPONENT = new ComponentName(TEST_PACKAGE, TEST_CLASS);
+    private static final List<ResolveInfo> RESOLVE_INFOS = new ArrayList<>();
+    private static final ResolveInfo TEST_RESOLVE_INFO = new ResolveInfo();
+    static {
+        TEST_RESOLVE_INFO.serviceInfo = new ServiceInfo();
+        TEST_RESOLVE_INFO.serviceInfo.packageName = TEST_PACKAGE;
+        TEST_RESOLVE_INFO.serviceInfo.name = TEST_CLASS;
+        TEST_RESOLVE_INFO.serviceInfo.permission = Manifest.permission.BIND_CALL_DIAGNOSTIC_SERVICE;
+        RESOLVE_INFOS.add(TEST_RESOLVE_INFO);
+    }
+    private static final String ID_1 = "1";
+    private static final String ID_2 = "2";
+
+    @Mock
+    private CallDiagnosticServiceController.ContextProxy mContextProxy;
+    @Mock
+    private Call mCall;
+    @Mock
+    private Call mCallTwo;
+    @Mock
+    private ICallDiagnosticService mICallDiagnosticService;
+    private TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() { };
+
+    private CallDiagnosticServiceController mCallDiagnosticService;
+    private ServiceConnection mServiceConnection;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        when(mCall.getId()).thenReturn(ID_1);
+        when(mCall.isSimCall()).thenReturn(true);
+        when(mCall.isExternalCall()).thenReturn(false);
+
+        when(mCallTwo.getId()).thenReturn(ID_2);
+        when(mCallTwo.isSimCall()).thenReturn(true);
+        when(mCallTwo.isExternalCall()).thenReturn(false);
+        mServiceConnection = null;
+
+        // Mock out the context and other junk that we depend on.
+        when(mContextProxy.queryIntentServicesAsUser(any(Intent.class), anyInt(), anyInt()))
+                .thenReturn(RESOLVE_INFOS);
+        when(mContextProxy.bindServiceAsUser(any(Intent.class), any(ServiceConnection.class),
+                anyInt(), any(UserHandle.class))).thenReturn(true);
+        when(mContextProxy.getCurrentUserHandle()).thenReturn(UserHandle.CURRENT);
+
+        mCallDiagnosticService = new CallDiagnosticServiceController(mContextProxy,
+                TEST_PACKAGE, mLock);
+    }
+
+    /**
+     * Verify no binding takes place for a non-sim call.
+     */
+    @Test
+    public void testNoBindOnNonSimCall() {
+        Call mockCall = Mockito.mock(Call.class);
+        when(mockCall.isSimCall()).thenReturn(false);
+
+        mCallDiagnosticService.onCallAdded(mockCall);
+
+        verify(mContextProxy, never()).bindServiceAsUser(any(Intent.class),
+                any(ServiceConnection.class), anyInt(), any(UserHandle.class));
+    }
+
+    /**
+     * Verify no binding takes place for a SIM external call.
+     */
+    @Test
+    public void testNoBindOnExternalCall() {
+        Call mockCall = Mockito.mock(Call.class);
+        when(mockCall.isSimCall()).thenReturn(true);
+        when(mockCall.isExternalCall()).thenReturn(true);
+
+        mCallDiagnosticService.onCallAdded(mockCall);
+
+        verify(mContextProxy, never()).bindServiceAsUser(any(Intent.class),
+                any(ServiceConnection.class), anyInt(), any(UserHandle.class));
+    }
+
+    /**
+     * Verify a valid SIM call causes binding to initiate.
+     */
+    @Test
+    public void testAddSimCallCausesBind() throws RemoteException {
+        mCallDiagnosticService.onCallAdded(mCall);
+
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        ArgumentCaptor<ServiceConnection> serviceConnectionCaptor = ArgumentCaptor.forClass(
+                ServiceConnection.class);
+        verify(mContextProxy).bindServiceAsUser(intentCaptor.capture(),
+                serviceConnectionCaptor.capture(), anyInt(), any(UserHandle.class));
+        assertEquals(TEST_PACKAGE, intentCaptor.getValue().getPackage());
+
+        // Now we'll pretend bind completed and we sent back the binder.
+        IBinder mockBinder = mock(IBinder.class);
+        when(mockBinder.queryLocalInterface(anyString())).thenReturn(mICallDiagnosticService);
+        serviceConnectionCaptor.getValue().onServiceConnected(TEST_COMPONENT, mockBinder);
+        mServiceConnection = serviceConnectionCaptor.getValue();
+
+        // Make sure it's sent
+        verify(mICallDiagnosticService).initializeDiagnosticCall(any(ParcelableCall.class));
+    }
+
+    /**
+     * Verify removing the active call causes it to be removed from the CallDiagnosticService and
+     * that an unbind takes place.
+     */
+    @Test
+    public void testRemoveSimCallCausesRemoveAndUnbind() throws RemoteException {
+        testAddSimCallCausesBind();
+        mCallDiagnosticService.onCallRemoved(mCall);
+
+        verify(mICallDiagnosticService).removeDiagnosticCall(eq(ID_1));
+        verify(mContextProxy).unbindService(eq(mServiceConnection));
+    }
+
+    /**
+     * Try to add and remove two and verify bind/unbind.
+     */
+    @Test
+    public void testAddTwo() throws RemoteException {
+        testAddSimCallCausesBind();
+        mCallDiagnosticService.onCallAdded(mCallTwo);
+        verify(mICallDiagnosticService, times(2)).initializeDiagnosticCall(
+                any(ParcelableCall.class));
+
+        mCallDiagnosticService.onCallRemoved(mCall);
+        // Not yet!
+        verify(mContextProxy, never()).unbindService(eq(mServiceConnection));
+
+        mCallDiagnosticService.onCallRemoved(mCallTwo);
+
+        verify(mICallDiagnosticService).removeDiagnosticCall(eq(ID_1));
+        verify(mICallDiagnosticService).removeDiagnosticCall(eq(ID_2));
+        verify(mContextProxy).unbindService(eq(mServiceConnection));
+    }
+
+    /**
+     * Verifies we can override the call diagnostic service package to a test package (used by CTS
+     * tests).
+     */
+    @Test
+    public void testTestOverride() {
+        mCallDiagnosticService.setTestCallDiagnosticService(TEST_CDS_PACKAGE);
+        mCallDiagnosticService.onCallAdded(mCall);
+
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mContextProxy).bindServiceAsUser(intentCaptor.capture(),
+                any(ServiceConnection.class), anyInt(), any(UserHandle.class));
+        assertEquals(TEST_CDS_PACKAGE, intentCaptor.getValue().getPackage());
+    }
+}
diff --git a/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java b/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java
index 953c711..b9f5667 100644
--- a/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java
@@ -18,6 +18,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
@@ -40,7 +41,9 @@
 import android.location.Country;
 import android.location.CountryDetector;
 import android.location.CountryListener;
+import android.location.Location;
 import android.net.Uri;
+import android.os.Bundle;
 import android.os.Looper;
 import android.os.PersistableBundle;
 import android.os.SystemClock;
@@ -52,6 +55,7 @@
 import android.telecom.DisconnectCause;
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
+import android.telecom.TelecomManager;
 import android.telecom.VideoProfile;
 import android.telephony.CarrierConfigManager;
 import android.telephony.PhoneNumberUtils;
@@ -60,6 +64,7 @@
 
 import androidx.test.filters.FlakyTest;
 
+import com.android.server.telecom.Analytics;
 import com.android.server.telecom.Call;
 import com.android.server.telecom.CallLogManager;
 import com.android.server.telecom.CallState;
@@ -89,6 +94,7 @@
     private PhoneAccountHandle mOtherUserAccountHandle;
     private PhoneAccountHandle mManagedProfileAccountHandle;
     private PhoneAccountHandle mSelfManagedAccountHandle;
+    private Analytics.CallInfo mCallInfo;
 
     private static final Uri TEL_PHONEHANDLE = Uri.parse("tel:5555551234");
 
@@ -148,6 +154,7 @@
                 TEST_SELF_MGD_PHONE_ACCOUNT_ID,
                 UserHandle.of(CURRENT_USER_ID)
         );
+        mCallInfo = new Analytics.CallInfo();
 
         // Since we can't mock ContentResolver directly, use a ContentProvider
         when(mContext.getContentResolver()).thenReturn(ContentResolver.wrap(mContentProvider));
@@ -169,7 +176,7 @@
         when(userManager.isUserUnlocked(any(UserHandle.class))).thenReturn(true);
         when(userManager.hasUserRestriction(any(String.class), any(UserHandle.class)))
                 .thenReturn(false);
-        when(userManager.getUsers(any(Boolean.class)))
+        when(userManager.getAliveUsers())
                 .thenReturn(Arrays.asList(userInfo, otherUserInfo, managedProfileUserInfo));
         when(userManager.getUserInfo(eq(CURRENT_USER_ID))).thenReturn(userInfo);
         when(userManager.getUserInfo(eq(OTHER_USER_ID))).thenReturn(otherUserInfo);
@@ -816,6 +823,54 @@
 
     @SmallTest
     @Test
+    public void testCallComposerElements() {
+        Call fakeCall = makeFakeCall(
+                DisconnectCause.LOCAL, // disconnectCauseCode
+                false, // isConference
+                true, // isIncoming
+                1L, // creationTimeMillis
+                1000L, // ageMillis
+                TEL_PHONEHANDLE, // callHandle
+                mDefaultAccountHandle, // phoneAccountHandle
+                NO_VIDEO_STATE, // callVideoState
+                POST_DIAL_STRING, // postDialDigits
+                VIA_NUMBER_STRING, // viaNumber
+                UserHandle.of(CURRENT_USER_ID)
+        );
+        String subject = "segmentation fault";
+        // =)
+        double lat = 40.649723;
+        double lon = -80.082090;
+        Location location = new Location("");
+        location.setLatitude(lat);
+        location.setLongitude(lon);
+
+        Uri fakeProviderUri = Uri.parse("content://nothing_to_see_here/12345");
+
+        Bundle extras = new Bundle();
+        extras.putInt(TelecomManager.EXTRA_PRIORITY, TelecomManager.PRIORITY_URGENT);
+        extras.putString(TelecomManager.EXTRA_CALL_SUBJECT, subject);
+        extras.putParcelable(TelecomManager.EXTRA_LOCATION, location);
+        extras.putParcelable(TelecomManager.EXTRA_PICTURE_URI, fakeProviderUri);
+        when(fakeCall.getIntentExtras()).thenReturn(extras);
+
+        mCallLogManager.onCallStateChanged(fakeCall, CallState.ACTIVE,
+                CallState.DISCONNECTED);
+        ContentValues locationValues = verifyLocationInsertionWithCapture(CURRENT_USER_ID);
+        assertEquals(lat, locationValues.getAsDouble(CallLog.Locations.LATITUDE), 0);
+        assertEquals(lon, locationValues.getAsDouble(CallLog.Locations.LONGITUDE), 0);
+
+        ContentValues callLogValues = verifyInsertionWithCapture(CURRENT_USER_ID);
+        assertEquals(subject, callLogValues.getAsString(Calls.SUBJECT));
+        assertEquals(fakeProviderUri.toString(),
+                callLogValues.getAsString(Calls.COMPOSER_PHOTO_URI));
+        assertEquals(TelecomManager.PRIORITY_URGENT,
+                (int) callLogValues.getAsInteger(Calls.PRIORITY));
+        assertNotNull(callLogValues.getAsString(Calls.LOCATION));
+    }
+
+    @SmallTest
+    @Test
     public void testDoNotLogConferenceWithNoChildren() {
         Call fakeCall = makeFakeCall(
                 DisconnectCause.LOCAL, // disconnectCauseCode
@@ -973,6 +1028,14 @@
         return captor.getValue();
     }
 
+    private ContentValues verifyLocationInsertionWithCapture(int userId) {
+        Uri uri = ContentProvider.maybeAddUserId(CallLog.Locations.CONTENT_URI, userId);
+        ArgumentCaptor<ContentValues> captor = ArgumentCaptor.forClass(ContentValues.class);
+        verify(mContentProvider, timeout(TEST_TIMEOUT_MILLIS).times(1)).insert(
+                eq(uri), captor.capture());
+        return captor.getValue();
+    }
+
     private Call makeFakeCall(int disconnectCauseCode, boolean isConference, boolean isIncoming,
             long creationTimeMillis, long ageMillis, Uri callHandle,
             PhoneAccountHandle phoneAccountHandle, int callVideoState,
@@ -1006,6 +1069,7 @@
         when(fakeCall.getParentCall()).thenReturn(null);
         when(fakeCall.hadChildren()).thenReturn(true);
         when(fakeCall.hasProperty(eq(Connection.PROPERTY_REMOTELY_HOSTED))).thenReturn(false);
+        when(fakeCall.getAnalytics()).thenReturn(mCallInfo);
         return fakeCall;
     }
 
diff --git a/tests/src/com/android/server/telecom/tests/CallRedirectionProcessorTest.java b/tests/src/com/android/server/telecom/tests/CallRedirectionProcessorTest.java
index ff16880..0a896a8 100644
--- a/tests/src/com/android/server/telecom/tests/CallRedirectionProcessorTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallRedirectionProcessorTest.java
@@ -65,8 +65,6 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import java.util.ArrayList;
-
 @RunWith(JUnit4.class)
 public class CallRedirectionProcessorTest extends TelecomTestCase {
     @Mock private Context mContext;
@@ -151,7 +149,7 @@
     }
 
     private void setIsInCarMode(boolean isInCarMode) {
-        when(mSystemStateHelper.isCarMode()).thenReturn(isInCarMode);
+        when(mSystemStateHelper.isCarModeOrProjectionActive()).thenReturn(isInCarMode);
     }
 
     private void enableUserDefinedCallRedirectionService() {
diff --git a/tests/src/com/android/server/telecom/tests/CallScreeningServiceFilterTest.java b/tests/src/com/android/server/telecom/tests/CallScreeningServiceFilterTest.java
index c7b3a7e..68caf67 100644
--- a/tests/src/com/android/server/telecom/tests/CallScreeningServiceFilterTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallScreeningServiceFilterTest.java
@@ -93,6 +93,16 @@
             .setShouldShowNotification(true)
             .build();
 
+    private static final CallFilteringResult PASS_RESULT_WITH_NAME =
+            new CallFilteringResult.Builder()
+                    .setShouldAllowCall(true)
+                    .setShouldReject(false)
+                    .setShouldAddToCallLog(true)
+                    .setShouldShowNotification(true)
+                    .setCallScreeningAppName(APP_NAME)
+                    .setCallScreeningComponentName(COMPONENT_NAME.flattenToString())
+                    .build();
+
     @Override
     @Before
     public void setUp() throws Exception {
@@ -235,8 +245,14 @@
 
         serviceConnection.onServiceConnected(COMPONENT_NAME, mBinder);
         ICallScreeningAdapter csAdapter = getCallScreeningAdapter();
-        csAdapter.allowCall(CALL_ID);
-        assertEquals(PASS_RESULT,
+        CallScreeningService.CallResponse allowCallResponse =
+                new CallScreeningService.CallResponse.Builder()
+                .setDisallowCall(false)
+                .setRejectCall(false)
+                .setSilenceCall(false)
+                .build();
+        csAdapter.onScreeningResponse(CALL_ID, COMPONENT_NAME, allowCallResponse.toParcelable());
+        assertEquals(PASS_RESULT_WITH_NAME,
                 resultFuture.toCompletableFuture().get(
                         CallScreeningServiceFilter.CALL_SCREENING_FILTER_TIMEOUT,
                         TimeUnit.MILLISECONDS));
@@ -264,12 +280,16 @@
         ServiceConnection serviceConnection = verifyBindingIntent();
 
         serviceConnection.onServiceConnected(COMPONENT_NAME, mBinder);
+        CallScreeningService.CallResponse disallowCallResponse =
+                new CallScreeningService.CallResponse.Builder()
+                        .setDisallowCall(true)
+                        .setRejectCall(true)
+                        .setSkipCallLog(false)
+                        .setSkipNotification(false)
+                        .build();
         ICallScreeningAdapter csAdapter = getCallScreeningAdapter();
-        csAdapter.disallowCall(CALL_ID,
-                true, // shouldReject
-                true, //shouldAddToCallLog
-                true, // shouldShowNotification
-                COMPONENT_NAME);
+        csAdapter.onScreeningResponse(CALL_ID, COMPONENT_NAME, disallowCallResponse.toParcelable());
+
         assertEquals(expectedResult,
                 resultFuture.toCompletableFuture().get(
                         CallScreeningServiceFilter.CALL_SCREENING_FILTER_TIMEOUT,
@@ -286,6 +306,8 @@
                 .setShouldSilence(true)
                 .setShouldAddToCallLog(true)
                 .setShouldShowNotification(true)
+                .setCallScreeningAppName(APP_NAME)
+                .setCallScreeningComponentName(COMPONENT_NAME.flattenToString())
                 .build();
         CallScreeningServiceFilter filter = new CallScreeningServiceFilter(mCall, PKG_NAME,
                 CallScreeningServiceFilter.PACKAGE_TYPE_CARRIER, mContext, mCallsManager,
@@ -296,7 +318,13 @@
 
         serviceConnection.onServiceConnected(COMPONENT_NAME, mBinder);
         ICallScreeningAdapter csAdapter = getCallScreeningAdapter();
-        csAdapter.silenceCall(CALL_ID);
+        CallScreeningService.CallResponse silenceCallResponse =
+                new CallScreeningService.CallResponse.Builder()
+                        .setDisallowCall(false)
+                        .setRejectCall(false)
+                        .setSilenceCall(true)
+                        .build();
+        csAdapter.onScreeningResponse(CALL_ID, COMPONENT_NAME, silenceCallResponse.toParcelable());
         assertEquals(expectedResult,
                 resultFuture.toCompletableFuture().get(
                         CallScreeningServiceFilter.CALL_SCREENING_FILTER_TIMEOUT,
@@ -314,6 +342,7 @@
                 .setShouldSilence(false)
                 .setShouldScreenViaAudio(true)
                 .setCallScreeningAppName(APP_NAME)
+                .setCallScreeningComponentName(COMPONENT_NAME.flattenToString())
                 .build();
         CallScreeningServiceFilter filter = new CallScreeningServiceFilter(mCall, PKG_NAME,
                 CallScreeningServiceFilter.PACKAGE_TYPE_DEFAULT_DIALER, mContext, mCallsManager,
@@ -323,8 +352,17 @@
         ServiceConnection serviceConnection = verifyBindingIntent();
 
         serviceConnection.onServiceConnected(COMPONENT_NAME, mBinder);
+
+        CallScreeningService.CallResponse additionalScreeningResponse =
+                new CallScreeningService.CallResponse.Builder()
+                        .setDisallowCall(false)
+                        .setRejectCall(false)
+                        .setSilenceCall(false)
+                        .setShouldScreenCallViaAudioProcessing(true)
+                        .build();
         ICallScreeningAdapter csAdapter = getCallScreeningAdapter();
-        csAdapter.screenCallFurther(CALL_ID);
+        csAdapter.onScreeningResponse(CALL_ID, COMPONENT_NAME,
+                additionalScreeningResponse.toParcelable());
         assertEquals(expectedResult,
                 resultFuture.toCompletableFuture().get(
                         CallScreeningServiceFilter.CALL_SCREENING_FILTER_TIMEOUT,
diff --git a/tests/src/com/android/server/telecom/tests/CallTest.java b/tests/src/com/android/server/telecom/tests/CallTest.java
index 541d278..d326a29 100644
--- a/tests/src/com/android/server/telecom/tests/CallTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallTest.java
@@ -19,13 +19,17 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
 
 import android.content.ComponentName;
 import android.net.Uri;
@@ -34,6 +38,7 @@
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
 import android.widget.Toast;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -44,6 +49,8 @@
 import com.android.server.telecom.CallsManager;
 import com.android.server.telecom.ClockProxy;
 import com.android.server.telecom.ConnectionServiceWrapper;
+import com.android.server.telecom.InCallController;
+import com.android.server.telecom.InCallController.InCallServiceInfo;
 import com.android.server.telecom.PhoneAccountRegistrar;
 import com.android.server.telecom.PhoneNumberUtilsAdapter;
 import com.android.server.telecom.TelecomSystem;
@@ -54,17 +61,23 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 
 @RunWith(AndroidJUnit4.class)
 public class CallTest extends TelecomTestCase {
     private static final Uri TEST_ADDRESS = Uri.parse("tel:555-1212");
+    private static final ComponentName COMPONENT_NAME_1 = ComponentName
+            .unflattenFromString("com.foo/.Blah");
+    private static final ComponentName COMPONENT_NAME_2 = ComponentName
+            .unflattenFromString("com.bar/.Blah");
     private static final PhoneAccountHandle SIM_1_HANDLE = new PhoneAccountHandle(
-            ComponentName.unflattenFromString("com.foo/.Blah"), "Sim1");
+            COMPONENT_NAME_1, "Sim1");
     private static final PhoneAccount SIM_1_ACCOUNT = new PhoneAccount.Builder(SIM_1_HANDLE, "Sim1")
             .setCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION
                     | PhoneAccount.CAPABILITY_CALL_PROVIDER)
             .setIsEnabled(true)
             .build();
+    private static final long TIMEOUT_MILLIS = 1000;
 
     @Mock private CallsManager mMockCallsManager;
     @Mock private CallerInfoLookupHelper mMockCallerInfoLookupHelper;
diff --git a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
index 4dffe59..da72933 100644
--- a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
@@ -21,6 +21,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -36,19 +37,27 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.ComponentName;
+import android.content.ContentResolver;
 import android.content.Context;
+import android.content.Intent;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
 import android.os.Process;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.telecom.CallerInfo;
 import android.telecom.Connection;
+import android.telecom.DisconnectCause;
+import android.telecom.Log;
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
@@ -56,6 +65,7 @@
 import android.telephony.TelephonyManager;
 import android.test.suitebuilder.annotation.MediumTest;
 import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Pair;
 import android.widget.Toast;
 
 import com.android.server.telecom.AsyncRingtonePlayer;
@@ -63,6 +73,7 @@
 import com.android.server.telecom.CallAudioManager;
 import com.android.server.telecom.CallAudioModeStateMachine;
 import com.android.server.telecom.CallAudioRouteStateMachine;
+import com.android.server.telecom.CallDiagnosticServiceController;
 import com.android.server.telecom.CallState;
 import com.android.server.telecom.CallerInfoLookupHelper;
 import com.android.server.telecom.CallsManager;
@@ -70,6 +81,7 @@
 import com.android.server.telecom.ClockProxy;
 import com.android.server.telecom.ConnectionServiceFocusManager;
 import com.android.server.telecom.ConnectionServiceFocusManager.ConnectionServiceFocusManagerFactory;
+import com.android.server.telecom.ConnectionServiceWrapper;
 import com.android.server.telecom.DefaultDialerCache;
 import com.android.server.telecom.EmergencyCallHelper;
 import com.android.server.telecom.HeadsetMediaButton;
@@ -92,7 +104,6 @@
 import com.android.server.telecom.bluetooth.BluetoothRouteManager;
 import com.android.server.telecom.bluetooth.BluetoothStateReceiver;
 import com.android.server.telecom.callfiltering.CallFilteringResult;
-import com.android.server.telecom.callfiltering.IncomingCallFilter;
 import com.android.server.telecom.ui.AudioProcessingNotification;
 import com.android.server.telecom.ui.DisconnectedCallNotifier;
 import com.android.server.telecom.ui.ToastFactory;
@@ -107,8 +118,6 @@
 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;
@@ -116,6 +125,8 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 
@@ -193,10 +204,9 @@
     @Mock private CallAudioRouteStateMachine.Factory mCallAudioRouteStateMachineFactory;
     @Mock private CallAudioModeStateMachine mCallAudioModeStateMachine;
     @Mock private CallAudioModeStateMachine.Factory mCallAudioModeStateMachineFactory;
+    @Mock private CallDiagnosticServiceController mCallDiagnosticServiceController;
     @Mock private BluetoothStateReceiver mBluetoothStateReceiver;
     @Mock private RoleManagerAdapter mRoleManagerAdapter;
-    @Mock private IncomingCallFilter.Factory mIncomingCallFilterFactory;
-    @Mock private IncomingCallFilter mIncomingCallFilter;
     @Mock private ToastFactory mToastFactory;
     @Mock private Toast mToast;
 
@@ -219,14 +229,14 @@
                 anyInt())).thenReturn(mCallAudioRouteStateMachine);
         when(mCallAudioModeStateMachineFactory.create(any(), any()))
                 .thenReturn(mCallAudioModeStateMachine);
-        when(mIncomingCallFilterFactory.create(any(), any(), any(), any(), any(), any()))
-                .thenReturn(mIncomingCallFilter);
         when(mClockProxy.currentTimeMillis()).thenReturn(System.currentTimeMillis());
         when(mClockProxy.elapsedRealtime()).thenReturn(SystemClock.elapsedRealtime());
         when(mConnSvrFocusManagerFactory.create(any())).thenReturn(mConnectionSvrFocusMgr);
         doNothing().when(mRoleManagerAdapter).setCurrentUserHandle(any());
         when(mDisconnectedCallNotifierFactory.create(any(Context.class),any(CallsManager.class)))
                 .thenReturn(mDisconnectedCallNotifier);
+        when(mTimeoutsAdapter.getCallDiagnosticServiceTimeoutMillis(any(ContentResolver.class)))
+                .thenReturn(2000L);
         mCallsManager = new CallsManager(
                 mComponentContextFixture.getTestDouble().getApplicationContext(),
                 mLock,
@@ -254,8 +264,8 @@
                 mCallAudioRouteStateMachineFactory,
                 mCallAudioModeStateMachineFactory,
                 mInCallControllerFactory,
+                mCallDiagnosticServiceController,
                 mRoleManagerAdapter,
-                mIncomingCallFilterFactory,
                 mToastFactory);
 
         when(mPhoneAccountRegistrar.getPhoneAccount(
@@ -1019,7 +1029,7 @@
     @SmallTest
     @Test
     public void testHangupActiveCallWhenHeadsetMediaButtonLongPressDuringTwoCalls() {
-        // GIVEN an  ongoing call
+        // GIVEN an ongoing call
         Call ongoingCall = addSpyCall();
         doReturn(CallState.ACTIVE).when(ongoingCall).getState();
 
@@ -1105,6 +1115,46 @@
         assertFalse(mCallsManager.isInEmergencyCall());
     }
 
+
+    @SmallTest
+    @Test
+    public void testBlockNonEmergencyCallDuringEmergencyCall() throws Exception {
+        // Setup a call which the network identified as an emergency call.
+        Call ongoingCall = addSpyCall();
+        ongoingCall.setConnectionProperties(Connection.PROPERTY_NETWORK_IDENTIFIED_EMERGENCY_CALL);
+        assertTrue(mCallsManager.isInEmergencyCall());
+
+        Call newCall = addSpyCall(CallState.NEW);
+        ConnectionServiceWrapper service = mock(ConnectionServiceWrapper.class);
+        doReturn(SIM_2_HANDLE.getComponentName()).when(service).getComponentName();
+
+        // Ensure contact info lookup succeeds
+        doAnswer(invocation -> {
+            Uri handle = invocation.getArgument(0);
+            CallerInfo info = new CallerInfo();
+            CompletableFuture<Pair<Uri, CallerInfo>> callerInfoFuture = new CompletableFuture<>();
+            callerInfoFuture.complete(new Pair<>(handle, info));
+            return callerInfoFuture;
+        }).when(mCallerInfoLookupHelper).startLookup(any(Uri.class));
+
+        // Ensure we have candidate phone account handle info.
+        when(mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(any(), any())).thenReturn(
+                SIM_1_HANDLE);
+        when(mPhoneAccountRegistrar.getCallCapablePhoneAccounts(any(), anyBoolean(),
+                any(), anyInt(), anyInt())).thenReturn(
+                new ArrayList<>(Arrays.asList(SIM_1_HANDLE, SIM_2_HANDLE)));
+        mCallsManager.addConnectionServiceRepositoryCache(SIM_2_HANDLE.getComponentName(),
+                SIM_2_HANDLE.getUserHandle(), service);
+
+        CompletableFuture<Call> callFuture = mCallsManager.startOutgoingCall(
+                newCall.getHandle(), newCall.getTargetPhoneAccount(), new Bundle(),
+                UserHandle.CURRENT, new Intent(), "com.test.stuff");
+
+        verify(service, timeout(TEST_TIMEOUT)).createConnectionFailed(any());
+        Call result = callFuture.get(TEST_TIMEOUT, TimeUnit.MILLISECONDS);
+        assertNull(result);
+    }
+
     @SmallTest
     @Test
     public void testHasEmergencyCallIncomingCallPermitted() {
@@ -1203,6 +1253,21 @@
         verify(ringingCall).reject(anyBoolean(), any(), any());
     }
 
+    @SmallTest
+    @Test
+    public void testMakeRoomForOutgoingCallConnecting() {
+        Call ongoingCall = addSpyCall(SIM_2_HANDLE, CallState.CONNECTING);
+
+        Call newCall = createCall(SIM_1_HANDLE, CallState.NEW);
+        when(mComponentContextFixture.getTelephonyManager().isEmergencyNumber(any()))
+                .thenReturn(false);
+        newCall.setHandle(Uri.fromParts("tel", "5551213", null),
+                TelecomManager.PRESENTATION_ALLOWED);
+
+        assertTrue(mCallsManager.makeRoomForOutgoingCall(newCall));
+        verify(ongoingCall).disconnect(anyLong(), anyString());
+    }
+
     /**
      * Verifies that changes to a {@link PhoneAccount}'s
      * {@link PhoneAccount#CAPABILITY_VIDEO_CALLING} capability will be reflected on a call.
@@ -1332,6 +1397,8 @@
     @Test
     public void testHandleSilenceVsBackgroundScreeningOrdering() throws Exception {
         Call screenedCall = mock(Call.class);
+        Bundle extra = new Bundle();
+        when(screenedCall.getIntentExtras()).thenReturn(extra);
         String appName = "blah";
         CallFilteringResult result = new CallFilteringResult.Builder()
                 .setShouldAllowCall(true)
@@ -1342,7 +1409,7 @@
                 .setShouldShowNotification(true)
                 .setCallScreeningAppName(appName)
                 .build();
-        mCallsManager.onCallFilteringComplete(screenedCall, result);
+        mCallsManager.onCallFilteringComplete(screenedCall, result, false);
 
         verify(mConnectionSvrFocusMgr).requestFocus(eq(screenedCall),
                 nullable(ConnectionServiceFocusManager.RequestFocusCallback.class));
@@ -1381,6 +1448,80 @@
     }
 
     /**
+     * This test verifies a race condition seen with the new outgoing call broadcast.
+     * The scenario occurs when an incoming call is handled by an app which receives the
+     * NewOutgoingCallBroadcast.  That app cancels the call by modifying the new outgoing call
+     * broadcast.  Meanwhile, it places that same call again, expecting that Telecom will reuse the
+     * same same.  HOWEVER, if the system delays passing of the new outgoing call broadcast back to
+     * Telecom, the app will have placed a new outgoing call BEFORE telecom is aware that the call
+     * was cancelled.
+     * The consequence of this is that in CallsManager#startOutgoingCall, when we first get the
+     * call to reuse, it will come back empty.  Meanwhile, by the time we get into the various
+     * completable futures, the call WILL be in the list of calls which can be reused.  Since the
+     * reusable call was not found earlier on, we end up aborting the new outgoing call.
+     * @throws Exception
+     */
+    @SmallTest
+    @Test
+    public void testReuseCallConcurrency() throws Exception {
+        // Ensure contact info lookup succeeds
+        doAnswer(invocation -> {
+            Uri handle = invocation.getArgument(0);
+            CallerInfo info = new CallerInfo();
+            CompletableFuture<Pair<Uri, CallerInfo>> callerInfoFuture = new CompletableFuture<>();
+            callerInfoFuture.complete(new Pair<>(handle, info));
+            return callerInfoFuture;
+        }).when(mCallerInfoLookupHelper).startLookup(any(Uri.class));
+
+        // Ensure we have candidate phone account handle info.
+        when(mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(any(), any())).thenReturn(
+                SIM_1_HANDLE);
+        when(mPhoneAccountRegistrar.getCallCapablePhoneAccounts(any(), anyBoolean(),
+                any(), anyInt(), anyInt())).thenReturn(
+                new ArrayList<>(Arrays.asList(SIM_1_HANDLE, SIM_2_HANDLE)));
+
+        // Let's add an existing call which is in connecting state; this emulates the case where
+        // we have an outgoing call which we have not yet disconnected as a result of the new
+        // outgoing call broadcast cancelling the call.
+        Call outgoingCall = addSpyCall(CallState.CONNECTING);
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        // Get the handler for the main looper, which is the same one the CallsManager will use.
+        // We'll post a little something to block up the handler for now.  This prevents
+        // startOutgoingCall from process it's completablefutures.
+        Handler handler = new Handler(Looper.getMainLooper());
+        handler.post(() -> {
+            try {
+                latch.await();
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+        });
+
+        // Now while the main handler is blocked up we'll start another outgoing call.
+        CompletableFuture<Call> callFuture = mCallsManager.startOutgoingCall(
+                outgoingCall.getHandle(), outgoingCall.getTargetPhoneAccount(), new Bundle(),
+                UserHandle.CURRENT, new Intent(), "com.test.stuff");
+
+        // And we'll add the initial outgoing call to the list of pending disconnects; this
+        // emulates a scenario where the pending disconnect call came in AFTER this call began.
+        mCallsManager.addToPendingCallsToDisconnect(outgoingCall);
+
+        // And we'll unblock the handler; this will let all the startOutgoingCall futures to happen.
+        latch.countDown();
+
+        // Wait for the future to become the present.
+        callFuture.join();
+
+        // We should have gotten a call out of this; if we did not then it means the call was
+        // aborted.
+        assertNotNull(callFuture.get());
+
+        // And the original call should be disconnected now.
+        assertEquals(CallState.DISCONNECTED, outgoingCall.getState());
+    }
+
+    /**
      * Ensures that if we have two calls hosted by the same connection manager, but with
      * different target phone accounts, we can swap between them.
      * @throws Exception
@@ -1431,6 +1572,59 @@
                 eq(CallState.ACTIVE));
     }
 
+    /**
+     * Verifies where a call diagnostic service is NOT in use that we don't try to relay to the
+     * CallDiagnosticService and that we get a synchronous disconnect.
+     * @throws Exception
+     */
+    @MediumTest
+    @Test
+    public void testDisconnectCallSynchronous() throws Exception {
+        Call callSpy = addSpyCall();
+        callSpy.setIsSimCall(true);
+        when(mCallDiagnosticServiceController.isConnected()).thenReturn(false);
+        mCallsManager.markCallAsDisconnected(callSpy, new DisconnectCause(DisconnectCause.ERROR));
+
+        verify(mCallDiagnosticServiceController, never()).onCallDisconnected(any(Call.class),
+                any(DisconnectCause.class));
+        verify(callSpy).setDisconnectCause(any(DisconnectCause.class));
+    }
+
+    @MediumTest
+    @Test
+    public void testDisconnectCallAsynchronous() throws Exception {
+        Call callSpy = addSpyCall();
+        callSpy.setIsSimCall(true);
+        when(mCallDiagnosticServiceController.isConnected()).thenReturn(true);
+        when(mCallDiagnosticServiceController.onCallDisconnected(any(Call.class),
+                any(DisconnectCause.class))).thenReturn(true);
+        mCallsManager.markCallAsDisconnected(callSpy, new DisconnectCause(DisconnectCause.ERROR));
+
+        verify(mCallDiagnosticServiceController).onCallDisconnected(any(Call.class),
+                any(DisconnectCause.class));
+        verify(callSpy, never()).setDisconnectCause(any(DisconnectCause.class));
+    }
+    
+    /**
+     * Verifies that if call state goes from DIALING to DISCONNECTED, and a call diagnostic service
+     * IS in use, it would call onCallDisconnected of the CallDiagnosticService
+     * @throws Exception
+     */
+    @MediumTest
+    @Test
+    public void testDisconnectDialingCall() throws Exception {
+        Call callSpy = addSpyCall(CallState.DIALING);
+        callSpy.setIsSimCall(true);
+        when(mCallDiagnosticServiceController.isConnected()).thenReturn(true);
+        when(mCallDiagnosticServiceController.onCallDisconnected(any(Call.class),
+                any(DisconnectCause.class))).thenReturn(true);
+        mCallsManager.markCallAsDisconnected(callSpy, new DisconnectCause(DisconnectCause.ERROR));
+
+        verify(mCallDiagnosticServiceController).onCallDisconnected(any(Call.class),
+                any(DisconnectCause.class));
+        verify(callSpy, never()).setDisconnectCause(any(DisconnectCause.class));
+    }
+
     private Call addSpyCall() {
         return addSpyCall(SIM_2_HANDLE, CallState.ACTIVE);
     }
@@ -1460,7 +1654,6 @@
         // Mocks some methods to not call the real method.
         doNothing().when(callSpy).unhold();
         doNothing().when(callSpy).hold();
-        doNothing().when(callSpy).disconnect();
         doNothing().when(callSpy).answer(Matchers.anyInt());
         doNothing().when(callSpy).setStartWithSpeakerphoneOn(Matchers.anyBoolean());
 
diff --git a/tests/src/com/android/server/telecom/tests/CarModeTrackerTest.java b/tests/src/com/android/server/telecom/tests/CarModeTrackerTest.java
index 4ef4596..4ad46ae 100644
--- a/tests/src/com/android/server/telecom/tests/CarModeTrackerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CarModeTrackerTest.java
@@ -17,12 +17,10 @@
 package com.android.server.telecom.tests;
 
 import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 import static junit.framework.TestCase.assertNull;
 
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.when;
-
 import android.app.UiModeManager;
 
 import com.android.server.telecom.CarModeTracker;
@@ -32,7 +30,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
-import org.mockito.Mock;
 
 @RunWith(JUnit4.class)
 public class CarModeTrackerTest extends TelecomTestCase {
@@ -104,6 +101,17 @@
     }
 
     /**
+     * Ensure that we don't keep a package around after it's been removed from the device
+     */
+    @Test
+    public void testForceExitCarMode() {
+        testEnterCarModeBasic();
+        mCarModeTracker.forceRemove(CAR_MODE_APP1_PACKAGE_NAME);
+        assertFalse(mCarModeTracker.isInCarMode());
+        assertNull(mCarModeTracker.getCurrentCarModePackage());
+    }
+
+    /**
      * Verifies only the first app at the default priority gets tracked.
      */
     @Test
@@ -214,4 +222,109 @@
                 CAR_MODE_APP3_PACKAGE_NAME);
         assertNull(mCarModeTracker.getCurrentCarModePackage());
     }
+
+    /**
+     * Verifies that setting automotive projection by itself works.
+     */
+    @Test
+    public void testSetAutomotiveProjectionBasic() {
+        mCarModeTracker.handleSetAutomotiveProjection(CAR_MODE_APP1_PACKAGE_NAME);
+        assertEquals(CAR_MODE_APP1_PACKAGE_NAME, mCarModeTracker.getCurrentCarModePackage());
+        // We should be tracking our car mode app.
+        assertEquals(1, mCarModeTracker.getCarModeApps().size());
+        assertTrue(mCarModeTracker.isInCarMode());
+    }
+
+    /**
+     * Verifies that if we set automotive projection more than once with the same package, nothing
+     * changes.
+     */
+    @Test
+    public void testSetAutomotiveProjectionMultipleTimes() {
+        mCarModeTracker.handleSetAutomotiveProjection(CAR_MODE_APP1_PACKAGE_NAME);
+        mCarModeTracker.handleSetAutomotiveProjection(CAR_MODE_APP1_PACKAGE_NAME);
+        // Should still only have one app.
+        assertEquals(1, mCarModeTracker.getCarModeApps().size());
+        assertTrue(mCarModeTracker.isInCarMode());
+        // It should be the same one.
+        assertEquals(CAR_MODE_APP1_PACKAGE_NAME, mCarModeTracker.getCurrentCarModePackage());
+    }
+
+    /**
+     * Verifies that if we set automotive projection more than once, the new package overrides.
+     */
+    @Test
+    public void testSetAutomotiveProjectionMultipleTimesDifferentPackages() {
+        mCarModeTracker.handleSetAutomotiveProjection(CAR_MODE_APP1_PACKAGE_NAME);
+        mCarModeTracker.handleSetAutomotiveProjection(CAR_MODE_APP2_PACKAGE_NAME);
+        // Should still only have one app.
+        assertEquals(1, mCarModeTracker.getCarModeApps().size());
+        assertTrue(mCarModeTracker.isInCarMode());
+        // It should be the newer one.
+        assertEquals(CAR_MODE_APP2_PACKAGE_NAME, mCarModeTracker.getCurrentCarModePackage());
+    }
+
+    /**
+     * Verifies that releasing automotive projection works as expected.
+     */
+    @Test
+    public void testReleaseAutomotiveProjectionBasic() {
+        // Releasing before something's set shouldn't break anything.
+        mCarModeTracker.handleReleaseAutomotiveProjection();
+        assertEquals(0, mCarModeTracker.getCarModeApps().size());
+        assertFalse(mCarModeTracker.isInCarMode());
+
+        mCarModeTracker.handleSetAutomotiveProjection(CAR_MODE_APP1_PACKAGE_NAME);
+        mCarModeTracker.handleReleaseAutomotiveProjection();
+        // Should be gone now.
+        assertEquals(0, mCarModeTracker.getCarModeApps().size());
+        assertFalse(mCarModeTracker.isInCarMode());
+    }
+
+    /**
+     * Verifies that setting automotive projection overrides but doesn't overwrite car mode apps.
+     */
+    @Test
+    public void testAutomotiveProjectionOverridesCarMode() {
+        mCarModeTracker.handleEnterCarMode(50, CAR_MODE_APP1_PACKAGE_NAME);
+        mCarModeTracker.handleSetAutomotiveProjection(CAR_MODE_APP4_PACKAGE_NAME);
+
+        // Should have two apps now, the car mode and the automotive projection one.
+        assertEquals(2, mCarModeTracker.getCarModeApps().size());
+        assertTrue(mCarModeTracker.isInCarMode());
+
+        // Automotive projection takes priority.
+        assertEquals(CAR_MODE_APP4_PACKAGE_NAME, mCarModeTracker.getCurrentCarModePackage());
+
+        // If we add another car mode app, automotive projection still has priority.
+        mCarModeTracker.handleEnterCarMode(Integer.MAX_VALUE, CAR_MODE_APP2_PACKAGE_NAME);
+        assertEquals(3, mCarModeTracker.getCarModeApps().size());
+        assertTrue(mCarModeTracker.isInCarMode());
+        assertEquals(CAR_MODE_APP4_PACKAGE_NAME, mCarModeTracker.getCurrentCarModePackage());
+
+        // If we release automotive projection, we go back to the prioritized list of plain car
+        // mode apps.
+        mCarModeTracker.handleReleaseAutomotiveProjection();
+        assertEquals(2, mCarModeTracker.getCarModeApps().size());
+        assertTrue(mCarModeTracker.isInCarMode());
+        assertEquals(CAR_MODE_APP2_PACKAGE_NAME, mCarModeTracker.getCurrentCarModePackage());
+
+        // Make sure we didn't mess with the first app that was added.
+        mCarModeTracker.handleExitCarMode(Integer.MAX_VALUE, CAR_MODE_APP2_PACKAGE_NAME);
+        assertEquals(1, mCarModeTracker.getCarModeApps().size());
+        assertTrue(mCarModeTracker.isInCarMode());
+        assertEquals(CAR_MODE_APP1_PACKAGE_NAME, mCarModeTracker.getCurrentCarModePackage());
+    }
+
+    /**
+     * Verifies that releasing automotive projection doesn't interfere with plain car mode apps.
+     */
+    @Test
+    public void testReleaseAutomotiveProjectionNoopForCarModeApps() {
+        mCarModeTracker.handleEnterCarMode(50, CAR_MODE_APP1_PACKAGE_NAME);
+        mCarModeTracker.handleReleaseAutomotiveProjection();
+        assertEquals(1, mCarModeTracker.getCarModeApps().size());
+        assertTrue(mCarModeTracker.isInCarMode());
+        assertEquals(CAR_MODE_APP1_PACKAGE_NAME, mCarModeTracker.getCurrentCarModePackage());
+    }
 }
diff --git a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
index af062d7..ebb336e 100644
--- a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
+++ b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
@@ -30,7 +30,10 @@
 import android.app.AppOpsManager;
 import android.app.NotificationManager;
 import android.app.StatusBarManager;
+import android.app.UiModeManager;
 import android.app.role.RoleManager;
+import android.content.AttributionSource;
+import android.content.AttributionSourceState;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.ContentResolver;
@@ -42,10 +45,12 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.PermissionInfo;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.content.res.Configuration;
 import android.content.res.Resources;
+import android.hardware.SensorPrivacyManager;
 import android.location.Country;
 import android.location.CountryDetector;
 import android.media.AudioManager;
@@ -54,8 +59,11 @@
 import android.os.IInterface;
 import android.os.PersistableBundle;
 import android.os.PowerWhitelistManager;
+import android.os.Process;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.os.VibratorManager;
+import android.permission.PermissionCheckerManager;
 import android.telecom.CallAudioState;
 import android.telecom.ConnectionService;
 import android.telecom.Log;
@@ -67,6 +75,7 @@
 import android.telephony.TelephonyManager;
 import android.telephony.TelephonyRegistryManager;
 import android.test.mock.MockContext;
+import android.util.DisplayMetrics;
 
 import java.io.File;
 import java.io.IOException;
@@ -78,7 +87,9 @@
 import java.util.Map;
 import java.util.concurrent.Executor;
 
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.matches;
+import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyInt;
@@ -205,6 +216,14 @@
                     return mRoleManager;
                 case Context.TELEPHONY_REGISTRY_SERVICE:
                     return mTelephonyRegistryManager;
+                case Context.UI_MODE_SERVICE:
+                    return mUiModeManager;
+                case Context.VIBRATOR_MANAGER_SERVICE:
+                    return mVibratorManager;
+                case Context.PERMISSION_CHECKER_SERVICE:
+                    return mPermissionCheckerManager;
+                case Context.SENSOR_PRIVACY_SERVICE:
+                    return mSensorPrivacyManager;
                 default:
                     return null;
             }
@@ -226,6 +245,14 @@
                 return Context.TELEPHONY_SUBSCRIPTION_SERVICE;
             } else if (svcClass == TelephonyRegistryManager.class) {
                 return Context.TELEPHONY_REGISTRY_SERVICE;
+            } else if (svcClass == UiModeManager.class) {
+                return Context.UI_MODE_SERVICE;
+            } else if (svcClass == VibratorManager.class) {
+                return Context.VIBRATOR_MANAGER_SERVICE;
+            } else if (svcClass == PermissionCheckerManager.class) {
+                return Context.PERMISSION_CHECKER_SERVICE;
+            } else if (svcClass == SensorPrivacyManager.class) {
+                return Context.SENSOR_PRIVACY_SERVICE;
             }
             throw new UnsupportedOperationException();
         }
@@ -251,6 +278,11 @@
         }
 
         @Override
+        public AttributionSource getAttributionSource() {
+            return mAttributionSource;
+        }
+
+        @Override
         public ContentResolver getContentResolver() {
             return new ContentResolver(mApplicationContextSpy) {
                 @Override
@@ -359,6 +391,11 @@
         }
 
         @Override
+        public int checkSelfPermission(String permission) {
+            return PackageManager.PERMISSION_GRANTED;
+        }
+
+        @Override
         public void enforceCallingOrSelfPermission(String permission, String message) {
             // Don't bother enforcing anything in mock.
         }
@@ -438,6 +475,10 @@
         }
     }
 
+    private static final String PACKAGE_NAME = "com.android.server.telecom.tests";
+    private final AttributionSource mAttributionSource =
+            new AttributionSource.Builder(Process.myUid()).setPackageName(PACKAGE_NAME).build();
+
     private final Multimap<String, ComponentName> mComponentNamesByAction =
             ArrayListMultimap.create();
     private final Map<ComponentName, IInterface> mServiceByComponentName = new HashMap<>();
@@ -468,6 +509,7 @@
     private final Resources.Theme mResourcesTheme = mock(Resources.Theme.class);
     private final Resources mResources = mock(Resources.class);
     private final Context mApplicationContextSpy = spy(mApplicationContext);
+    private final DisplayMetrics mDisplayMetrics = mock(DisplayMetrics.class);
     private final PackageManager mPackageManager = mock(PackageManager.class);
     private final Executor mMainExecutor = mock(Executor.class);
     private final AudioManager mAudioManager = spy(new FakeAudioManager(mContext));
@@ -485,6 +527,12 @@
     private final RoleManager mRoleManager = mock(RoleManager.class);
     private final TelephonyRegistryManager mTelephonyRegistryManager =
             mock(TelephonyRegistryManager.class);
+    private final VibratorManager mVibratorManager = mock(VibratorManager.class);
+    private final UiModeManager mUiModeManager = mock(UiModeManager.class);
+    private final PermissionCheckerManager mPermissionCheckerManager =
+            mock(PermissionCheckerManager.class);
+    private final PermissionInfo mPermissionInfo = mock(PermissionInfo.class);
+    private final SensorPrivacyManager mSensorPrivacyManager = mock(SensorPrivacyManager.class);
 
     private TelecomManager mTelecomManager = mock(TelecomManager.class);
 
@@ -493,6 +541,9 @@
         when(mResources.getConfiguration()).thenReturn(mResourceConfiguration);
         when(mResources.getString(anyInt())).thenReturn("");
         when(mResources.getStringArray(anyInt())).thenReturn(new String[0]);
+        when(mResources.newTheme()).thenReturn(mResourcesTheme);
+        when(mResources.getDisplayMetrics()).thenReturn(mDisplayMetrics);
+        mDisplayMetrics.density = 3.125f;
         mResourceConfiguration.setLocale(Locale.TAIWAN);
 
         // TODO: Move into actual tests
@@ -535,9 +586,19 @@
         }).when(mPackageManager).queryBroadcastReceiversAsUser((Intent) any(), anyInt(), anyInt());
 
         // By default, tests use non-ui apps instead of 3rd party companion apps.
-        when(mPackageManager.checkPermission(
-                matches(Manifest.permission.CALL_COMPANION_APP), anyString()))
-                .thenReturn(PackageManager.PERMISSION_DENIED);
+        when(mPermissionCheckerManager.checkPermission(
+                matches(Manifest.permission.CALL_COMPANION_APP), any(AttributionSourceState.class),
+                nullable(String.class), anyBoolean(), anyBoolean(), anyBoolean(), anyInt()))
+                .thenReturn(PermissionCheckerManager.PERMISSION_HARD_DENIED);
+
+        try {
+            when(mPackageManager.getPermissionInfo(anyString(), anyInt())).thenReturn(
+                    mPermissionInfo);
+        } catch (PackageManager.NameNotFoundException ex) {
+        }
+
+        when(mPermissionInfo.isAppOp()).thenReturn(true);
+        when(mVibratorManager.getVibratorIds()).thenReturn(new int[0]);
 
         // Used in CreateConnectionProcessor to rank emergency numbers by viability.
         // For the test, make them all equal to INVALID so that the preferred PhoneAccount will be
@@ -605,11 +666,15 @@
         serviceInfo.name = componentName.getClassName();
         mServiceInfoByComponentName.put(componentName, serviceInfo);
 
-        // Used in InCallController to check permissions for CONTROL_INCALL_EXPERIENCE
+        // Used in InCallController to check permissions for CONTROL_INCALL_fvEXPERIENCE
         when(mPackageManager.getPackagesForUid(eq(uid))).thenReturn(new String[] {
                 componentName.getPackageName() });
         when(mPackageManager.checkPermission(eq(Manifest.permission.CONTROL_INCALL_EXPERIENCE),
                 eq(componentName.getPackageName()))).thenReturn(PackageManager.PERMISSION_GRANTED);
+        when(mPermissionCheckerManager.checkPermission(
+                eq(Manifest.permission.CONTROL_INCALL_EXPERIENCE),
+                any(AttributionSourceState.class), anyString(), anyBoolean(), anyBoolean(),
+                anyBoolean(), anyInt())).thenReturn(PackageManager.PERMISSION_GRANTED);
     }
 
     public void addIntentReceiver(String action, ComponentName name) {
@@ -652,6 +717,10 @@
         return mTelephonyManager;
     }
 
+    public NotificationManager getNotificationManager() {
+        return mNotificationManager;
+    }
+
     private void addService(String action, ComponentName name, IInterface service) {
         mComponentNamesByAction.put(action, name);
         mServiceByComponentName.put(name, service);
diff --git a/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java b/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
index 46b1522..6e6646f 100755
--- a/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
+++ b/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
@@ -34,6 +34,7 @@
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.telecom.CallAudioState;
+import android.telecom.CallScreeningService;
 import android.telecom.Conference;
 import android.telecom.Connection;
 import android.telecom.ConnectionRequest;
@@ -249,6 +250,7 @@
 
         @Override
         public void createConnectionComplete(String id, Session.Info info) throws RemoteException {
+            Log.i(ConnectionServiceFixture.this, "createConnectionComplete: %s", id);
             mConnectionServiceDelegateAdapter.createConnectionComplete(id, null /*Session.Info*/);
         }
 
@@ -341,6 +343,14 @@
                 throws RemoteException { }
 
         @Override
+        public void onUsingAlternativeUi(String activeCallId, boolean usingAlternativeUi,
+                Session.Info info) throws RemoteException { }
+
+        @Override
+        public void onTrackedByNonUiService(String activeCallId, boolean isTracked,
+                Session.Info info) throws RemoteException { }
+
+        @Override
         public void playDtmfTone(String callId, char digit,
                 Session.Info info) throws RemoteException { }
 
@@ -428,6 +438,10 @@
 
         @Override
         public void handoverComplete(String callId, Session.Info sessionInfo) {}
+
+        @Override
+        public void onCallFilteringCompleted(String callId,
+                Connection.CallFilteringCompletionInfo completionInfo, Session.Info sessionInfo) { }
     }
 
     FakeConnectionServiceDelegate mConnectionServiceDelegate;
diff --git a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
index 78b3332..6cc19ad 100644
--- a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
+++ b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
@@ -18,13 +18,16 @@
 
 import static com.android.server.telecom.InCallController.IN_CALL_SERVICE_NOTIFICATION_ID;
 import static com.android.server.telecom.InCallController.NOTIFICATION_TAG;
+import static com.android.server.telecom.tests.TelecomSystemTest.TEST_TIMEOUT;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.matches;
 import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Matchers.any;
@@ -35,22 +38,29 @@
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.AppOpsManager;
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.UiModeManager;
+import android.content.AttributionSource;
+import android.content.AttributionSourceState;
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.PermissionChecker;
 import android.content.ServiceConnection;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.PermissionInfo;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.content.res.Resources;
@@ -59,7 +69,9 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
+import android.os.Process;
 import android.os.UserHandle;
+import android.permission.PermissionCheckerManager;
 import android.telecom.CallAudioState;
 import android.telecom.InCallService;
 import android.telecom.ParcelableCall;
@@ -67,8 +79,10 @@
 import android.telecom.TelecomManager;
 import android.test.mock.MockContext;
 import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.SmallTest;
 import android.text.TextUtils;
 
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
 import com.android.internal.telecom.IInCallAdapter;
 import com.android.internal.telecom.IInCallService;
 import com.android.server.telecom.Analytics;
@@ -93,14 +107,18 @@
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatcher;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
 import org.mockito.invocation.InvocationOnMock;
+import org.mockito.quality.Strictness;
 import org.mockito.stubbing.Answer;
 
 import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Objects;
 import java.util.concurrent.CompletableFuture;
 
 @RunWith(JUnit4.class)
@@ -110,6 +128,7 @@
     @Mock BluetoothHeadsetProxy mMockBluetoothHeadset;
     @Mock SystemStateHelper mMockSystemStateHelper;
     @Mock PackageManager mMockPackageManager;
+    @Mock PermissionCheckerManager mMockPermissionCheckerManager;
     @Mock Call mMockCall;
     @Mock Resources mMockResources;
     @Mock AppOpsManager mMockAppOpsManager;
@@ -120,6 +139,7 @@
     @Mock ClockProxy mClockProxy;
     @Mock Analytics.CallInfoImpl mCallInfo;
     @Mock NotificationManager mNotificationManager;
+    @Mock PermissionInfo mMockPermissionInfo;
 
     private static final int CURRENT_USER_ID = 900973;
     private static final String DEF_PKG = "defpkg";
@@ -140,6 +160,9 @@
     private static final String NONUI_PKG = "nonui_pkg";
     private static final String NONUI_CLASS = "nonui_cls";
     private static final int NONUI_UID = 6;
+    private static final String APPOP_NONUI_PKG = "appop_nonui_pkg";
+    private static final String APPOP_NONUI_CLASS = "appop_nonui_cls";
+    private static final int APPOP_NONUI_UID = 7;
 
     private static final PhoneAccountHandle PA_HANDLE =
             new PhoneAccountHandle(new ComponentName("pa_pkg", "pa_cls"), "pa_id");
@@ -148,6 +171,8 @@
     private InCallController mInCallController;
     private TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() {};
     private EmergencyCallHelper mEmergencyCallHelper;
+    private SystemStateHelper.SystemStateListener mSystemStateListener;
+    private CarModeTracker mCarModeTracker = spy(new CarModeTracker());
 
     @Override
     @Before
@@ -169,9 +194,21 @@
         when(mMockCallsManager.getRoleManagerAdapter()).thenReturn(mMockRoleManagerAdapter);
         when(mMockContext.getSystemService(eq(Context.NOTIFICATION_SERVICE)))
                 .thenReturn(mNotificationManager);
+        when(mMockContext.getSystemService(eq(PermissionCheckerManager.class)))
+                .thenReturn(mMockPermissionCheckerManager);
+        when(mMockPackageManager.getPermissionInfo(anyString(), anyInt())).thenReturn(
+                mMockPermissionInfo);
+        when(mMockContext.getAttributionSource()).thenReturn(new AttributionSource(Process.myUid(),
+                "com.android.server.telecom.tests", null));
         mInCallController = new InCallController(mMockContext, mLock, mMockCallsManager,
                 mMockSystemStateHelper, mDefaultDialerCache, mTimeoutsAdapter,
-                mEmergencyCallHelper, new CarModeTracker(), mClockProxy);
+                mEmergencyCallHelper, mCarModeTracker, mClockProxy);
+
+        ArgumentCaptor<SystemStateHelper.SystemStateListener> systemStateListenerArgumentCaptor
+                = ArgumentCaptor.forClass(SystemStateHelper.SystemStateListener.class);
+        verify(mMockSystemStateHelper).addListener(systemStateListenerArgumentCaptor.capture());
+        mSystemStateListener = systemStateListenerArgumentCaptor.getValue();
+
         // Companion Apps don't have CONTROL_INCALL_EXPERIENCE permission.
         doAnswer(invocation -> {
             int uid = invocation.getArgument(0);
@@ -188,21 +225,42 @@
                     return new String[] { CAR2_PKG };
                 case NONUI_UID:
                     return new String[] { NONUI_PKG };
+                case APPOP_NONUI_UID:
+                    return new String[] { APPOP_NONUI_PKG };
             }
             return null;
         }).when(mMockPackageManager).getPackagesForUid(anyInt());
-        when(mMockPackageManager.checkPermission(
+
+        when(mMockPermissionCheckerManager.checkPermission(
                 matches(Manifest.permission.CONTROL_INCALL_EXPERIENCE),
-                matches(COMPANION_PKG))).thenReturn(PackageManager.PERMISSION_DENIED);
-        when(mMockPackageManager.checkPermission(
+                matchesAttributionSourcePackage(COMPANION_PKG), nullable(String.class),
+                anyBoolean(), anyBoolean(), anyBoolean(), anyInt()))
+                .thenReturn(PackageManager.PERMISSION_DENIED);
+
+        when(mMockPermissionCheckerManager.checkPermission(
                 matches(Manifest.permission.CONTROL_INCALL_EXPERIENCE),
-                matches(CAR_PKG))).thenReturn(PackageManager.PERMISSION_GRANTED);
-        when(mMockPackageManager.checkPermission(
+                matchesAttributionSourcePackage(CAR_PKG), nullable(String.class),
+                anyBoolean(), anyBoolean(), anyBoolean(), anyInt()))
+                .thenReturn(PackageManager.PERMISSION_GRANTED);
+
+        when(mMockPermissionCheckerManager.checkPermission(
                 matches(Manifest.permission.CONTROL_INCALL_EXPERIENCE),
-                matches(CAR2_PKG))).thenReturn(PackageManager.PERMISSION_GRANTED);
-        when(mMockPackageManager.checkPermission(
+                matchesAttributionSourcePackage(CAR2_PKG), nullable(String.class),
+                anyBoolean(), anyBoolean(), anyBoolean(), anyInt()))
+                .thenReturn(PackageManager.PERMISSION_GRANTED);
+
+        when(mMockPermissionCheckerManager.checkPermission(
                 matches(Manifest.permission.CONTROL_INCALL_EXPERIENCE),
-                matches(NONUI_PKG))).thenReturn(PackageManager.PERMISSION_GRANTED);
+                matchesAttributionSourcePackage(NONUI_PKG), nullable(String.class),
+                anyBoolean(), anyBoolean(), anyBoolean(), anyInt()))
+                .thenReturn(PackageManager.PERMISSION_GRANTED);
+
+        when(mMockPermissionCheckerManager.checkPermission(
+                matches(Manifest.permission.CONTROL_INCALL_EXPERIENCE),
+                matchesAttributionSourcePackage(APPOP_NONUI_PKG), nullable(String.class),
+                anyBoolean(), anyBoolean(), anyBoolean(), anyInt()))
+                .thenReturn(PackageManager.PERMISSION_DENIED);
+
         when(mMockCallsManager.getAudioState()).thenReturn(new CallAudioState(false, 0, 0));
     }
 
@@ -214,6 +272,63 @@
         super.tearDown();
     }
 
+    @SmallTest
+    @Test
+    public void testCarModeAppRemoval() {
+        setupMockPackageManager(true /* default */, true /* system */, true /* external calls */);
+        when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
+        when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+
+        when(mMockSystemStateHelper.isCarModeOrProjectionActive()).thenReturn(true);
+
+        mSystemStateListener.onCarModeChanged(666, CAR_PKG, true);
+        verify(mCarModeTracker).handleEnterCarMode(666, CAR_PKG);
+        assertTrue(mCarModeTracker.isInCarMode());
+
+        mSystemStateListener.onPackageUninstalled(CAR_PKG);
+        verify(mCarModeTracker).forceRemove(CAR_PKG);
+        assertFalse(mCarModeTracker.isInCarMode());
+    }
+
+    /**
+     * Ensure that if we remove a random unrelated app we don't exit car mode.
+     */
+    @SmallTest
+    @Test
+    public void testRandomAppRemovalInCarMode() {
+        setupMockPackageManager(true /* default */, true /* system */, true /* external calls */);
+        when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
+        when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+
+        when(mMockSystemStateHelper.isCarModeOrProjectionActive()).thenReturn(true);
+
+        mSystemStateListener.onCarModeChanged(666, CAR_PKG, true);
+        verify(mCarModeTracker).handleEnterCarMode(666, CAR_PKG);
+        assertTrue(mCarModeTracker.isInCarMode());
+
+        mSystemStateListener.onPackageUninstalled("com.foo.test");
+        verify(mCarModeTracker, never()).forceRemove(CAR_PKG);
+        assertTrue(mCarModeTracker.isInCarMode());
+    }
+
+    @SmallTest
+    @Test
+    public void testAutomotiveProjectionAppRemoval() {
+        setupMockPackageManager(true /* default */, true /* system */, true /* external calls */);
+        when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
+        when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+
+        when(mMockSystemStateHelper.isCarModeOrProjectionActive()).thenReturn(true);
+
+        mSystemStateListener.onAutomotiveProjectionStateSet(CAR_PKG);
+        verify(mCarModeTracker).handleSetAutomotiveProjection(CAR_PKG);
+        assertTrue(mCarModeTracker.isInCarMode());
+
+        mSystemStateListener.onPackageUninstalled(CAR_PKG);
+        verify(mCarModeTracker).forceRemove(CAR_PKG);
+        assertFalse(mCarModeTracker.isInCarMode());
+    }
+
     @MediumTest
     @Test
     public void testBindToService_NoServicesFound_IncomingCall() throws Exception {
@@ -306,7 +421,8 @@
         ArgumentCaptor<Intent> queryIntentCaptor = ArgumentCaptor.forClass(Intent.class);
         verify(mMockPackageManager, times(4)).queryIntentServicesAsUser(
                 queryIntentCaptor.capture(),
-                eq(PackageManager.GET_META_DATA), eq(CURRENT_USER_ID));
+                eq(PackageManager.GET_META_DATA | PackageManager.MATCH_DISABLED_COMPONENTS),
+                eq(CURRENT_USER_ID));
 
         // Verify call for default dialer InCallService
         assertEquals(DEF_PKG, queryIntentCaptor.getAllValues().get(0).getPackage());
@@ -365,7 +481,8 @@
         ArgumentCaptor<Intent> queryIntentCaptor = ArgumentCaptor.forClass(Intent.class);
         verify(mMockPackageManager, times(4)).queryIntentServicesAsUser(
                 queryIntentCaptor.capture(),
-                eq(PackageManager.GET_META_DATA), eq(CURRENT_USER_ID));
+                eq(PackageManager.GET_META_DATA | PackageManager.MATCH_DISABLED_COMPONENTS),
+                eq(CURRENT_USER_ID));
 
         // Verify call for default dialer InCallService
         assertEquals(DEF_PKG, queryIntentCaptor.getAllValues().get(0).getPackage());
@@ -397,7 +514,7 @@
         // Pretend that the call has gone away.
         when(mMockCallsManager.getCalls()).thenReturn(Collections.emptyList());
         mInCallController.onCallRemoved(mMockCall);
-        waitForHandlerAction(new Handler(Looper.getMainLooper()), TelecomSystemTest.TEST_TIMEOUT);
+        waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
 
         verify(mMockPackageManager).revokeRuntimePermission(eq(SYS_PKG),
                 eq(Manifest.permission.ACCESS_FINE_LOCATION), eq(mUserHandle));
@@ -443,7 +560,8 @@
         ArgumentCaptor<Intent> queryIntentCaptor = ArgumentCaptor.forClass(Intent.class);
         verify(mMockPackageManager, times(4)).queryIntentServicesAsUser(
                 queryIntentCaptor.capture(),
-                eq(PackageManager.GET_META_DATA), eq(CURRENT_USER_ID));
+                eq(PackageManager.GET_META_DATA | PackageManager.MATCH_DISABLED_COMPONENTS),
+                eq(CURRENT_USER_ID));
 
         // Verify call for default dialer InCallService
         assertEquals(DEF_PKG, queryIntentCaptor.getAllValues().get(0).getPackage());
@@ -525,7 +643,8 @@
         ArgumentCaptor<Intent> queryIntentCaptor = ArgumentCaptor.forClass(Intent.class);
         verify(mMockPackageManager, times(4)).queryIntentServicesAsUser(
                 queryIntentCaptor.capture(),
-                eq(PackageManager.GET_META_DATA), eq(CURRENT_USER_ID));
+                eq(PackageManager.GET_META_DATA | PackageManager.MATCH_DISABLED_COMPONENTS),
+                eq(CURRENT_USER_ID));
 
         // Verify call for default dialer InCallService
         assertEquals(DEF_PKG, queryIntentCaptor.getAllValues().get(0).getPackage());
@@ -651,7 +770,8 @@
         ArgumentCaptor<Intent> queryIntentCaptor = ArgumentCaptor.forClass(Intent.class);
         verify(mMockPackageManager, times(4)).queryIntentServicesAsUser(
                 queryIntentCaptor.capture(),
-                eq(PackageManager.GET_META_DATA), eq(CURRENT_USER_ID));
+                eq(PackageManager.GET_META_DATA | PackageManager.MATCH_DISABLED_COMPONENTS),
+                eq(CURRENT_USER_ID));
 
         // Verify call for default dialer InCallService
         assertEquals(DEF_PKG, queryIntentCaptor.getAllValues().get(0).getPackage());
@@ -741,7 +861,7 @@
         setupMockPackageManager(true /* default */, true /* system */, true /* external calls */);
 
         // Enable car mode
-        when(mMockSystemStateHelper.isCarMode()).thenReturn(true);
+        when(mMockSystemStateHelper.isCarModeOrProjectionActive()).thenReturn(true);
         mInCallController.handleCarModeChange(UiModeManager.DEFAULT_PRIORITY, CAR_PKG, true);
 
         // Now bind; we should only bind to one app.
@@ -771,7 +891,7 @@
                 matches(Manifest.permission.CONTROL_INCALL_EXPERIENCE),
                 matches(CAR_PKG))).thenReturn(PackageManager.PERMISSION_DENIED);
         // Enable car mode
-        when(mMockSystemStateHelper.isCarMode()).thenReturn(true);
+        when(mMockSystemStateHelper.isCarModeOrProjectionActive()).thenReturn(true);
 
         // Register the fact that the invalid app entered car mode.
         mInCallController.handleCarModeChange(UiModeManager.DEFAULT_PRIORITY, CAR_PKG, true);
@@ -789,6 +909,85 @@
         verifyBinding(bindIntentCaptor, 0, DEF_PKG, DEF_CLASS);
     }
 
+   /**
+     * Ensures that the {@link InCallController} will bind to an {@link InCallService} which
+     * supports third party app
+     */
+    @MediumTest
+    @Test
+    public void testBindToService_ThirdPartyApp() throws Exception {
+        final MockitoSession mockitoSession = ExtendedMockito.mockitoSession()
+                .strictness(Strictness.WARN)
+                .spyStatic(PermissionChecker.class)
+                .startMocking();
+        try {
+            setupMocks(false /* isExternalCall */);
+            setupMockPackageManager(false /* default */, false /* nonui */, true /* appop_nonui */,
+                    true /* system */, false /* external calls */, false /* self mgd in default */,
+                    false /* self mgd in car*/);
+
+            // Enable Third Party Companion App
+            ExtendedMockito.doReturn(PermissionChecker.PERMISSION_GRANTED).when(() ->
+                    PermissionChecker.checkPermissionForDataDeliveryFromDataSource(
+                            any(Context.class), eq(Manifest.permission.MANAGE_ONGOING_CALLS),
+                            anyInt(), any(AttributionSource.class), nullable(String.class)));
+
+            // Now bind; we should bind to the system dialer and app op non ui app.
+            mInCallController.bindToServices(mMockCall);
+
+            // Bind InCallServices
+            ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
+            verify(mMockContext, times(2)).bindServiceAsUser(
+                    bindIntentCaptor.capture(),
+                    any(ServiceConnection.class),
+                    eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
+                            | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS),
+                    eq(UserHandle.CURRENT));
+
+            // Verify bind
+            assertEquals(2, bindIntentCaptor.getAllValues().size());
+
+            // Should have first bound to the system dialer.
+            verifyBinding(bindIntentCaptor, 0, SYS_PKG, SYS_CLASS);
+
+            // Should have next bound to the third party app op non ui app.
+            verifyBinding(bindIntentCaptor, 1, APPOP_NONUI_PKG, APPOP_NONUI_CLASS);
+        } finally {
+            mockitoSession.finishMocking();
+        }
+    }
+
+    /**
+     * Ensures that the {@link InCallController} will bind to a non-ui service even if no ui service
+     * is bound if the call is self managed.
+     */
+    @MediumTest
+    @Test
+    public void testBindToService_NonUiSelfManaged() throws Exception {
+        setupMocks(false /* isExternalCall */, true);
+        setupMockPackageManager(false /* default */, true/* nonui */, true /* appop_nonui */,
+                true /* system */, false /* external calls */, false /* self mgd in default */,
+                false /* self mgd in car*/, true /* self managed in nonui */);
+
+        // we should bind to only the non ui app.
+        mInCallController.bindToServices(mMockCall);
+
+        // Bind InCallServices
+        ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mMockContext, times(1)).bindServiceAsUser(
+                bindIntentCaptor.capture(),
+                any(ServiceConnection.class),
+                eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
+                        | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS),
+                eq(UserHandle.CURRENT));
+
+        // Verify bind
+        assertEquals(1, bindIntentCaptor.getAllValues().size());
+
+        // Should have bound to the third party non ui app.
+        verifyBinding(bindIntentCaptor, 0, NONUI_PKG, NONUI_CLASS);
+    }
+
     @MediumTest
     @Test
     public void testSanitizeContactName() throws Exception {
@@ -833,6 +1032,31 @@
      */
     @MediumTest
     @Test
+    public void testRandomAppRemovalWhenNotInCarMode() throws Exception {
+        setupMocks(true /* isExternalCall */);
+        setupMockPackageManager(true /* default */, true /* system */, true /* external calls */);
+        // Bind to default dialer.
+        mInCallController.bindToServices(mMockCall);
+
+        // Uninstall an unrelated app.
+        mSystemStateListener.onPackageUninstalled("com.joe.stuff");
+
+        // Bind InCallServices, just once; we should not re-bind to the same app.
+        ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mMockContext).bindServiceAsUser(
+                bindIntentCaptor.capture(),
+                any(ServiceConnection.class),
+                eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
+                        | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS),
+                eq(UserHandle.CURRENT));
+    }
+
+    /**
+     * Ensures that the {@link InCallController} will bind to a higher priority car mode service
+     * when one becomes available.
+     */
+    @MediumTest
+    @Test
     public void testCarmodeRebindHigherPriority() throws Exception {
         setupMocks(true /* isExternalCall */);
         setupMockPackageManager(true /* default */, true /* system */, true /* external calls */);
@@ -840,7 +1064,7 @@
         mInCallController.bindToServices(mMockCall);
 
         // Enable car mode and enter car mode at default priority.
-        when(mMockSystemStateHelper.isCarMode()).thenReturn(true);
+        when(mMockSystemStateHelper.isCarModeOrProjectionActive()).thenReturn(true);
         mInCallController.handleCarModeChange(UiModeManager.DEFAULT_PRIORITY, CAR_PKG, true);
 
         // And change to the second car mode app.
@@ -901,8 +1125,8 @@
                 nullable(ContentResolver.class))).thenReturn(500L);
 
         when(mMockCallsManager.getCalls()).thenReturn(Collections.singletonList(mMockCall));
-        setupMockPackageManager(true /* default */, true /* nonui */, true /* system */,
-                false /* external calls */,
+        setupMockPackageManager(true /* default */, true /* nonui */, false /* appop_nonui */ ,
+                true /* system */, false /* external calls */,
                 false /* self mgd in default*/, false /* self mgd in car*/);
         mInCallController.bindToServices(mMockCall);
 
@@ -987,7 +1211,7 @@
 
         // Now switch to car mode.
         // Enable car mode and enter car mode at default priority.
-        when(mMockSystemStateHelper.isCarMode()).thenReturn(true);
+        when(mMockSystemStateHelper.isCarModeOrProjectionActive()).thenReturn(true);
         mInCallController.handleCarModeChange(UiModeManager.DEFAULT_PRIORITY, CAR_PKG, true);
 
         ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -1027,7 +1251,7 @@
 
         // Now switch to car mode.
         // Enable car mode and enter car mode at default priority.
-        when(mMockSystemStateHelper.isCarMode()).thenReturn(true);
+        when(mMockSystemStateHelper.isCarModeOrProjectionActive()).thenReturn(true);
         mInCallController.handleCarModeChange(UiModeManager.DEFAULT_PRIORITY, CAR_PKG, true);
 
         // We currently will bind to the car-mode InCallService even if there are no calls available
@@ -1072,6 +1296,7 @@
                 anyInt(), eq(UserHandle.CURRENT))).thenReturn(true);
         when(mMockCall.isExternalCall()).thenReturn(isExternalCall);
         when(mMockCall.isSelfManaged()).thenReturn(isSelfManagedCall);
+        when(mMockCall.visibleToInCallService()).thenReturn(isSelfManagedCall);
     }
 
     private ResolveInfo getDefResolveInfo(final boolean includeExternalCalls,
@@ -1083,6 +1308,7 @@
             serviceInfo.applicationInfo = new ApplicationInfo();
             serviceInfo.applicationInfo.uid = DEF_UID;
             serviceInfo.permission = Manifest.permission.BIND_INCALL_SERVICE;
+            serviceInfo.enabled = true;
             serviceInfo.metaData = new Bundle();
             serviceInfo.metaData.putBoolean(
                     TelecomManager.METADATA_IN_CALL_SERVICE_UI, true);
@@ -1110,6 +1336,7 @@
                 serviceInfo.applicationInfo.uid = CAR2_UID;
             }
             serviceInfo.permission = Manifest.permission.BIND_INCALL_SERVICE;
+            serviceInfo.enabled = true;
             serviceInfo.metaData = new Bundle();
             serviceInfo.metaData.putBoolean(
                     TelecomManager.METADATA_IN_CALL_SERVICE_CAR_MODE_UI, true);
@@ -1131,6 +1358,7 @@
             serviceInfo.name = SYS_CLASS;
             serviceInfo.applicationInfo = new ApplicationInfo();
             serviceInfo.applicationInfo.uid = SYS_UID;
+            serviceInfo.enabled = true;
             serviceInfo.permission = Manifest.permission.BIND_INCALL_SERVICE;
         }};
     }
@@ -1142,11 +1370,12 @@
             serviceInfo.name = COMPANION_CLASS;
             serviceInfo.applicationInfo = new ApplicationInfo();
             serviceInfo.applicationInfo.uid = COMPANION_UID;
+            serviceInfo.enabled = true;
             serviceInfo.permission = Manifest.permission.BIND_INCALL_SERVICE;
         }};
     }
 
-    private ResolveInfo getNonUiResolveinfo() {
+    private ResolveInfo getNonUiResolveinfo(boolean supportsSelfManaged) {
         return new ResolveInfo() {{
             serviceInfo = new ServiceInfo();
             serviceInfo.packageName = NONUI_PKG;
@@ -1155,12 +1384,29 @@
             serviceInfo.applicationInfo.uid = NONUI_UID;
             serviceInfo.enabled = true;
             serviceInfo.permission = Manifest.permission.BIND_INCALL_SERVICE;
+            serviceInfo.metaData = new Bundle();
+            if (supportsSelfManaged) {
+                serviceInfo.metaData.putBoolean(
+                        TelecomManager.METADATA_INCLUDE_SELF_MANAGED_CALLS, true);
+            }
+        }};
+    }
+
+    private ResolveInfo getAppOpNonUiResolveinfo() {
+        return new ResolveInfo() {{
+            serviceInfo = new ServiceInfo();
+            serviceInfo.packageName = APPOP_NONUI_PKG;
+            serviceInfo.name = APPOP_NONUI_CLASS;
+            serviceInfo.applicationInfo = new ApplicationInfo();
+            serviceInfo.applicationInfo.uid = APPOP_NONUI_UID;
+            serviceInfo.enabled = true;
+            serviceInfo.permission = Manifest.permission.BIND_INCALL_SERVICE;
         }};
     }
 
     private void setupMockPackageManager(final boolean useDefaultDialer,
             final boolean useSystemDialer, final boolean includeExternalCalls) {
-        setupMockPackageManager(useDefaultDialer, false, useSystemDialer, includeExternalCalls,
+        setupMockPackageManager(useDefaultDialer, false, false, useSystemDialer, includeExternalCalls,
                 false /* self mgd */, false /* self mgd */);
     }
 
@@ -1168,16 +1414,28 @@
             final boolean useSystemDialer, final boolean includeExternalCalls,
             final boolean includeSelfManagedCallsInDefaultDialer,
             final boolean includeSelfManagedCallsInCarModeDialer) {
-        setupMockPackageManager(useDefaultDialer, false /* nonui */, useSystemDialer,
-                includeExternalCalls, includeSelfManagedCallsInDefaultDialer,
+        setupMockPackageManager(useDefaultDialer, false /* nonui */, false /* appop_nonui */,
+                useSystemDialer, includeExternalCalls, includeSelfManagedCallsInDefaultDialer,
                 includeSelfManagedCallsInCarModeDialer);
     }
 
     private void setupMockPackageManager(final boolean useDefaultDialer,
-            final boolean useNonUiInCalls,
+            final boolean useNonUiInCalls, final boolean useAppOpNonUiInCalls,
             final boolean useSystemDialer, final boolean includeExternalCalls,
             final boolean includeSelfManagedCallsInDefaultDialer,
             final boolean includeSelfManagedCallsInCarModeDialer) {
+        setupMockPackageManager(useDefaultDialer, useNonUiInCalls/* nonui */,
+                useAppOpNonUiInCalls/* appop_nonui */,
+                useSystemDialer, includeExternalCalls, includeSelfManagedCallsInDefaultDialer,
+                includeSelfManagedCallsInCarModeDialer, false);
+    }
+
+    private void setupMockPackageManager(final boolean useDefaultDialer,
+            final boolean useNonUiInCalls, final boolean useAppOpNonUiInCalls,
+            final boolean useSystemDialer, final boolean includeExternalCalls,
+            final boolean includeSelfManagedCallsInDefaultDialer,
+            final boolean includeSelfManagedCallsInCarModeDialer,
+            final boolean includeSelfManagedCallsInNonUi) {
         doAnswer(new Answer() {
             @Override
             public Object answer(InvocationOnMock invocation) throws Throwable {
@@ -1215,14 +1473,40 @@
                 } else {
                     // InCallController uses a blank package name when querying for non-ui incalls
                     if (useNonUiInCalls) {
-                        resolveInfo.add(getNonUiResolveinfo());
+                        resolveInfo.add(getNonUiResolveinfo(includeSelfManagedCallsInNonUi));
+                    }
+                    // InCallController uses a blank package name when querying for App Op non-ui incalls
+                    if (useAppOpNonUiInCalls) {
+                        resolveInfo.add(getAppOpNonUiResolveinfo());
                     }
                 }
 
                 return resolveInfo;
             }
         }).when(mMockPackageManager).queryIntentServicesAsUser(
-                any(Intent.class), eq(PackageManager.GET_META_DATA), eq(CURRENT_USER_ID));
+                any(Intent.class), anyInt(), eq(CURRENT_USER_ID));
+
+        if (useDefaultDialer) {
+            when(mMockPackageManager
+                    .getComponentEnabledSetting(new ComponentName(DEF_PKG, DEF_CLASS)))
+                    .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+        }
+
+        when(mMockPackageManager
+                .getComponentEnabledSetting(new ComponentName(SYS_PKG, SYS_CLASS)))
+                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+
+        when(mMockPackageManager
+                .getComponentEnabledSetting(new ComponentName(CAR_PKG, CAR_CLASS)))
+                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+
+        when(mMockPackageManager
+                .getComponentEnabledSetting(new ComponentName(COMPANION_PKG, COMPANION_CLASS)))
+                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+
+        when(mMockPackageManager
+                .getComponentEnabledSetting(new ComponentName(CAR2_PKG, CAR2_CLASS)))
+                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
     }
 
     private void setupMockPackageManagerLocationPermission(final String pkg,
@@ -1232,4 +1516,24 @@
                         ? PackageManager.PERMISSION_GRANTED
                         : PackageManager.PERMISSION_DENIED);
   }
+
+    private static AttributionSourceState matchesAttributionSourcePackage(
+            @Nullable String packageName) {
+        return argThat(new PackageNameArgumentMatcher(packageName));
+    }
+
+    private static class PackageNameArgumentMatcher implements
+            ArgumentMatcher<AttributionSourceState> {
+        @Nullable
+        private final String mPackgeName;
+
+        PackageNameArgumentMatcher(@Nullable String packageName) {
+            mPackgeName = packageName;
+        }
+
+        @Override
+        public boolean matches(@NonNull AttributionSourceState attributionSource) {
+            return Objects.equals(mPackgeName, attributionSource.packageName);
+        }
+    }
 }
diff --git a/tests/src/com/android/server/telecom/tests/IncomingCallFilterGraphTest.java b/tests/src/com/android/server/telecom/tests/IncomingCallFilterGraphTest.java
index 8c0adfb..9269836 100644
--- a/tests/src/com/android/server/telecom/tests/IncomingCallFilterGraphTest.java
+++ b/tests/src/com/android/server/telecom/tests/IncomingCallFilterGraphTest.java
@@ -112,7 +112,7 @@
     @Test
     public void testEmptyGraph() throws Exception {
         CompletableFuture<CallFilteringResult> testResult = new CompletableFuture<>();
-        CallFilterResultCallback listener = (call, result) -> testResult.complete(result);
+        CallFilterResultCallback listener = (call, result, timeout) -> testResult.complete(result);
 
         IncomingCallFilterGraph graph = new IncomingCallFilterGraph(mCall, listener, mContext,
                 mTimeoutsAdapter, mLock);
@@ -125,7 +125,7 @@
     @Test
     public void testFiltersPerformOrder() throws Exception {
         CompletableFuture<CallFilteringResult> testResult = new CompletableFuture<>();
-        CallFilterResultCallback listener = (call, result) -> testResult.complete(result);
+        CallFilterResultCallback listener = (call, result, timeout) -> testResult.complete(result);
 
         IncomingCallFilterGraph graph = new IncomingCallFilterGraph(mCall, listener, mContext,
                 mTimeoutsAdapter, mLock);
@@ -143,7 +143,7 @@
     @Test
     public void testFiltersPerformInParallel() throws Exception {
         CompletableFuture<CallFilteringResult> testResult = new CompletableFuture<>();
-        CallFilterResultCallback listener = (call, result) -> testResult.complete(result);
+        CallFilterResultCallback listener = (call, result, timeout) -> testResult.complete(result);
 
         IncomingCallFilterGraph graph = new IncomingCallFilterGraph(mCall, listener, mContext,
                 mTimeoutsAdapter, mLock);
@@ -162,7 +162,7 @@
     @Test
     public void testFiltersTimeout() throws Exception {
         CompletableFuture<CallFilteringResult> testResult = new CompletableFuture<>();
-        CallFilterResultCallback listener = (call, result) -> testResult.complete(result);
+        CallFilterResultCallback listener = (call, result, timeout) -> testResult.complete(result);
 
         IncomingCallFilterGraph graph = new IncomingCallFilterGraph(mCall, listener, mContext,
                 mTimeoutsAdapter, mLock);
diff --git a/tests/src/com/android/server/telecom/tests/IncomingCallFilterTest.java b/tests/src/com/android/server/telecom/tests/IncomingCallFilterTest.java
deleted file mode 100644
index 8e2d11e..0000000
--- a/tests/src/com/android/server/telecom/tests/IncomingCallFilterTest.java
+++ /dev/null
@@ -1,328 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.telecom.tests;
-
-import android.content.ContentResolver;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.Looper;
-import android.provider.CallLog.Calls;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import com.android.server.telecom.Call;
-import com.android.server.telecom.Timeouts;
-import com.android.server.telecom.callfiltering.CallFilterResultCallback;
-import com.android.server.telecom.callfiltering.CallFilteringResult;
-import com.android.server.telecom.callfiltering.CallFilteringResult.Builder;
-import com.android.server.telecom.callfiltering.IncomingCallFilter;
-import com.android.server.telecom.TelecomSystem;
-
-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;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.atMost;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-@RunWith(JUnit4.class)
-public class IncomingCallFilterTest extends TelecomTestCase {
-    @Mock private CallFilterResultCallback mResultCallback;
-    @Mock private Call mCall;
-    private final TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() {};
-
-    @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;
-
-    private static final Uri TEST_HANDLE = Uri.parse("tel:1235551234");
-    private static final long LONG_TIMEOUT = 1000000;
-    private static final long SHORT_TIMEOUT = 100;
-
-    private static final CallFilteringResult PASS_CALL_RESULT =
-            new Builder()
-                    .setShouldAllowCall(true)
-                    .setShouldReject(false)
-                    .setShouldAddToCallLog(true)
-                    .setShouldShowNotification(true)
-                    .build();
-
-    private static final CallFilteringResult ASYNC_BLOCK_CHECK_BLOCK_RESULT =
-            new Builder()
-                    .setShouldAllowCall(false)
-                    .setShouldReject(true)
-                    .setShouldAddToCallLog(true)
-                    .setShouldShowNotification(false)
-                    .setCallBlockReason(Calls.BLOCK_REASON_BLOCKED_NUMBER)
-                    .setCallScreeningAppName(null)
-                    .setCallScreeningComponentName(null)
-                    .build();
-
-    private static final CallFilteringResult DIRECT_TO_VOICEMAIL_CALL_BLOCK_RESULT =
-            new Builder()
-                    .setShouldAllowCall(false)
-                    .setShouldReject(true)
-                    .setShouldAddToCallLog(true)
-                    .setShouldShowNotification(true)
-                    .setCallBlockReason(Calls.BLOCK_REASON_DIRECT_TO_VOICEMAIL)
-                    .setCallScreeningAppName(null)
-                    .setCallScreeningComponentName(null)
-                    .build();
-
-    private static final CallFilteringResult CALL_SCREENING_SERVICE_BLOCK_RESULT =
-            new Builder()
-                    .setShouldAllowCall(false)
-                    .setShouldReject(true)
-                    .setShouldAddToCallLog(false)
-                    .setShouldShowNotification(true)
-                    .setCallBlockReason(Calls.BLOCK_REASON_CALL_SCREENING_SERVICE)
-                    .setCallScreeningAppName("com.android.thirdparty")
-                    .setCallScreeningComponentName(
-                            "com.android.thirdparty/"
-                                    + "com.android.thirdparty.callscreeningserviceimpl")
-                    .build();
-
-    private static final CallFilteringResult DEFAULT_RESULT = PASS_CALL_RESULT;
-    private Handler mHandler = new Handler(Looper.getMainLooper());
-
-    @Override
-    @Before
-    public void setUp() throws Exception {
-        super.setUp();
-        mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
-        when(mCall.getHandle()).thenReturn(TEST_HANDLE);
-        setTimeoutLength(LONG_TIMEOUT);
-    }
-
-    @Override
-    @After
-    public void tearDown() throws Exception {
-        mHandler.removeCallbacksAndMessages(null);
-        waitForHandlerAction(mHandler, 1000);
-        super.tearDown();
-    }
-
-    @SmallTest
-    @Test
-    public void testAsyncBlockCallResultFilter() {
-        IncomingCallFilter testFilter = new IncomingCallFilter(mContext, mResultCallback, mCall,
-                mLock, mTimeoutsAdapter, Collections.singletonList(mFilter1), mHandler);
-        testFilter.performFiltering();
-        verify(mFilter1).startFilterLookup(mCall, testFilter);
-
-        testFilter.onCallFilteringComplete(mCall, ASYNC_BLOCK_CHECK_BLOCK_RESULT);
-        waitForHandlerAction(testFilter.getHandler(), SHORT_TIMEOUT * 2);
-        verify(mResultCallback).onCallFilteringComplete(eq(mCall), eq
-                (ASYNC_BLOCK_CHECK_BLOCK_RESULT));
-    }
-
-    @SmallTest
-    @Test
-    public void testDirectToVoiceMailCallResultFilter() {
-        IncomingCallFilter testFilter = new IncomingCallFilter(mContext, mResultCallback, mCall,
-                mLock, mTimeoutsAdapter, Collections.singletonList(mFilter1), mHandler);
-        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), mHandler);
-        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), mHandler);
-        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, mHandler);
-        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 Builder()
-                .setShouldAllowCall(false)
-                .setShouldReject(true)
-                .setShouldAddToCallLog(false)
-                .setShouldShowNotification(false)
-                .setCallBlockReason(Calls.BLOCK_REASON_BLOCKED_NUMBER)
-                .setCallScreeningAppName(null)
-                .setCallScreeningComponentName(null)
-                .build()));
-    }
-
-    @SmallTest
-    @Test
-    public void testMultipleFiltersForDirectToVoicemailCallFilter() {
-        List<IncomingCallFilter.CallFilter> filters =
-                new ArrayList<IncomingCallFilter.CallFilter>() {{
-                    add(mFilter1);
-                    add(mFilter2);
-                    add(mFilter3);
-                }};
-        IncomingCallFilter testFilter = new IncomingCallFilter(mContext, mResultCallback, mCall,
-                mLock, mTimeoutsAdapter, filters, mHandler);
-        testFilter.performFiltering();
-        verify(mFilter1).startFilterLookup(mCall, testFilter);
-        verify(mFilter2).startFilterLookup(mCall, testFilter);
-        verify(mFilter3).startFilterLookup(mCall, testFilter);
-
-        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 Builder()
-                .setShouldAllowCall(false)
-                .setShouldReject(true)
-                .setShouldAddToCallLog(false)
-                .setShouldShowNotification(true)
-                .setCallBlockReason(Calls.BLOCK_REASON_DIRECT_TO_VOICEMAIL)
-                .setCallScreeningAppName(null)
-                .setCallScreeningComponentName(null)
-                .build()));
-    }
-
-    @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, mHandler);
-        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 Builder()
-                .setShouldAllowCall(false)
-                .setShouldReject(true)
-                .setShouldAddToCallLog(false)
-                .setShouldShowNotification(true)
-                .setCallBlockReason(Calls.BLOCK_REASON_CALL_SCREENING_SERVICE)
-                .setCallScreeningAppName("com.android.thirdparty")
-                .setCallScreeningComponentName(
-                        "com.android.thirdparty/com.android.thirdparty.callscreeningserviceimpl")
-                .build()));
-    }
-
-    @SmallTest
-    @Test
-    public void testFilterTimeout() throws Exception {
-        setTimeoutLength(SHORT_TIMEOUT);
-        IncomingCallFilter testFilter = new IncomingCallFilter(mContext, mResultCallback, mCall,
-                mLock, mTimeoutsAdapter, Collections.singletonList(mFilter1), mHandler);
-        testFilter.performFiltering();
-        verify(mResultCallback, timeout((int) SHORT_TIMEOUT * 2)).onCallFilteringComplete(eq(mCall),
-                eq(DEFAULT_RESULT));
-        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),
-                any(CallFilteringResult.class));
-    }
-
-    @SmallTest
-    @Test
-    public void testFilterTimeoutDoesntTrip() throws Exception {
-        setTimeoutLength(SHORT_TIMEOUT);
-        IncomingCallFilter testFilter = new IncomingCallFilter(mContext, mResultCallback, mCall,
-                mLock, mTimeoutsAdapter, Collections.singletonList(mFilter1), mHandler);
-        testFilter.performFiltering();
-        testFilter.onCallFilteringComplete(mCall, PASS_CALL_RESULT);
-        waitForHandlerAction(testFilter.getHandler(), SHORT_TIMEOUT * 2);
-        Thread.sleep(SHORT_TIMEOUT);
-        verify(mResultCallback, atMost(1)).onCallFilteringComplete(any(Call.class),
-                any(CallFilteringResult.class));
-    }
-
-    @SmallTest
-    @Test
-    public void testToString() {
-        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 {
-        when(mTimeoutsAdapter.getCallScreeningTimeoutMillis(any(ContentResolver.class)))
-                .thenReturn(length);
-    }
-}
diff --git a/tests/src/com/android/server/telecom/tests/MissedCallNotifierImplTest.java b/tests/src/com/android/server/telecom/tests/MissedCallNotifierImplTest.java
index 9dd90f5..2b05430 100644
--- a/tests/src/com/android/server/telecom/tests/MissedCallNotifierImplTest.java
+++ b/tests/src/com/android/server/telecom/tests/MissedCallNotifierImplTest.java
@@ -29,6 +29,8 @@
 import android.content.IContentProvider;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.Bundle;
@@ -65,11 +67,13 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -79,6 +83,7 @@
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Matchers.isNull;
+import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -132,6 +137,7 @@
         }
     }
 
+    private static final long TIMEOUT_DELAY = 5000;
     private static final Uri TEL_CALL_HANDLE = Uri.parse("tel:+11915552620");
     private static final Uri SIP_CALL_HANDLE = Uri.parse("sip:testaddress@testdomain.com");
     private static final String CALLER_NAME = "Fake Name";
@@ -139,6 +145,7 @@
     private static final String MISSED_CALLS_TITLE = "Missed Calls";
     private static final String MISSED_CALLS_MSG = "%s missed calls";
     private static final String USER_CALL_ACTIVITY_LABEL = "Phone";
+    private static final String DEFAULT_DIALER_PACKAGE = "com.android.server.telecom.test";
 
     private static final int REQUEST_ID = 0;
     private static final long CALL_TIMESTAMP;
@@ -213,6 +220,49 @@
         cancelNotificationTestInternal(SECONARY_USER);
     }
 
+    @SmallTest
+    @Test
+    public void testDefaultDialerClear() {
+        MissedCallNotifier missedCallNotifier = setupMissedCallNotificationThroughDefaultDialer();
+        missedCallNotifier.clearMissedCalls(PRIMARY_USER);
+
+        ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext).sendBroadcastAsUser(intentArgumentCaptor.capture(), any(),
+                anyString(), any());
+        Intent sentIntent = intentArgumentCaptor.getValue();
+        assertEquals(0, sentIntent.getIntExtra(TelecomManager.EXTRA_NOTIFICATION_COUNT, -1));
+    }
+
+    @SmallTest
+    @Test
+    public void testDefaultDialerIncrement() {
+        MissedCallNotifier missedCallNotifier = setupMissedCallNotificationThroughDefaultDialer();
+        PhoneAccount phoneAccount = makePhoneAccount(PRIMARY_USER, NO_CAPABILITY);
+        MissedCallNotifier.CallInfo fakeCall = makeFakeCallInfo(TEL_CALL_HANDLE, CALLER_NAME,
+                CALL_TIMESTAMP, phoneAccount.getAccountHandle());
+
+        missedCallNotifier.showMissedCallNotification(fakeCall);
+        ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext).sendBroadcastAsUser(intentArgumentCaptor.capture(), any(),
+                anyString(), any());
+
+        Intent sentIntent = intentArgumentCaptor.getValue();
+        assertEquals(1, sentIntent.getIntExtra(TelecomManager.EXTRA_NOTIFICATION_COUNT, -1));
+    }
+
+    private MissedCallNotifier setupMissedCallNotificationThroughDefaultDialer() {
+        mComponentContextFixture.addIntentReceiver(
+                TelecomManager.ACTION_SHOW_MISSED_CALLS_NOTIFICATION, COMPONENT_NAME);
+        when(mDefaultDialerCache.getDefaultDialerApplication(anyInt())).thenReturn(
+                DEFAULT_DIALER_PACKAGE);
+
+        Notification.Builder builder1 = makeNotificationBuilder("builder1");
+        Notification.Builder builder2 = makeNotificationBuilder("builder2");
+        MissedCallNotifierImpl.NotificationBuilderFactory fakeBuilderFactory =
+                makeNotificationBuilderFactory(builder1, builder1, builder2, builder2);
+        return makeMissedCallNotifier(fakeBuilderFactory, PRIMARY_USER);
+    }
+
     private void cancelNotificationTestInternal(UserHandle userHandle) {
         Notification.Builder builder1 = makeNotificationBuilder("builder1");
         Notification.Builder builder2 = makeNotificationBuilder("builder2");
@@ -400,9 +450,9 @@
                 TelecomBroadcastReceiver.class);
 
         assertNotNull(PendingIntent.getBroadcast(mContext, REQUEST_ID,
-                callBackIntent, PendingIntent.FLAG_NO_CREATE));
+                callBackIntent, PendingIntent.FLAG_NO_CREATE | PendingIntent.FLAG_IMMUTABLE));
         assertNotNull(PendingIntent.getBroadcast(mContext, REQUEST_ID,
-                smsIntent, PendingIntent.FLAG_NO_CREATE));
+                smsIntent, PendingIntent.FLAG_NO_CREATE | PendingIntent.FLAG_IMMUTABLE));
     }
 
     @SmallTest
@@ -437,9 +487,9 @@
                 TelecomBroadcastReceiver.class);
 
         assertNotNull(PendingIntent.getBroadcast(mContext, REQUEST_ID,
-                callBackIntent, PendingIntent.FLAG_NO_CREATE));
+                callBackIntent, PendingIntent.FLAG_NO_CREATE | PendingIntent.FLAG_IMMUTABLE));
         assertNull(PendingIntent.getBroadcast(mContext, REQUEST_ID,
-                smsIntent, PendingIntent.FLAG_NO_CREATE));
+                smsIntent, PendingIntent.FLAG_NO_CREATE | PendingIntent.FLAG_IMMUTABLE));
     }
 
     @SmallTest
@@ -458,7 +508,7 @@
                         CallLog.Calls.PRESENTATION_ALLOWED, CALL_TIMESTAMP)
                 .build();
 
-        when(cp.query(anyString(), nullable(String.class), eq(queryUri), nullable(String[].class),
+        when(cp.query(any(), eq(queryUri), nullable(String[].class),
                 nullable(Bundle.class), nullable(ICancellationSignal.class)))
                 .thenReturn(mockMissedCallsCursor);
 
@@ -528,7 +578,7 @@
                 PRIMARY_USER.getIdentifier());
         IContentProvider cp = getContentProviderForUser(PRIMARY_USER.getIdentifier());
 
-        when(cp.query(anyString(), nullable(String.class), eq(queryUri), nullable(String[].class),
+        when(cp.query(any(), eq(queryUri), nullable(String[].class),
                 nullable(Bundle.class), nullable(ICancellationSignal.class)))
                 .thenReturn(mockMissedCallsCursor);
 
@@ -611,7 +661,7 @@
         assertNotNull("Not expecting null options bundle", bundleCaptor.getValue());
         BroadcastOptions options = new BroadcastOptions(bundleCaptor.getValue());
         assertTrue("App must have a temporary exemption set.",
-                options.getTemporaryAppWhitelistDuration() > 0);
+                options.getTemporaryAppAllowlistDuration() > 0);
 
         // A notification should never be posted by Telecom
         verify(mNotificationManager, never()).notifyAsUser(nullable(String.class), anyInt(),
diff --git a/tests/src/com/android/server/telecom/tests/MissedInformationTest.java b/tests/src/com/android/server/telecom/tests/MissedInformationTest.java
new file mode 100644
index 0000000..a8e1c5f
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/MissedInformationTest.java
@@ -0,0 +1,257 @@
+/*
+ * 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.tests;
+
+import static android.provider.CallLog.Calls.AUTO_MISSED_EMERGENCY_CALL;
+import static android.provider.CallLog.Calls.AUTO_MISSED_MAXIMUM_DIALING;
+import static android.provider.CallLog.Calls.AUTO_MISSED_MAXIMUM_RINGING;
+import static android.provider.CallLog.Calls.MISSED_REASON_NOT_MISSED;
+import static android.provider.CallLog.Calls.USER_MISSED_CALL_FILTERS_TIMEOUT;
+import static android.provider.CallLog.Calls.USER_MISSED_CALL_SCREENING_SERVICE_SILENCED;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.IContentProvider;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.CallLog;
+import android.telecom.DisconnectCause;
+import android.telecom.TelecomManager;
+
+import com.android.server.telecom.Analytics;
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallIntentProcessor;
+import com.android.server.telecom.CallState;
+import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.TelecomSystem;
+import com.android.server.telecom.callfiltering.CallFilteringResult;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+
+import java.util.Map;
+
+public class MissedInformationTest extends TelecomSystemTest {
+    private static final int TEST_TIMEOUT_MILLIS = 1000;
+    private static final String TEST_NUMBER = "650-555-1212";
+    private static final String TEST_NUMBER_1 = "7";
+    private static final String PACKAGE_NAME = "com.android.server.telecom.tests";
+    private static final String CALL_SCREENING_SERVICE_PACKAGE_NAME = "testapp";
+    private static final String CALL_SCREENING_COMPONENT_NAME = "testapp";
+
+    @Mock ContentResolver mContentResolver;
+    @Mock IContentProvider mContentProvider;
+    @Mock Call mEmergencyCall;
+    @Mock Analytics.CallInfo mCallInfo;
+    @Mock Call mIncomingCall;
+    private CallsManager mCallsManager;
+    private CallIntentProcessor.AdapterImpl mAdapter;
+
+    @Override
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        mCallsManager = mTelecomSystem.getCallsManager();
+        mAdapter = new CallIntentProcessor.AdapterImpl(mCallsManager.getDefaultDialerCache());
+        when(mContentResolver.getPackageName()).thenReturn(PACKAGE_NAME);
+        when(mContentResolver.acquireProvider(any(String.class))).thenReturn(mContentProvider);
+        when(mContentProvider.call(any(String.class), any(String.class),
+                any(String.class), any(Bundle.class))).thenReturn(new Bundle());
+        doReturn(mContentResolver).when(mSpyContext).getContentResolver();
+    }
+
+    @Override
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    @Test
+    public void testNotMissedCall() throws Exception {
+        IdPair testCall = startAndMakeActiveIncomingCall(
+                TEST_NUMBER,
+                mPhoneAccountA0.getAccountHandle(),
+                mConnectionServiceFixtureA);
+
+        mConnectionServiceFixtureA.
+                sendSetDisconnected(testCall.mConnectionId, DisconnectCause.LOCAL);
+        ContentValues values = verifyInsertionWithCapture();
+
+        Map<String, Analytics.CallInfoImpl> analyticsMap = Analytics.cloneData();
+        Analytics.CallInfoImpl callAnalytics = analyticsMap.get(testCall.mCallId);
+        assertEquals(MISSED_REASON_NOT_MISSED, callAnalytics.missedReason);
+        assertEquals(MISSED_REASON_NOT_MISSED,
+                (long) values.getAsLong(CallLog.Calls.MISSED_REASON));
+    }
+
+    @Test
+    public void testEmergencyCallPlacing() throws Exception {
+        Analytics.dumpToParcelableAnalytics();
+        setUpEmergencyCall();
+        mCallsManager.addCall(mEmergencyCall);
+        assertTrue(mCallsManager.isInEmergencyCall());
+
+        Intent intent = new Intent();
+        intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
+               mPhoneAccountA0.getAccountHandle());
+        mAdapter.processIncomingCallIntent(mCallsManager, intent);
+
+        ContentValues values = verifyInsertionWithCapture();
+
+        Map<String, Analytics.CallInfoImpl> analyticsMap = Analytics.cloneData();
+        assertEquals(AUTO_MISSED_EMERGENCY_CALL,
+                (long) values.getAsLong(CallLog.Calls.MISSED_REASON));
+        for (Analytics.CallInfoImpl ci : analyticsMap.values()) {
+            assertEquals(AUTO_MISSED_EMERGENCY_CALL, ci.missedReason);
+        }
+    }
+
+    @Test
+    public void testMaximumDialingCalls() throws Exception {
+        Analytics.dumpToParcelableAnalytics();
+        IdPair testDialingCall = startAndMakeDialingOutgoingCall(
+                TEST_NUMBER,
+                mPhoneAccountA0.getAccountHandle(),
+                mConnectionServiceFixtureA);
+
+        Intent intent = new Intent();
+        intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
+                mPhoneAccountA0.getAccountHandle());
+        mAdapter.processIncomingCallIntent(mCallsManager, intent);
+
+        ContentValues values = verifyInsertionWithCapture();
+
+        Map<String, Analytics.CallInfoImpl> analyticsMap = Analytics.cloneData();
+        for (String callId : analyticsMap.keySet()) {
+            if (callId.equals(testDialingCall.mCallId)) {
+                continue;
+            }
+            assertEquals(AUTO_MISSED_MAXIMUM_DIALING, analyticsMap.get(callId).missedReason);
+        }
+        assertEquals(AUTO_MISSED_MAXIMUM_DIALING,
+                (long) values.getAsLong(CallLog.Calls.MISSED_REASON));
+    }
+
+    @Test
+    public void testMaximumRingingCalls() throws Exception {
+        Analytics.dumpToParcelableAnalytics();
+        IdPair testRingingCall = startAndMakeRingingIncomingCall(
+                TEST_NUMBER,
+                mPhoneAccountA0.getAccountHandle(),
+                mConnectionServiceFixtureA);
+
+        Intent intent = new Intent();
+        intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
+                mPhoneAccountA0.getAccountHandle());
+        mAdapter.processIncomingCallIntent(mCallsManager, intent);
+
+        ContentValues values = verifyInsertionWithCapture();
+
+        Map<String, Analytics.CallInfoImpl> analyticsMap = Analytics.cloneData();
+        for (String callId : analyticsMap.keySet()) {
+            if (callId.equals(testRingingCall.mCallId)) {
+                continue;
+            }
+            assertEquals(AUTO_MISSED_MAXIMUM_RINGING, analyticsMap.get(callId).missedReason);
+        }
+        assertEquals(AUTO_MISSED_MAXIMUM_RINGING,
+                (long) values.getAsLong(CallLog.Calls.MISSED_REASON));
+    }
+
+    @Test
+    public void testCallFiltersTimeout() throws Exception {
+        setUpIncomingCall();
+        CallFilteringResult result = new CallFilteringResult.Builder()
+                .setShouldAllowCall(true)
+                .build();
+        mCallsManager.onCallFilteringComplete(mIncomingCall, result, true);
+        mCallsManager.markCallAsDisconnected(mIncomingCall,
+                    new DisconnectCause(DisconnectCause.MISSED));
+        ContentValues values = verifyInsertionWithCapture();
+
+        long missedReason = values.getAsLong(CallLog.Calls.MISSED_REASON);
+        assertTrue((missedReason & USER_MISSED_CALL_FILTERS_TIMEOUT) > 0);
+        missedReason = ((Analytics.CallInfoImpl) mIncomingCall.getAnalytics()).missedReason;
+        assertTrue((missedReason & USER_MISSED_CALL_FILTERS_TIMEOUT) > 0);
+    }
+
+    @Test
+    public void testCallScreeningServiceSilence() throws Exception {
+        setUpIncomingCall();
+        CallFilteringResult result = new CallFilteringResult.Builder()
+                .setShouldAllowCall(true)
+                .setShouldSilence(true)
+                .setCallScreeningAppName(CALL_SCREENING_SERVICE_PACKAGE_NAME)
+                .setCallScreeningComponentName(CALL_SCREENING_COMPONENT_NAME)
+                .build();
+        mCallsManager.onCallFilteringComplete(mIncomingCall, result, false);
+        assertTrue(mIncomingCall.isIncoming());
+        mCallsManager.markCallAsDisconnected(mIncomingCall,
+                new DisconnectCause(DisconnectCause.MISSED));
+        ContentValues values = verifyInsertionWithCapture();
+
+        long missedReason = values.getAsLong(CallLog.Calls.MISSED_REASON);
+        assertTrue((missedReason & USER_MISSED_CALL_SCREENING_SERVICE_SILENCED) > 0);
+        assertEquals(CALL_SCREENING_COMPONENT_NAME,
+                values.getAsString(CallLog.Calls.CALL_SCREENING_COMPONENT_NAME));
+        assertEquals(CALL_SCREENING_SERVICE_PACKAGE_NAME,
+                values.getAsString(CallLog.Calls.CALL_SCREENING_APP_NAME));
+        missedReason = ((Analytics.CallInfoImpl) mIncomingCall.getAnalytics()).missedReason;
+        assertTrue((missedReason & USER_MISSED_CALL_SCREENING_SERVICE_SILENCED) > 0);
+    }
+
+    private ContentValues verifyInsertionWithCapture() {
+        ArgumentCaptor<ContentValues> captor = ArgumentCaptor.forClass(ContentValues.class);
+        verify(mContentResolver, timeout(TEST_TIMEOUT_MILLIS))
+                .insert(any(Uri.class), captor.capture());
+        return captor.getValue();
+    }
+
+    private void setUpEmergencyCall() {
+        when(mEmergencyCall.isEmergencyCall()).thenReturn(true);
+        when(mEmergencyCall.getIntentExtras()).thenReturn(new Bundle());
+        when(mEmergencyCall.getAnalytics()).thenReturn(mCallInfo);
+        when(mEmergencyCall.getState()).thenReturn(CallState.ACTIVE);
+        when(mEmergencyCall.getContext()).thenReturn(mSpyContext);
+        when(mEmergencyCall.getHandle()).thenReturn(Uri.parse("tel:" + TEST_NUMBER));
+    }
+
+    private void setUpIncomingCall() throws Exception {
+        mIncomingCall = spy(new Call("0", mSpyContext, mCallsManager,
+                (TelecomSystem.SyncRoot) mTelecomSystem.getLock(),
+                null, mCallsManager.getPhoneNumberUtilsAdapter(), null,
+                null, null, mPhoneAccountA0.getAccountHandle(),
+                Call.CALL_DIRECTION_INCOMING, false, false,
+                mClockProxy, null));
+        mIncomingCall.initAnalytics();
+        when(mIncomingCall.getIntentExtras()).thenReturn(new Bundle());
+        when(mIncomingCall.getViaNumber()).thenReturn(TEST_NUMBER);
+    }
+}
diff --git a/tests/src/com/android/server/telecom/tests/NewOutgoingCallIntentBroadcasterTest.java b/tests/src/com/android/server/telecom/tests/NewOutgoingCallIntentBroadcasterTest.java
index 9470008..e6c6bac 100644
--- a/tests/src/com/android/server/telecom/tests/NewOutgoingCallIntentBroadcasterTest.java
+++ b/tests/src/com/android/server/telecom/tests/NewOutgoingCallIntentBroadcasterTest.java
@@ -110,7 +110,7 @@
         when(mPhoneAccountRegistrar.getPhoneAccountUnchecked(
             any(PhoneAccountHandle.class))).thenReturn(mPhoneAccount);
         when(mPhoneAccount.isSelfManaged()).thenReturn(true);
-        when(mSystemStateHelper.isCarMode()).thenReturn(false);
+        when(mSystemStateHelper.isCarModeOrProjectionActive()).thenReturn(false);
     }
 
     @Override
@@ -229,7 +229,7 @@
         mComponentContextFixture.putResource(R.string.dialer_default_class,
                 dialer_default_class_string);
         when(mDefaultDialerCache.getSystemDialerApplication()).thenReturn(ui_package_string);
-        when(mDefaultDialerCache.getSystemDialerComponent()).thenReturn(
+        when(mDefaultDialerCache.getDialtactsSystemDialerComponent()).thenReturn(
                 new ComponentName(ui_package_string, dialer_default_class_string));
 
         int result = processIntent(intent, false).disconnectCause;
diff --git a/tests/src/com/android/server/telecom/tests/ParcelableCallUtilsTest.java b/tests/src/com/android/server/telecom/tests/ParcelableCallUtilsTest.java
index 6c941fe..a503283 100644
--- a/tests/src/com/android/server/telecom/tests/ParcelableCallUtilsTest.java
+++ b/tests/src/com/android/server/telecom/tests/ParcelableCallUtilsTest.java
@@ -1,13 +1,12 @@
 package com.android.server.telecom.tests;
 
-import static com.android.server.telecom.TelecomSystem.*;
+import static com.android.server.telecom.TelecomSystem.SyncRoot;
 
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.when;
 
 import android.content.ComponentName;
@@ -17,6 +16,7 @@
 import android.telecom.Connection;
 import android.telecom.ParcelableCall;
 import android.telecom.PhoneAccountHandle;
+import android.telephony.ims.ImsCallProfile;
 import android.test.suitebuilder.annotation.SmallTest;
 
 import com.android.server.telecom.Call;
@@ -26,7 +26,6 @@
 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 com.android.server.telecom.ui.ToastFactory;
 
 import org.junit.After;
@@ -98,6 +97,7 @@
 
         Bundle parceledExtras = call.getExtras();
         assertFalse(parceledExtras.containsKey(Connection.EXTRA_SIP_INVITE));
+        assertTrue(parceledExtras.containsKey(ImsCallProfile.EXTRA_IS_BUSINESS_CALL));
         assertFalse(parceledExtras.containsKey("SomeExtra"));
         assertTrue(parceledExtras.containsKey(Connection.EXTRA_CALL_SUBJECT));
     }
@@ -115,6 +115,7 @@
 
         Bundle parceledExtras = call.getExtras();
         assertTrue(parceledExtras.containsKey(Connection.EXTRA_SIP_INVITE));
+        assertTrue(parceledExtras.containsKey(ImsCallProfile.EXTRA_IS_BUSINESS_CALL));
         assertTrue(parceledExtras.containsKey("SomeExtra"));
         assertTrue(parceledExtras.containsKey(Connection.EXTRA_CALL_SUBJECT));
     }
@@ -128,6 +129,7 @@
 
         Bundle parceledExtras = call.getExtras();
         assertTrue(parceledExtras.containsKey(Connection.EXTRA_SIP_INVITE));
+        assertTrue(parceledExtras.containsKey(ImsCallProfile.EXTRA_IS_BUSINESS_CALL));
         assertFalse(parceledExtras.containsKey("SomeExtra"));
         assertFalse(parceledExtras.containsKey(Connection.EXTRA_CALL_SUBJECT));
     }
@@ -141,6 +143,7 @@
 
         Bundle parceledExtras = call.getExtras();
         assertFalse(parceledExtras.containsKey(Connection.EXTRA_SIP_INVITE));
+        assertFalse(parceledExtras.containsKey(ImsCallProfile.EXTRA_IS_BUSINESS_CALL));
         assertFalse(parceledExtras.containsKey("SomeExtra"));
         assertFalse(parceledExtras.containsKey(Connection.EXTRA_CALL_SUBJECT));
     }
@@ -192,6 +195,7 @@
         extras.putString(Connection.EXTRA_SIP_INVITE, "scary data");
         extras.putString("SomeExtra", "Extra Extra");
         extras.putString(Connection.EXTRA_CALL_SUBJECT, "Blah");
+        extras.putBoolean(ImsCallProfile.EXTRA_IS_BUSINESS_CALL, true);
         return extras;
     }
 }
diff --git a/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java b/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
index 00ef87c..a56036a 100644
--- a/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
+++ b/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
@@ -725,16 +725,20 @@
         mComponentContextFixture.addConnectionService(componentC,
                 Mockito.mock(IConnectionService.class));
 
+        Bundle account1Extras = new Bundle();
+        account1Extras.putInt(PhoneAccount.EXTRA_SORT_ORDER, 1);
         PhoneAccount account1 = new PhoneAccount.Builder(
                 makeQuickAccountHandle(componentA, "c"), "c")
                 .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
-                .setExtras(Bundle.forPair(PhoneAccount.EXTRA_SORT_ORDER, "A"))
+                .setExtras(account1Extras)
                 .build();
 
+        Bundle account2Extras = new Bundle();
+        account2Extras.putInt(PhoneAccount.EXTRA_SORT_ORDER, 2);
         PhoneAccount account2 = new PhoneAccount.Builder(
                 makeQuickAccountHandle(componentB, "b"), "b")
                 .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
-                .setExtras(Bundle.forPair(PhoneAccount.EXTRA_SORT_ORDER, "B"))
+                .setExtras(account2Extras)
                 .build();
 
         PhoneAccount account3 = new PhoneAccount.Builder(
@@ -814,18 +818,23 @@
                 Mockito.mock(IConnectionService.class));
         mComponentContextFixture.addConnectionService(componentZ,
                 Mockito.mock(IConnectionService.class));
+
+        Bundle account1Extras = new Bundle();
+        account1Extras.putInt(PhoneAccount.EXTRA_SORT_ORDER, 2);
         PhoneAccount account1 = new PhoneAccount.Builder(makeQuickAccountHandle(
                 makeQuickConnectionServiceComponentName(), "y"), "y")
                 .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER |
                         PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)
-                .setExtras(Bundle.forPair(PhoneAccount.EXTRA_SORT_ORDER, "2"))
+                .setExtras(account1Extras)
                 .build();
 
+        Bundle account2Extras = new Bundle();
+        account2Extras.putInt(PhoneAccount.EXTRA_SORT_ORDER, 1);
         PhoneAccount account2 = new PhoneAccount.Builder(makeQuickAccountHandle(
                 makeQuickConnectionServiceComponentName(), "z"), "z")
                 .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER |
                         PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)
-                .setExtras(Bundle.forPair(PhoneAccount.EXTRA_SORT_ORDER, "1"))
+                .setExtras(account2Extras)
                 .build();
 
         PhoneAccount account3 = new PhoneAccount.Builder(makeQuickAccountHandle(
diff --git a/tests/src/com/android/server/telecom/tests/RingerTest.java b/tests/src/com/android/server/telecom/tests/RingerTest.java
index 38f63d2..0e93481 100644
--- a/tests/src/com/android/server/telecom/tests/RingerTest.java
+++ b/tests/src/com/android/server/telecom/tests/RingerTest.java
@@ -79,6 +79,16 @@
         }
 
         @Override
+        public VibrationEffect resolve(int defaultAmplitude) {
+            return this;
+        }
+
+        @Override
+        public VibrationEffect scale(float scaleFactor) {
+            return this;
+        }
+
+        @Override
         public void validate() {
             // not needed
         }
diff --git a/tests/src/com/android/server/telecom/tests/SessionTest.java b/tests/src/com/android/server/telecom/tests/SessionTest.java
index 6a14a64..4be3dad 100644
--- a/tests/src/com/android/server/telecom/tests/SessionTest.java
+++ b/tests/src/com/android/server/telecom/tests/SessionTest.java
@@ -173,6 +173,36 @@
     }
 
     /**
+     * Ensure creating two sessions and setting the child as the parent to itself doesn't cause a
+     * crash due to infinite recursion.
+     */
+    @SmallTest
+    @Test
+    public void testRecursion_toString_childCircDep() {
+        Log.startSession("testParent");
+        // Running in the same thread, so mark as invisible subsession
+        Session childSession = Log.getSessionManager()
+                .createSubsession(true /*isStartedFromActiveSession*/);
+        Log.continueSession(childSession, "child");
+        Session parentSession = childSession.getParentSession();
+        // Create a circular dependency and ensure we do not crash
+        childSession.setParentSession(childSession);
+
+        // Make sure calling these methods does not result in a crash
+        try {
+            parentSession.toString();
+            childSession.toString();
+        } catch (Exception e) {
+            fail("Exception: " + e.getMessage());
+        } finally {
+            // End child
+            Log.endSession();
+            // End parent
+            Log.endSession();
+        }
+    }
+
+    /**
      * Ensure creating two sessions that are parent/child of each other does not lead to a crash
      * or infinite recursion when using Session#getInfo.
      */
diff --git a/tests/src/com/android/server/telecom/tests/SystemStateHelperTest.java b/tests/src/com/android/server/telecom/tests/SystemStateHelperTest.java
index 2de3c83..dc7d1fd 100644
--- a/tests/src/com/android/server/telecom/tests/SystemStateHelperTest.java
+++ b/tests/src/com/android/server/telecom/tests/SystemStateHelperTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.telecom.tests;
 
+import static junit.framework.Assert.fail;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -23,9 +25,11 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -39,10 +43,12 @@
 import android.hardware.SensorEvent;
 import android.hardware.SensorEventListener;
 import android.hardware.SensorManager;
+import android.net.Uri;
 import android.test.suitebuilder.annotation.SmallTest;
 
 import com.android.server.telecom.SystemStateHelper;
 import com.android.server.telecom.SystemStateHelper.SystemStateListener;
+import com.android.server.telecom.TelecomSystem;
 
 import org.junit.After;
 import org.junit.Before;
@@ -53,11 +59,11 @@
 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;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
 
 /**
  * Unit tests for SystemStateHelper
@@ -73,6 +79,7 @@
     @Mock SensorManager mSensorManager;
     @Mock Intent mIntentEnter;
     @Mock Intent mIntentExit;
+    TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() { };
 
     @Override
     @Before
@@ -87,6 +94,8 @@
         when(mSensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY)).thenReturn(mGravitySensor);
         when(mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY)).thenReturn(mProxSensor);
 
+        doReturn(mUiModeManager).when(mContext).getSystemService(UiModeManager.class);
+
         mComponentContextFixture.putFloatResource(
                 R.dimen.device_on_ear_xy_gravity_threshold, 5.5f);
         mComponentContextFixture.putFloatResource(
@@ -102,7 +111,7 @@
     @SmallTest
     @Test
     public void testListeners() throws Exception {
-        SystemStateHelper systemStateHelper = new SystemStateHelper(mContext);
+        SystemStateHelper systemStateHelper = new SystemStateHelper(mContext, mLock);
 
         assertFalse(systemStateHelper.removeListener(mSystemStateListener));
         systemStateHelper.addListener(mSystemStateListener);
@@ -113,31 +122,104 @@
     @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());
+        assertTrue(new SystemStateHelper(mContext, mLock).isCarModeOrProjectionActive());
     }
 
     @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());
+        assertFalse(new SystemStateHelper(mContext, mLock).isCarModeOrProjectionActive());
+    }
+
+    @SmallTest
+    @Test
+    public void testQuerySystemForAutomotiveProjection_True() {
+        when(mUiModeManager.getActiveProjectionTypes())
+                .thenReturn(UiModeManager.PROJECTION_TYPE_AUTOMOTIVE);
+        assertTrue(new SystemStateHelper(mContext, mLock).isCarModeOrProjectionActive());
+
+        when(mUiModeManager.getActiveProjectionTypes())
+                .thenReturn(UiModeManager.PROJECTION_TYPE_ALL);
+        assertTrue(new SystemStateHelper(mContext, mLock).isCarModeOrProjectionActive());
+    }
+
+    @SmallTest
+    @Test
+    public void testQuerySystemForAutomotiveProjection_False() {
+        when(mUiModeManager.getActiveProjectionTypes())
+                .thenReturn(UiModeManager.PROJECTION_TYPE_NONE);
+        assertFalse(new SystemStateHelper(mContext, mLock).isCarModeOrProjectionActive());
+    }
+
+    @SmallTest
+    @Test
+    public void testQuerySystemForAutomotiveProjectionAndCarMode_True() {
+        when(mUiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_CAR);
+        when(mUiModeManager.getActiveProjectionTypes())
+                .thenReturn(UiModeManager.PROJECTION_TYPE_AUTOMOTIVE);
+        assertTrue(new SystemStateHelper(mContext, mLock).isCarModeOrProjectionActive());
+    }
+
+    @SmallTest
+    @Test
+    public void testQuerySystemForAutomotiveProjectionOrCarMode_nullService() {
+        when(mContext.getSystemService(UiModeManager.class))
+                .thenReturn(mUiModeManager)  // Without this, class construction will throw NPE.
+                .thenReturn(null);
+        assertFalse(new SystemStateHelper(mContext, mLock).isCarModeOrProjectionActive());
+    }
+
+    @SmallTest
+    @Test
+    public void testPackageRemoved() {
+        ArgumentCaptor<BroadcastReceiver> receiver =
+                ArgumentCaptor.forClass(BroadcastReceiver.class);
+        new SystemStateHelper(mContext, mLock).addListener(mSystemStateListener);
+        verify(mContext, atLeastOnce())
+                .registerReceiver(receiver.capture(), any(IntentFilter.class));
+        Intent packageRemovedIntent = new Intent(Intent.ACTION_PACKAGE_REMOVED);
+        packageRemovedIntent.setData(Uri.fromParts("package", "com.android.test", null));
+        receiver.getValue().onReceive(mContext, packageRemovedIntent);
+        verify(mSystemStateListener).onPackageUninstalled("com.android.test");
     }
 
     @SmallTest
     @Test
     public void testReceiverAndIntentFilter() {
-        ArgumentCaptor<IntentFilter> intentFilter = ArgumentCaptor.forClass(IntentFilter.class);
-        new SystemStateHelper(mContext);
-        verify(mContext).registerReceiver(any(BroadcastReceiver.class), intentFilter.capture());
+        ArgumentCaptor<IntentFilter> intentFilterCaptor =
+                ArgumentCaptor.forClass(IntentFilter.class);
+        new SystemStateHelper(mContext, mLock);
+        verify(mContext, times(2)).registerReceiver(
+                any(BroadcastReceiver.class), intentFilterCaptor.capture());
 
-        assertEquals(2, intentFilter.getValue().countActions());
-        assertEquals(UiModeManager.ACTION_ENTER_CAR_MODE_PRIORITIZED,
-                intentFilter.getValue().getAction(0));
-        assertEquals(UiModeManager.ACTION_EXIT_CAR_MODE_PRIORITIZED,
-                intentFilter.getValue().getAction(1));
+        Predicate<IntentFilter> carModeFilterTest = (intentFilter) ->
+                2 == intentFilter.countActions()
+                        && intentFilter.hasAction(UiModeManager.ACTION_ENTER_CAR_MODE_PRIORITIZED)
+                        && intentFilter.hasAction(UiModeManager.ACTION_EXIT_CAR_MODE_PRIORITIZED);
+
+        Predicate<IntentFilter> packageRemovedFilterTest = (intentFilter) ->
+                1 == intentFilter.countActions()
+                        && intentFilter.hasAction(Intent.ACTION_PACKAGE_REMOVED)
+                        && intentFilter.hasDataScheme("package");
+
+        List<IntentFilter> capturedFilters = intentFilterCaptor.getAllValues();
+        assertEquals(2, capturedFilters.size());
+        for (IntentFilter filter : capturedFilters) {
+            if (carModeFilterTest.test(filter)) {
+                carModeFilterTest = (i) -> false;
+                continue;
+            }
+            if (packageRemovedFilterTest.test(filter)) {
+                packageRemovedFilterTest = (i) -> false;
+                continue;
+            }
+            String failString = String.format("Registered intent filters not correct. Got %s",
+                    capturedFilters.stream().map(IntentFilter::toString)
+                            .collect(Collectors.joining("\n")));
+            fail(failString);
+        }
     }
 
     @SmallTest
@@ -145,9 +227,10 @@
     public void testOnEnterExitCarMode() {
         ArgumentCaptor<BroadcastReceiver> receiver =
                 ArgumentCaptor.forClass(BroadcastReceiver.class);
-        new SystemStateHelper(mContext).addListener(mSystemStateListener);
+        new SystemStateHelper(mContext, mLock).addListener(mSystemStateListener);
 
-        verify(mContext).registerReceiver(receiver.capture(), any(IntentFilter.class));
+        verify(mContext, atLeastOnce())
+                .registerReceiver(receiver.capture(), any(IntentFilter.class));
 
         when(mIntentEnter.getAction()).thenReturn(UiModeManager.ACTION_ENTER_CAR_MODE_PRIORITIZED);
         receiver.getValue().onReceive(mContext, mIntentEnter);
@@ -162,6 +245,40 @@
 
     @SmallTest
     @Test
+    public void testOnSetReleaseAutomotiveProjection() {
+        SystemStateHelper systemStateHelper = new SystemStateHelper(mContext, mLock);
+        // We don't care what listener is registered, that's an implementation detail, but we need
+        // to call methods on whatever it is.
+        ArgumentCaptor<UiModeManager.OnProjectionStateChangedListener> listenerCaptor =
+                ArgumentCaptor.forClass(UiModeManager.OnProjectionStateChangedListener.class);
+        verify(mUiModeManager).addOnProjectionStateChangedListener(
+                eq(UiModeManager.PROJECTION_TYPE_AUTOMOTIVE), any(), listenerCaptor.capture());
+        systemStateHelper.addListener(mSystemStateListener);
+
+        String packageName1 = "Sufjan Stevens";
+        String packageName2 = "The Ascension";
+
+        // Should pay attention to automotive projection, though.
+        listenerCaptor.getValue().onProjectionStateChanged(
+                UiModeManager.PROJECTION_TYPE_AUTOMOTIVE, Set.of(packageName2));
+        verify(mSystemStateListener).onAutomotiveProjectionStateSet(packageName2);
+
+        // Without any automotive projection, it should see it as released.
+        listenerCaptor.getValue().onProjectionStateChanged(
+                UiModeManager.PROJECTION_TYPE_NONE, Set.of());
+        verify(mSystemStateListener).onAutomotiveProjectionStateReleased();
+
+        // Try the whole thing again, with different values.
+        listenerCaptor.getValue().onProjectionStateChanged(
+                UiModeManager.PROJECTION_TYPE_AUTOMOTIVE, Set.of(packageName1));
+        verify(mSystemStateListener).onAutomotiveProjectionStateSet(packageName1);
+        listenerCaptor.getValue().onProjectionStateChanged(
+                UiModeManager.PROJECTION_TYPE_AUTOMOTIVE, Set.of());
+        verify(mSystemStateListener, times(2)).onAutomotiveProjectionStateReleased();
+    }
+
+    @SmallTest
+    @Test
     public void testDeviceOnEarCorrectlyDetected() {
         doAnswer(invocation -> {
             SensorEventListener listener = invocation.getArgument(0);
diff --git a/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java b/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
index 0dfe29a..3cec50b 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
@@ -460,10 +460,10 @@
     @Test
     public void testGetPhoneAccount() throws RemoteException {
         makeAccountsVisibleToAllUsers(TEL_PA_HANDLE_16, SIP_PA_HANDLE_17);
-        assertEquals(TEL_PA_HANDLE_16, mTSIBinder.getPhoneAccount(TEL_PA_HANDLE_16)
-                .getAccountHandle());
-        assertEquals(SIP_PA_HANDLE_17, mTSIBinder.getPhoneAccount(SIP_PA_HANDLE_17)
-                .getAccountHandle());
+        assertEquals(TEL_PA_HANDLE_16, mTSIBinder.getPhoneAccount(TEL_PA_HANDLE_16,
+                mContext.getPackageName()).getAccountHandle());
+        assertEquals(SIP_PA_HANDLE_17, mTSIBinder.getPhoneAccount(SIP_PA_HANDLE_17,
+                mContext.getPackageName()).getAccountHandle());
     }
 
     @SmallTest
@@ -1202,6 +1202,39 @@
     }
 
     /**
+     * Ensure self-managed calls cannot be ended using {@link TelecomManager#endCall()}.
+     * @throws Exception
+     */
+    @SmallTest
+    @Test
+    public void testCannotEndSelfManagedCall() throws Exception {
+        Call call = mock(Call.class);
+        when(call.isSelfManaged()).thenReturn(true);
+        when(call.getState()).thenReturn(CallState.ACTIVE);
+        when(mFakeCallsManager.getFirstCallWithState(any()))
+                .thenReturn(call);
+        assertFalse(mTSIBinder.endCall(TEST_PACKAGE));
+        verify(mFakeCallsManager, never()).disconnectCall(eq(call));
+    }
+
+    /**
+     * Ensure self-managed calls cannot be answered using {@link TelecomManager#acceptRingingCall()}
+     * or {@link TelecomManager#acceptRingingCall(int)}.
+     * @throws Exception
+     */
+    @SmallTest
+    @Test
+    public void testCannotAnswerSelfManagedCall() throws Exception {
+        Call call = mock(Call.class);
+        when(call.isSelfManaged()).thenReturn(true);
+        when(call.getState()).thenReturn(CallState.ACTIVE);
+        when(mFakeCallsManager.getFirstCallWithState(any()))
+                .thenReturn(call);
+        mTSIBinder.acceptRingingCall(TEST_PACKAGE);
+        verify(mFakeCallsManager, never()).answerCall(eq(call), anyInt());
+    }
+
+    /**
      * Register phone accounts for the supplied PhoneAccountHandles to make them
      * visible to all users (via the isVisibleToCaller method in TelecomServiceImpl.
      * @param handles the handles for which phone accounts should be created for.
diff --git a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
index 68b10c6..7f462d4 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
@@ -69,7 +69,6 @@
 
 import com.android.internal.telecom.IInCallAdapter;
 import com.android.server.telecom.AsyncRingtonePlayer;
-import com.android.server.telecom.BluetoothPhoneServiceImpl;
 import com.android.server.telecom.CallAudioManager;
 import com.android.server.telecom.CallAudioModeStateMachine;
 import com.android.server.telecom.CallAudioRouteStateMachine;
@@ -96,8 +95,6 @@
 import com.android.server.telecom.Timeouts;
 import com.android.server.telecom.WiredHeadsetManager;
 import com.android.server.telecom.bluetooth.BluetoothRouteManager;
-import com.android.server.telecom.callfiltering.CallFilterResultCallback;
-import com.android.server.telecom.callfiltering.IncomingCallFilter;
 import com.android.server.telecom.components.UserCallIntentProcessor;
 import com.android.server.telecom.ui.IncomingCallNotifier;
 
@@ -203,7 +200,6 @@
     @Mock HeadsetMediaButton mHeadsetMediaButton;
     @Mock ProximitySensorManager mProximitySensorManager;
     @Mock InCallWakeLockController mInCallWakeLockController;
-    @Mock BluetoothPhoneServiceImpl mBluetoothPhoneServiceImpl;
     @Mock AsyncRingtonePlayer mAsyncRingtonePlayer;
     @Mock IncomingCallNotifier mIncomingCallNotifier;
     @Mock ClockProxy mClockProxy;
@@ -369,7 +365,7 @@
 
         // Next, create the TelecomSystem, our system under test
         setupTelecomSystem();
-        // Need to reset teseting tag here
+        // Need to reset testing tag here
         Log.setTag(TESTING_TAG);
 
         // Finally, register the ConnectionServices with the PhoneAccountRegistrar of the
@@ -405,11 +401,9 @@
         mConnectionServiceFixtureA.waitForHandlerToClear();
         mConnectionServiceFixtureB.waitForHandlerToClear();
 
-        // Print out any incomplete sessions for debugging tests
-        String sessions = Log.getSessionManager().printActiveSessions();
-        if (!TextUtils.isEmpty(sessions)) {
-            Log.w(this, "Active Sessions:\n" + sessions);
-        }
+        // Forcefully clean all sessions at the end of the test, which will also log any stale
+        // sessions for debugging.
+        Log.getSessionManager().cleanupStaleSessions(0);
 
         mTelecomSystem = null;
         super.tearDown();
@@ -487,7 +481,6 @@
                 proximitySensorManagerFactory,
                 inCallWakeLockControllerFactory,
                 () -> mAudioService,
-                (context, lock, callsManager, phoneAccountRegistrar) -> mBluetoothPhoneServiceImpl,
                 mConnServFMFactory,
                 mTimeoutsAdapter,
                 mAsyncRingtonePlayer,
@@ -525,16 +518,6 @@
                 },
                 mClockProxy,
                 mRoleManagerAdapter,
-                new IncomingCallFilter.Factory() {
-                    @Override
-                    public IncomingCallFilter create(Context context,
-                            CallFilterResultCallback listener, com.android.server.telecom.Call call,
-                            TelecomSystem.SyncRoot lock, Timeouts.Adapter timeoutsAdapter,
-                            List<IncomingCallFilter.CallFilter> filters) {
-                        return new IncomingCallFilter(context, listener, call, lock,
-                                timeoutsAdapter, filters, mHandlerThread.getThreadHandler());
-                    }
-                },
                 new ContactsAsyncHelper.Factory() {
                     @Override
                     public ContactsAsyncHelper create(
@@ -931,7 +914,12 @@
         // Wait for the handler to start the CallerInfo lookup
         waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
 
+        // Wait a few more times to address flakiness due to timing issues.
+        waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
+        waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
+
         // Ensure callback to CS on successful creation happened.
+
         verify(connectionServiceFixture.getTestDouble(), timeout(TEST_TIMEOUT))
                 .createConnectionComplete(anyString(), any());
 
@@ -1117,6 +1105,54 @@
         return ids;
     }
 
+    protected IdPair startAndMakeDialingOutgoingCall(
+            String number,
+            PhoneAccountHandle phoneAccountHandle,
+            ConnectionServiceFixture connectionServiceFixture) throws Exception {
+        IdPair ids = startOutgoingPhoneCall(number, phoneAccountHandle, connectionServiceFixture,
+                Process.myUserHandle(), VideoProfile.STATE_AUDIO_ONLY);
+
+        connectionServiceFixture.sendSetDialing(ids.mConnectionId);
+        if (phoneAccountHandle != mPhoneAccountSelfManaged.getAccountHandle()) {
+            assertEquals(Call.STATE_DIALING,
+                    mInCallServiceFixtureX.getCall(ids.mCallId).getState());
+            assertEquals(Call.STATE_DIALING,
+                    mInCallServiceFixtureY.getCall(ids.mCallId).getState());
+        }
+
+        return ids;
+    }
+
+    protected IdPair startAndMakeRingingIncomingCall(
+            String number,
+            PhoneAccountHandle phoneAccountHandle,
+            ConnectionServiceFixture connectionServiceFixture) throws Exception {
+        IdPair ids = startIncomingPhoneCall(number, phoneAccountHandle, connectionServiceFixture);
+
+        if (phoneAccountHandle != mPhoneAccountSelfManaged.getAccountHandle()) {
+            assertEquals(Call.STATE_RINGING,
+                    mInCallServiceFixtureX.getCall(ids.mCallId).getState());
+            assertEquals(Call.STATE_RINGING,
+                    mInCallServiceFixtureY.getCall(ids.mCallId).getState());
+
+            mInCallServiceFixtureX.mInCallAdapter
+                    .answerCall(ids.mCallId, VideoProfile.STATE_AUDIO_ONLY);
+
+            waitForHandlerAction(mTelecomSystem.getCallsManager()
+                    .getConnectionServiceFocusManager().getHandler(), TEST_TIMEOUT);
+
+            if (!VideoProfile.isVideo(VideoProfile.STATE_AUDIO_ONLY)) {
+                verify(connectionServiceFixture.getTestDouble(), timeout(TEST_TIMEOUT))
+                        .answer(eq(ids.mConnectionId), any());
+            } else {
+                verify(connectionServiceFixture.getTestDouble(), timeout(TEST_TIMEOUT))
+                        .answerVideo(eq(ids.mConnectionId), eq(VideoProfile.STATE_AUDIO_ONLY),
+                                any());
+            }
+        }
+        return ids;
+    }
+
     protected static void assertTrueWithTimeout(Predicate<Void> predicate) {
         int elapsed = 0;
         while (elapsed < TEST_TIMEOUT) {