Merge "Fix text color of the add block number button." into main
diff --git a/Android.bp b/Android.bp
index 1b422aa..f46c206 100644
--- a/Android.bp
+++ b/Android.bp
@@ -27,6 +27,11 @@
     ],
     static_libs: [
         "androidx.annotation_annotation",
+        "androidx.core_core",
+        "telecom_flags_core_java_lib",
+    ],
+    libs: [
+        "services",
     ],
     resource_dirs: ["res"],
     proto: {
@@ -46,6 +51,7 @@
     name: "TelecomUnitTests",
     static_libs: [
         "android-ex-camera2",
+        "flag-junit",
         "guava",
         "mockito-target-extended",
         "androidx.test.rules",
@@ -56,6 +62,7 @@
         "androidx.fragment_fragment",
         "androidx.test.ext.junit",
         "platform-compat-test-rules",
+        "telecom_flags_core_java_lib",
     ],
     srcs: [
         "tests/src/**/*.java",
@@ -94,8 +101,8 @@
     platform_apis: true,
     certificate: "platform",
     jacoco: {
-        include_filter: ["com.android.server.telecom.*"],
-        exclude_filter: ["com.android.server.telecom.tests.*"],
+        include_filter: ["com.android.server.telecom.**"],
+        exclude_filter: ["com.android.server.telecom.tests.**"],
     },
     test_suites: ["device-tests"],
     defaults: ["SettingsLibDefaults"],
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 648b4a9..c6f5e9c 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -28,7 +28,6 @@
     <!-- 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"/>
@@ -63,7 +62,10 @@
     <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.POST_NOTIFICATIONS"/>
+    <uses-permission android:name="android.permission.USE_COLORIZED_NOTIFICATIONS"/>
+    <uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT"/>
     <uses-permission android:name="com.android.phone.permission.ACCESS_LAST_KNOWN_CELL_ID"/>
+    <uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
 
     <permission android:name="android.permission.BROADCAST_CALLLOG_INFO"
          android:label="Broadcast the call type/duration information"
@@ -316,18 +318,6 @@
              android:exported="false"
              android:process=":ui"/>
 
-        <service android:name=".components.BluetoothPhoneService"
-             android:singleUser="true"
-             android:process="system"
-             android:exported="true">
-            <intent-filter>
-                <action android:name="android.bluetooth.IBluetoothHeadsetPhone"/>
-            </intent-filter>
-            <intent-filter>
-                <action android:name="android.bluetooth.IBluetoothLeCallControlCallback" />
-            </intent-filter>
-        </service>
-
         <service android:name=".components.TelecomService"
              android:singleUser="true"
              android:process="system"
diff --git a/OWNERS b/OWNERS
index 97cc81f..7e68aea 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,7 +1,6 @@
 breadley@google.com
 tgunn@google.com
 xiaotonj@google.com
-chinmayd@google.com
 tjstuart@google.com
 rgreenwalt@google.com
 pmadapurmath@google.com
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 9874044..489fab7 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -1,2 +1,3 @@
-[Hook Scripts]
-aosp_hook = ${REPO_ROOT}/packages/services/Telecomm/scripts/aosp_tag_preupload.py ${PREUPLOAD_COMMIT}
+# Uncomment to re-enable aosp warning.
+#[Hook Scripts]
+#aosp_hook = ${REPO_ROOT}/packages/services/Telecomm/scripts/aosp_tag_preupload.py ${PREUPLOAD_COMMIT}
diff --git a/flags/Android.bp b/flags/Android.bp
new file mode 100644
index 0000000..386831c
--- /dev/null
+++ b/flags/Android.bp
@@ -0,0 +1,43 @@
+//
+// Copyright (C) 2023 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"],
+}
+
+aconfig_declarations {
+    name: "telecom_flags",
+    package: "com.android.server.telecom.flags",
+    srcs: [
+      "telecom_broadcast_flags.aconfig",
+      "telecom_ringer_flag_declarations.aconfig",
+      "telecom_api_flags.aconfig",
+      "telecom_call_filtering_flags.aconfig",
+      "telecom_incallservice_flags.aconfig",
+      "telecom_default_phone_account_flags.aconfig",
+      "telecom_callaudioroutestatemachine_flags.aconfig",
+      "telecom_call_flags.aconfig",
+      "telecom_calls_manager_flags.aconfig",
+      "telecom_anomaly_report_flags.aconfig",
+      "telecom_callaudiomodestatemachine_flags.aconfig",
+      "telecom_calllog_flags.aconfig",
+      "telecom_resolve_hidden_dependencies.aconfig",
+      "telecom_bluetoothroutemanager_flags.aconfig",
+      "telecom_work_profile_flags.aconfig",
+      "telecom_connection_service_wrapper_flags.aconfig",
+    ],
+}
+
diff --git a/flags/telecom_anomaly_report_flags.aconfig b/flags/telecom_anomaly_report_flags.aconfig
new file mode 100644
index 0000000..dbacc08
--- /dev/null
+++ b/flags/telecom_anomaly_report_flags.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.server.telecom.flags"
+
+flag {
+  name: "gen_anom_report_on_focus_timeout"
+  namespace: "telecom"
+  description: "When getCurrentFocusCall times out, generate an anom. report"
+  bug: "309541253"
+}
diff --git a/flags/telecom_api_flags.aconfig b/flags/telecom_api_flags.aconfig
new file mode 100644
index 0000000..74cb447
--- /dev/null
+++ b/flags/telecom_api_flags.aconfig
@@ -0,0 +1,29 @@
+package: "com.android.server.telecom.flags"
+
+flag {
+  name: "voip_app_actions_support"
+  namespace: "telecom"
+  description: "When set, Telecom support for additional VOIP application actions is active."
+  bug: "296934278"
+}
+
+flag {
+  name: "call_details_id_changes"
+  namespace: "telecom"
+  description: "When set, call details/extras id updates to Telecom APIs for Android V are active."
+  bug: "301713560"
+}
+
+flag {
+  name: "unbind_timeout_connections"
+  namespace: "telecom"
+  description: "When set, Telecom will auto-unbind if a ConnectionService returns no connections after some time."
+  bug: "293458004"
+}
+
+flag{
+  name: "add_call_uri_for_missed_calls"
+  namespace: "telecom"
+  description: "The key is used for dialer apps to mark missed calls as read when it gets the notification on reboot."
+  bug: "292597423"
+}
diff --git a/flags/telecom_bluetoothroutemanager_flags.aconfig b/flags/telecom_bluetoothroutemanager_flags.aconfig
new file mode 100644
index 0000000..ddd8571
--- /dev/null
+++ b/flags/telecom_bluetoothroutemanager_flags.aconfig
@@ -0,0 +1,9 @@
+package: "com.android.server.telecom.flags"
+
+flag {
+  name: "use_actual_address_to_enter_connecting_state"
+  namespace: "telecom"
+  description: "Fix bugs that may add bluetooth device with null address."
+  bug: "306113816"
+}
+
diff --git a/flags/telecom_broadcast_flags.aconfig b/flags/telecom_broadcast_flags.aconfig
new file mode 100644
index 0000000..348d574
--- /dev/null
+++ b/flags/telecom_broadcast_flags.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.server.telecom.flags"
+
+flag {
+  name: "is_new_outgoing_call_broadcast_unblocking"
+  namespace: "telecom"
+  description: "When set, the ACTION_NEW_OUTGOING_CALL broadcast is unblocking."
+  bug: "224550864"
+}
\ No newline at end of file
diff --git a/flags/telecom_call_filtering_flags.aconfig b/flags/telecom_call_filtering_flags.aconfig
new file mode 100644
index 0000000..95e74ce
--- /dev/null
+++ b/flags/telecom_call_filtering_flags.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.server.telecom.flags"
+
+flag {
+  name: "skip_filter_phone_account_perform_dnd_filter"
+  namespace: "telecom"
+  description: "Gates whether to still perform Dnd filter when phone account has skip_filter call extra."
+  bug: "222333869"
+}
\ No newline at end of file
diff --git a/flags/telecom_call_flags.aconfig b/flags/telecom_call_flags.aconfig
new file mode 100644
index 0000000..b5ea6a2
--- /dev/null
+++ b/flags/telecom_call_flags.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.server.telecom.flags"
+
+flag {
+  name: "transactional_cs_verifier"
+  namespace: "telecom"
+  description: "verify connection service callbacks via a transaction"
+  bug: "309541257"
+}
\ No newline at end of file
diff --git a/flags/telecom_callaudiomodestatemachine_flags.aconfig b/flags/telecom_callaudiomodestatemachine_flags.aconfig
new file mode 100644
index 0000000..b263113
--- /dev/null
+++ b/flags/telecom_callaudiomodestatemachine_flags.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.server.telecom.flags"
+
+flag {
+  name: "set_audio_mode_before_abandon_focus"
+  namespace: "telecom"
+  description: "Set audio mode to MODE_NORMAL before abandon the audio focus."
+  bug: "281841785"
+}
diff --git a/flags/telecom_callaudioroutestatemachine_flags.aconfig b/flags/telecom_callaudioroutestatemachine_flags.aconfig
new file mode 100644
index 0000000..6f2c7fc
--- /dev/null
+++ b/flags/telecom_callaudioroutestatemachine_flags.aconfig
@@ -0,0 +1,64 @@
+package: "com.android.server.telecom.flags"
+
+flag {
+  name: "available_routes_never_updated_after_set_system_audio_state"
+  namespace: "telecom"
+  description: "Fix supported routes wrongly include bluetooth issue."
+  bug: "292599751"
+}
+
+flag {
+  name: "use_refactored_audio_route_switching"
+  namespace: "telecom"
+  description: "Refactored audio routing"
+  bug: "306395598"
+}
+
+flag {
+  name: "ensure_audio_mode_updates_on_foreground_call_change"
+  namespace: "telecom"
+  description: "Ensure that the audio mode is updated anytime the foreground call changes."
+  bug: "289861657"
+}
+
+flag {
+  name: "ignore_auto_route_to_watch_device"
+  namespace: "telecom"
+  description: "Ignore auto routing to wearable devices."
+  bug: "294378768"
+}
+
+flag {
+  name: "transit_route_before_audio_disconnect_bt"
+  namespace: "telecom"
+  description: "Fix audio route transition issue on call disconnection when bt audio connected."
+  bug: "306113816"
+}
+
+flag {
+  name: "call_audio_communication_device_refactor"
+  namespace: "telecom"
+  description: "Refactor call audio set/clear communication device and include unsupported routes."
+  bug: "308968392"
+}
+
+flag {
+  name: "communication_device_protected_by_lock"
+  namespace: "telecom"
+  description: "Protect set/clear communication device operation with lock to avoid race condition."
+  bug: "303001133"
+}
+
+flag {
+  name: "reset_mute_when_entering_quiescent_bt_route"
+  namespace: "telecom"
+  description: "Reset mute state when entering quiescent bluetooth route."
+  bug: "311313250"
+}
+
+flag {
+  name: "update_route_mask_when_bt_connected"
+  namespace: "telecom"
+  description: "Update supported route mask when Bluetooth devices audio connected."
+  bug: "301695370"
+}
diff --git a/flags/telecom_calllog_flags.aconfig b/flags/telecom_calllog_flags.aconfig
new file mode 100644
index 0000000..3ce7b63
--- /dev/null
+++ b/flags/telecom_calllog_flags.aconfig
@@ -0,0 +1,15 @@
+package: "com.android.server.telecom.flags"
+
+flag {
+  name: "telecom_log_external_wearable_calls"
+  namespace: "telecom"
+  description: "log external call if current device is a wearable one"
+  bug: "292600751"
+}
+
+flag {
+  name: "telecom_skip_log_based_on_extra"
+  namespace: "telecom"
+  description: "skipping logging a call based on passed extra"
+  bug: "295530944"
+}
diff --git a/flags/telecom_calls_manager_flags.aconfig b/flags/telecom_calls_manager_flags.aconfig
new file mode 100644
index 0000000..1a19480
--- /dev/null
+++ b/flags/telecom_calls_manager_flags.aconfig
@@ -0,0 +1,15 @@
+package: "com.android.server.telecom.flags"
+
+flag {
+  name: "use_improved_listener_order"
+  namespace: "telecom"
+  description: "Make InCallController the first listener to trigger"
+  bug: "24244713"
+}
+
+flag {
+  name: "fix_audio_flicker_for_outgoing_calls"
+  namespace: "telecom"
+  description: "This fix ensures the MO calls won't switch from Active to Quite b/c setDialing was not called"
+  bug: "309540769"
+}
diff --git a/flags/telecom_connection_service_wrapper_flags.aconfig b/flags/telecom_connection_service_wrapper_flags.aconfig
new file mode 100644
index 0000000..5f46c27
--- /dev/null
+++ b/flags/telecom_connection_service_wrapper_flags.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.server.telecom.flags"
+
+flag {
+  name: "updated_rcs_call_count_tracking"
+  namespace: "telecom"
+  description: "Ensure that the associatedCallCount of CS and RCS is accurately being tracked."
+  bug: "286154316"
+}
\ No newline at end of file
diff --git a/flags/telecom_default_phone_account_flags.aconfig b/flags/telecom_default_phone_account_flags.aconfig
new file mode 100644
index 0000000..03f324c
--- /dev/null
+++ b/flags/telecom_default_phone_account_flags.aconfig
@@ -0,0 +1,15 @@
+package: "com.android.server.telecom.flags"
+
+flag {
+  name: "only_update_telephony_on_valid_sub_ids"
+  namespace: "telecom"
+  description: "For testing purposes, only update Telephony when the default calling subId is non-zero"
+  bug: "234846282"
+}
+
+flag {
+  name: "telephony_has_default_but_telecom_does_not"
+  namespace: "telecom"
+  description: "Telecom is requesting the user to select a sim account to place the outgoing call on but the user has a default account in the settings"
+  bug: "302397094"
+}
\ No newline at end of file
diff --git a/flags/telecom_incallservice_flags.aconfig b/flags/telecom_incallservice_flags.aconfig
new file mode 100644
index 0000000..e1a652b
--- /dev/null
+++ b/flags/telecom_incallservice_flags.aconfig
@@ -0,0 +1,15 @@
+package: "com.android.server.telecom.flags"
+
+flag {
+  name: "early_binding_to_incall_service"
+  namespace: "telecom"
+  description: "Binds to InCallServices when call requires no call filtering on watch"
+  bug: "282113261"
+}
+
+flag {
+  name: "ecc_keyguard"
+  namespace: "telecom"
+  description: "Ensure that users are able to return to call from keyguard UI for ECC"
+  bug: "306582821"
+}
\ No newline at end of file
diff --git a/flags/telecom_resolve_hidden_dependencies.aconfig b/flags/telecom_resolve_hidden_dependencies.aconfig
new file mode 100644
index 0000000..ecc0123
--- /dev/null
+++ b/flags/telecom_resolve_hidden_dependencies.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.server.telecom.flags"
+
+flag {
+    name: "telecom_resolve_hidden_dependencies"
+    namespace: "android_platform_telecom"
+    description: "Mainland cleanup for hidden dependencies"
+    bug: "b/303440370"
+}
diff --git a/flags/telecom_ringer_flag_declarations.aconfig b/flags/telecom_ringer_flag_declarations.aconfig
new file mode 100644
index 0000000..54748d0
--- /dev/null
+++ b/flags/telecom_ringer_flag_declarations.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.server.telecom.flags"
+
+flag {
+  name: "use_device_provided_serialized_ringer_vibration"
+  namespace: "telecom"
+  description: "Gates whether to use a serialized, device-specific ring vibration."
+  bug: "282113261"
+}
\ No newline at end of file
diff --git a/flags/telecom_work_profile_flags.aconfig b/flags/telecom_work_profile_flags.aconfig
new file mode 100644
index 0000000..cc78b30
--- /dev/null
+++ b/flags/telecom_work_profile_flags.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.server.telecom.flags"
+
+flag {
+  name: "work_profile_associated_user"
+  namespace: "telecom"
+  description: "Redefines the associated user for calls in the context of work profile support (U+)"
+  bug: "294699269"
+}
\ No newline at end of file
diff --git a/proguard.flags b/proguard.flags
index 635eba6..7c71a15 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -9,17 +9,3 @@
 -keep class android.telecom.Log {
   *;
 }
-
-# Keep classes, annotations and members used by Lifecycle. Remove this once aapt2 is enabled
--keepattributes *Annotation*
-
--keep class * implements android.arch.lifecycle.LifecycleObserver {
-}
-
--keep class * implements android.arch.lifecycle.GeneratedAdapter {
-    <init>(...);
-}
-
--keepclassmembers class ** {
-    @android.arch.lifecycle.OnLifecycleEvent *;
-}
diff --git a/res/drawable/gm_phonelink.xml b/res/drawable/gm_phonelink.xml
new file mode 100644
index 0000000..2ffba0e
--- /dev/null
+++ b/res/drawable/gm_phonelink.xml
@@ -0,0 +1,11 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="20dp"
+    android:height="20dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="?android:attr/colorControlNormal"
+    android:autoMirrored="true">
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M5,6h16L21,4L5,4c-1.1,0 -2,0.9 -2,2v11L1,17v3h11v-3L5,17L5,6zM21,8h-6c-0.55,0 -1,0.45 -1,1v10c0,0.55 0.45,1 1,1h6c0.55,0 1,-0.45 1,-1L22,9c0,-0.55 -0.45,-1 -1,-1zM20,17h-4v-7h4v7z"/>
+</vector>
diff --git a/res/drawable/person_circle.xml b/res/drawable/person_circle.xml
new file mode 100644
index 0000000..e139b4f
--- /dev/null
+++ b/res/drawable/person_circle.xml
@@ -0,0 +1,13 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="48dp"
+    android:height="48dp"
+    android:viewportWidth="48"
+    android:viewportHeight="48">
+  <path
+      android:pathData="M21.094,0.92C22.839,-0.307 25.161,-0.307 26.906,0.92C28.031,1.71 29.428,2.009 30.777,1.746C32.868,1.339 34.989,2.287 36.086,4.12C36.794,5.302 37.95,6.145 39.288,6.456C41.363,6.938 42.917,8.671 43.177,10.794C43.345,12.163 44.059,13.406 45.156,14.236C46.856,15.524 47.574,17.742 46.952,19.788C46.551,21.107 46.7,22.534 47.365,23.741C48.397,25.612 48.154,27.931 46.758,29.546C45.857,30.587 45.416,31.951 45.535,33.326C45.72,35.457 44.559,37.476 42.629,38.381C41.384,38.965 40.429,40.03 39.981,41.335C39.287,43.357 37.408,44.727 35.279,44.766C33.906,44.79 32.601,45.374 31.664,46.382C30.211,47.946 27.939,48.431 25.979,47.596C24.714,47.057 23.286,47.057 22.021,47.596C20.061,48.431 17.789,47.946 16.336,46.382C15.399,45.374 14.094,44.79 12.721,44.766C10.592,44.727 8.713,43.357 8.019,41.335C7.571,40.03 6.616,38.965 5.371,38.381C3.441,37.476 2.28,35.457 2.465,33.326C2.584,31.951 2.143,30.587 1.242,29.546C-0.154,27.931 -0.397,25.612 0.635,23.741C1.3,22.534 1.449,21.107 1.048,19.788C0.427,17.742 1.144,15.524 2.844,14.236C3.941,13.406 4.655,12.163 4.823,10.794C5.083,8.671 6.637,6.938 8.712,6.456C10.05,6.145 11.206,5.302 11.914,4.12C13.011,2.287 15.132,1.339 17.223,1.746C18.572,2.009 19.969,1.71 21.094,0.92Z"
+      android:fillColor="#000000"/>
+  <path
+      android:pathData="M24.001,15.467C21.644,15.467 19.734,17.376 19.734,19.733C19.734,22.091 21.644,24 24.001,24C26.358,24 28.268,22.091 28.268,19.733C28.268,17.376 26.358,15.467 24.001,15.467ZM26.134,19.734C26.134,18.56 25.174,17.601 24,17.601C22.827,17.601 21.867,18.56 21.867,19.734C21.867,20.907 22.827,21.867 24,21.867C25.174,21.867 26.134,20.907 26.134,19.734ZM30.402,29.333C30.188,28.576 26.882,27.2 24.002,27.2C21.122,27.2 17.815,28.576 17.602,29.344V30.4H30.402V29.333ZM15.469,29.333C15.469,26.496 21.154,25.066 24.002,25.066C26.85,25.066 32.535,26.496 32.535,29.333V32.533H15.469V29.333Z"
+      android:fillColor="#ffffff"
+      android:fillType="evenOdd"/>
+</vector>
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index 61381ae..50bead5 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"Agtergrondoproepe"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Oproepe is ontkoppel"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Omgevalde foonprogramme"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"Oproepstroming"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"As jy hierdie oproep maak, sal dit jou <xliff:g id="OTHER_APP">%1$s</xliff:g>-oproep beëindig."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Kies hoe om hierdie oproep te maak"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Herlei oproep deur <xliff:g id="OTHER_APP">%1$s</xliff:g> te gebruik"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Luidspreker"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Ekstern"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Onbekend"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"Stroom oudio na ander toestel"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Beëindig oproep"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Skakel hier oor"</string>
 </resources>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index fc36464..f0923d5 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -90,7 +90,7 @@
     <string name="answering_ends_other_managed_video_call" msgid="1988508241432031327">"መመለስ እየተካሄደ ያለ የቪዲዮ ጥሪዎን ይጨርሳል"</string>
     <string name="answer_incoming_call" msgid="2045888814782215326">"ይመልሱ"</string>
     <string name="decline_incoming_call" msgid="922147089348451310">"አትቀበል"</string>
-    <string name="cant_call_due_to_no_supported_service" msgid="1635626384149947077">"የዚህን አይነት ጥሪዎች የሚደግፉ መደወያ መለያዎች ስለሌሉ ጥሪ መደረግ አይችልም።"</string>
+    <string name="cant_call_due_to_no_supported_service" msgid="1635626384149947077">"የዚህን ዓይነት ጥሪዎች የሚደግፉ መደወያ መለያዎች ስለሌሉ ጥሪ መደረግ አይችልም።"</string>
     <string name="cant_call_due_to_ongoing_call" msgid="8004235328451385493">"በ<xliff:g id="OTHER_CALL">%1$s</xliff:g> ጥሪዎ ምክንያት ጥሪ መደረግ አይችልም።"</string>
     <string name="cant_call_due_to_ongoing_calls" msgid="6379163795277824868">"በ<xliff:g id="OTHER_CALL">%1$s</xliff:g> ጥሪዎችዎ ምክንያት ጥሪዎች መደረግ አይችሉም።"</string>
     <string name="cant_call_due_to_ongoing_unknown_call" msgid="8243532328969433172">"በሌላ መተግበሪያ ውስጥ ባለ ጥሪ ምክንያት ጥሪ መደረግ አይችልም።"</string>
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"የጀርባ ጥሪዎች"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"የተቋረጡ ጥሪዎች"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"የተበላሹ የስልክ መተግበሪያዎች"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"የጥሪ ዥረት"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"ይህን ጥሪ ማድረግ የ<xliff:g id="OTHER_APP">%1$s</xliff:g> ጥሪዎን ያቋርጣል።"</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"ይህን ጥሪ እንዴት እንደሚያደርጉ ይምረጡ"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"<xliff:g id="OTHER_APP">%1$s</xliff:g>ን በመጠቀም አዘዋውር"</string>
@@ -127,7 +128,10 @@
     <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"ማዳመጫ"</string>
     <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"ብሉቱዝ"</string>
     <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"ባለገመድ ማዳመጫ"</string>
-    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"ድምጽ ማውጫ"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"ድምፅ ማውጫ"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"ውጫዊ"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"ያልታወቀ"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"ኦዲዮን ወደ ሌላ መሣሪያ በመልቀቅ ላይ"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"ዝጋ"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"እዚህ ቀይር"</string>
 </resources>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index b9f8842..2a56809 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"مكالمات في الخلفية"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"المكالمات التي تم قطع الاتصال بها"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"تطبيقات الهواتف المعطّلة"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"بث المكالمات"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"يؤدي إجراء هذه المكالمة إلى إنهاء مكالمة <xliff:g id="OTHER_APP">%1$s</xliff:g>."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"اختيار كيفية إجراء هذه المكالمة"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"إعادة توجيه المكالمة باستخدام <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"مكبّر صوت"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"المصادر الخارجية"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"غير معروف"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"بث الصوت على جهاز آخر"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"قطع الاتصال"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"الانتقال إلى هنا"</string>
 </resources>
diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml
index 9226599..72ac4db 100644
--- a/res/values-as/strings.xml
+++ b/res/values-as/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"নেপথ্যৰ কলসমূহ"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"সংযোগ বিচ্ছিন্ন কৰা কলসমূহ"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"ক্ৰেশ্ব হোৱা ফ\'ন এপ্‌সমূহ"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"কল ষ্ট্ৰীমিং"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"এই কলটো কৰিলে আপোনাৰ <xliff:g id="OTHER_APP">%1$s</xliff:g> কলটোৰ অন্ত পৰিব।"</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"এই কলটো কেনেকৈ কৰা হ’ব সেয়া বাছনি কৰক"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"<xliff:g id="OTHER_APP">%1$s</xliff:g> ব্যৱহাৰ কৰি কল ৰিডাইৰেক্ট কৰক"</string>
@@ -108,7 +109,7 @@
     <string name="phone_settings_call_blocking_txt" msgid="7311523114822507178">"কল অৱৰোধ"</string>
     <string name="phone_settings_number_not_in_contact_txt" msgid="2602249106007265757">"আপোনাৰ সর্ম্পকসূচীত নথকা"</string>
     <string name="phone_settings_number_not_in_contact_summary_txt" msgid="963327038085718969">"আপোনাৰ সর্ম্পকসূচীত নথকা নম্বৰ অৱৰোধ কৰক"</string>
-    <string name="phone_settings_private_num_txt" msgid="6339272760338475619">"ব্য়ক্তিগত"</string>
+    <string name="phone_settings_private_num_txt" msgid="6339272760338475619">"ব্যক্তিগত"</string>
     <string name="phone_settings_private_num_summary_txt" msgid="6755758240544021037">"যিসকল কল কৰোঁতাই তেওঁলোকৰ নম্বৰ প্ৰকাশ নকৰে তেওঁলোকক অৱৰোধ কৰক"</string>
     <string name="phone_settings_payphone_txt" msgid="5003987966052543965">"পে\'ফ\'ন"</string>
     <string name="phone_settings_payphone_summary_txt" msgid="3936631076065563665">"পে\'ফ\'নৰ পৰা অহা কল অৱৰোধ কৰক"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"স্পীকাৰ"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"বাহ্যিক"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"অজ্ঞাত"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"অন্য এটা ডিভাইচলৈ অডিঅ’ ষ্ট্ৰীম কৰি থকা হৈছে"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"কলটো কাটি দিয়ক"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"ইয়াত সলনি কৰক"</string>
 </resources>
diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml
index d2368fa..ead7f54 100644
--- a/res/values-az/strings.xml
+++ b/res/values-az/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"Arxa fon zəngləri"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Kəsilmiş zənglər"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Tətbiq xətaları"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"Zəng yayımı"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"Bu zəngin yerləşdirilməsi <xliff:g id="OTHER_APP">%1$s</xliff:g> zəngini sonlandıracaq."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Bu zəngi necə etməyi seçin"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"<xliff:g id="OTHER_APP">%1$s</xliff:g> istifadə edərək zəngi yönləndirin"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Dinamik"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Xarici"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Naməlum"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"Audio digər cihaza ötürülür"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Zəngi sonlandırın"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Buraya keçin"</string>
 </resources>
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
index f77b0bb..d527842 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"Pozivi u pozadini"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Prekinuti pozivi"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Aplikacije za telefoniranje koje su otkazale"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"Strimovanje poziva"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"Ako uputite ovaj poziv, završićete <xliff:g id="OTHER_APP">%1$s</xliff:g> poziv."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Izaberite kako želite da uputite ovaj poziv"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Preusmeri poziv pomoću: <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Zvučnik"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Eksterni"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Nepoznato"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"Zvuk se strimuje na drugi uređaj"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Prekini vezu"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Prebaci ovde"</string>
 </resources>
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
index 8560c9c..c5b59bd 100644
--- a/res/values-be/strings.xml
+++ b/res/values-be/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"Фонавыя выклікі"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Перарваныя выклікі"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Збоі ў праграмах \"Тэлефон\""</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"Перадача выкліку плынню"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"Калі зрабіць гэты выклік, ваш выклік праз праграму <xliff:g id="OTHER_APP">%1$s</xliff:g> скончыцца."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Выберыце, праз які нумар зрабіць выклік"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Перанакіраваць выклік, выкарыстоўваючы нумар \"<xliff:g id="OTHER_APP">%1$s</xliff:g>\""</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Знешні дынамік"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Знешняя прылада"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Невядома"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"Перадача аўдыя плынню на іншую прыладу"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Завяршыць выклік"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Пераключыцца"</string>
 </resources>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index c99dcd0..fe5d70f 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"Обаждания на заден план"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Прекъснати обаждания"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Приложения за телефон с прекъсната работа"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"Поточно предаване на обаждания"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"Ако извършите това обаждане, обаждането ви през <xliff:g id="OTHER_APP">%1$s</xliff:g> ще прекъсне."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Изберете как да се извърши обаждането"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Пренасочване на обаждането през <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Високоговорител"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Външно"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Неизвестно"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"Звукът се предава поточно към друго устройство"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Затваряне"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Превключете тук"</string>
 </resources>
diff --git a/res/values-bn/strings.xml b/res/values-bn/strings.xml
index 01b67f0..49e6ba3 100644
--- a/res/values-bn/strings.xml
+++ b/res/values-bn/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"ব্যাকগ্রাউন্ডের কল"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"ডিসকানেক্ট করা কলগুলি"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"ক্র্যাশ হওয়া ফোন অ্যাপ"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"কল স্ট্রিম করা হচ্ছে"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"এই কলটির উত্তর দেওয়া হলে তা আপনার <xliff:g id="OTHER_APP">%1$s</xliff:g> কলটি কেটে যাবে৷"</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"এই কলটি কীভাবে করবেন বেছে নিন"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"<xliff:g id="OTHER_APP">%1$s</xliff:g> ব্যবহার করে কল রিডাইরেক্ট করুন"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"স্পিকার"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"এক্সটার্নাল"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"অজানা"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"অন্য ডিভাইসে অডিও স্ট্রিম করা হচ্ছে"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"কল কেটে দিন"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"এখানে পাল্টান"</string>
 </resources>
diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml
index 201d8d1..61b86db 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"Pozivi u pozadini"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Prekinuti pozivi"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Padovi aplikacija za telefon"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"Prijenos poziva"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"Upućivanje ovog poziva će prekinuti poziv: <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Odaberite kako želite uputiti ovaj poziv"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Preusmjeri poziv pomoću aplikacije <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Zvučnik"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Vanjski"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Nepoznato"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"Prijenos zvuka na drugom uređaju"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Prekini vezu"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Prebaci ovdje"</string>
 </resources>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index 2c5727d..113d144 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"Trucades en segon pla"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Trucades desconnectades"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Aplicacions del telèfon que han fallat"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"Reproducció en directe de trucada"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"En fer aquesta trucada, finalitzarà la de l\'aplicació <xliff:g id="OTHER_APP">%1$s</xliff:g>."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Tria com vols fer aquesta trucada"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Desvia la trucada amb <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Altaveu"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Extern"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Desconegut"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"S\'està reproduint àudio en continu en un altre dispositiu"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Penja"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Canvia aquí"</string>
 </resources>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index 2945d28..ab74d61 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"Hovory na pozadí"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Odpojené hovory"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Aplikace, které spadly"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"Streamování hovoru"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"Uskutečněním tohoto hovoru ukončíte hovor <xliff:g id="OTHER_APP">%1$s</xliff:g>."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Vyberte, jak chcete tento hovor provést"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Přesměrovat hovor přes aplikaci <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Reproduktor"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Externí"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Není známo"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"Streamování zvuku do druhého zařízení"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Zavěsit"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Přepnout sem"</string>
 </resources>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index 366b584..4eead66 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -73,7 +73,7 @@
     <string name="non_primary_user" msgid="315564589279622098">"Det er kun ejeren af en enhed, der kan se og administrere blokerede numre."</string>
     <string name="delete_icon_description" msgid="5335959254954774373">"Fjern blokering"</string>
     <string name="blocked_numbers_butter_bar_title" msgid="582982373755950791">"Blokering er midlertidigt slået fra"</string>
-    <string name="blocked_numbers_butter_bar_body" msgid="1261213114919301485">"Når du har ringet eller sendt en sms-besked til alarmcentralen, bliver blokering slået fra for at sikre, at alarmcentralen kan komme i kontakt med dig."</string>
+    <string name="blocked_numbers_butter_bar_body" msgid="1261213114919301485">"Når du har ringet eller sendt en besked til alarmcentralen, bliver blokering slået fra for at sikre, at alarmcentralen kan komme i kontakt med dig."</string>
     <string name="blocked_numbers_butter_bar_button" msgid="2704456308072489793">"Genaktiver nu"</string>
     <string name="blocked_numbers_number_blocked_message" msgid="4314736791180919167">"<xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g> blev blokeret"</string>
     <string name="blocked_numbers_number_unblocked_message" msgid="2933071624674945601">"Blokeringen af <xliff:g id="UNBLOCKED_NUMBER">%1$s</xliff:g> blev ophævet"</string>
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"Opkald i baggrunden"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Afbrudte opkald"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Opkaldsapps, der er gået ned"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"Opkaldsstreaming"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"Hvis du foretager dette opkald, afsluttes dit opkald i <xliff:g id="OTHER_APP">%1$s</xliff:g>."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Vælg, hvordan du vil foretage dette opkald"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Omdiriger opkaldet ved hjælp af <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Højttaler"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Ekstern"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Ukendt"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"Streamer lyd til en anden enhed"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Læg på"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Skift hertil"</string>
 </resources>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index 801321b..dccdb87 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"Anrufe im Hintergrund"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Beendete Anrufe"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Abgestürzte Telefon-Apps"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"Anrufstreaming"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"Durch diesen Anruf wird der Anruf in <xliff:g id="OTHER_APP">%1$s</xliff:g> beendet."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Wie möchtest du anrufen?"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Mit <xliff:g id="OTHER_APP">%1$s</xliff:g> weiterleiten"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Lautsprecher"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Extern"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Unbekannt"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"Audio auf einem anderen Gerät streamen"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Anruf beenden"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Auf dieses Gerät wechseln"</string>
 </resources>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index 7a09f0a..6b58863 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -100,7 +100,8 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"Κλήσεις στο παρασκήνιο"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Αποσυνδεδεμένες κλήσεις"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Εφαρμογές τηλεφώνου που αντιμετώπισαν σφάλμα λειτουργίας"</string>
-    <string name="alert_outgoing_call" msgid="5319895109298927431">"Εάν πραγματοποιήσετε αυτήν την κλήση, η κλήση σας μέσω <xliff:g id="OTHER_APP">%1$s</xliff:g> θα τερματιστεί."</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"Ροή κλήσης"</string>
+    <string name="alert_outgoing_call" msgid="5319895109298927431">"Εάν πραγματοποιήσετε αυτή την κλήση, η κλήση σας μέσω <xliff:g id="OTHER_APP">%1$s</xliff:g> θα τερματιστεί."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Επιλέξτε πώς θα πραγματοποιήσετε την κλήση"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Ανακατεύθυνση της κλήσης μέσω <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
     <string name="alert_place_unredirect_outgoing_call" msgid="2467608535225764006">"Κλήση μέσω του αριθμού τηλεφώνου μου"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Ηχείο"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Εξωτερικά"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Άγνωστο"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"Ροή ήχου σε άλλη συσκευή"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Απόρριψη"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Εναλλαγή εδώ"</string>
 </resources>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index 0249401..250ab62 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"Background calls"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Disconnected calls"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Crashed phone apps"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"Call streaming"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"Placing this call will end your <xliff:g id="OTHER_APP">%1$s</xliff:g> call."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Choose how to make this call"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Redirect call using <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Speaker"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"External"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Unknown"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"Streaming audio to other device"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Hang up"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Switch here"</string>
 </resources>
diff --git a/res/values-en-rCA/strings.xml b/res/values-en-rCA/strings.xml
index 5f857c1..e6291f4 100644
--- a/res/values-en-rCA/strings.xml
+++ b/res/values-en-rCA/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"Background calls"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Disconnected calls"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Crashed phone apps"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"Call streaming"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"Placing this call will end your <xliff:g id="OTHER_APP">%1$s</xliff:g> call."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Choose how to place this call"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Redirect call using <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Speaker"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"External"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Unknown"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"Streaming audio to other device"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Hang up"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Switch here"</string>
 </resources>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index 0249401..250ab62 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"Background calls"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Disconnected calls"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Crashed phone apps"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"Call streaming"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"Placing this call will end your <xliff:g id="OTHER_APP">%1$s</xliff:g> call."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Choose how to make this call"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Redirect call using <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Speaker"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"External"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Unknown"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"Streaming audio to other device"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Hang up"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Switch here"</string>
 </resources>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index 0249401..250ab62 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"Background calls"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Disconnected calls"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Crashed phone apps"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"Call streaming"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"Placing this call will end your <xliff:g id="OTHER_APP">%1$s</xliff:g> call."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Choose how to make this call"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Redirect call using <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Speaker"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"External"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Unknown"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"Streaming audio to other device"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Hang up"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Switch here"</string>
 </resources>
diff --git a/res/values-en-rXC/strings.xml b/res/values-en-rXC/strings.xml
index 2ffae87..5bd0e25 100644
--- a/res/values-en-rXC/strings.xml
+++ b/res/values-en-rXC/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‎‎‎‎‏‏‎‎‎‎‏‏‏‎‎‎‎‏‏‎‏‏‏‎‏‎‏‎‎‏‏‎‏‎‏‏‎‏‏‏‏‎‎‎‏‏‏‎‏‏‏‎‏‎‏‎‎Background calls‎‏‎‎‏‎"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‎‎‎‏‏‎‎‎‏‏‏‏‏‏‏‎‏‎‏‎‏‎‏‏‎‏‏‏‎‏‏‏‎‏‏‎‎‎‏‏‏‎‎‎‏‎‎‏‏‏‎‏‏‏‏‎‏‎Disconnected calls‎‏‎‎‏‎"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‏‎‏‏‏‏‏‎‏‏‏‎‏‎‏‏‎‏‏‎‎‎‏‏‎‏‏‎‎‏‎‎‎‎‎‎‎‎‎‎‏‏‏‎‏‎‎‎‎‎‏‎‎‏‎‏‏‎Crashed phone apps‎‏‎‎‏‎"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‏‏‎‎‎‏‏‎‏‏‎‎‏‎‎‎‏‎‏‎‎‏‏‏‎‏‏‏‎‏‎‏‎‎‏‏‎‎‎‏‏‏‏‎‏‎‏‎‎‏‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎Call streaming‎‏‎‎‏‎"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‏‏‎‎‏‎‎‏‏‏‎‏‎‏‎‎‎‎‎‏‎‎‎‎‎‏‏‎‏‎‏‎‎‏‏‏‏‎‎‎‎‎‎‏‏‎‎‏‏‎‏‏‎‏‏‏‎‏‎‎‎‏‏‏‎Placing this call will end your ‎‏‎‎‏‏‎<xliff:g id="OTHER_APP">%1$s</xliff:g>‎‏‎‎‏‏‏‎ call.‎‏‎‎‏‎"</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‎‎‏‎‎‏‎‎‏‏‏‏‎‎‎‎‎‎‎‎‏‎‏‎‎‎‏‏‎‎‎‎‎‏‏‏‏‏‎‏‏‎‎‏‏‏‎‎‏‎‎‏‏‎‏‏‏‎‎‏‎‎‎Choose how to place this call‎‏‎‎‏‎"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‏‏‎‎‏‎‎‎‎‏‏‏‎‏‎‎‏‏‏‏‎‎‏‎‏‏‏‏‏‎‏‏‏‎‏‏‎‏‏‏‏‎‎‎‎‏‏‎‏‏‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎Redirect call using ‎‏‎‎‏‏‎<xliff:g id="OTHER_APP">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‎‏‏‏‎‏‏‎‏‎‏‏‏‎‏‎‎‎‏‏‎‎‏‏‎‏‏‎‎‏‏‎‎‎‎‏‏‏‎‏‎‏‏‏‏‏‎‎‎‎‏‎‎‏‎‎‎‏‏‎‏‎‏‎Speaker‎‏‎‎‏‎"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‎‏‎‎‎‎‎‎‏‏‏‎‎‎‎‏‏‎‎‏‏‏‎‏‎‏‎‏‏‎‏‎‏‎‏‎‎‏‎‎‎‏‎‎‏‏‏‏‎‎‎‏‏‏‎‎‏‏‏‏‎‎‎‎External‎‏‎‎‏‎"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‎‏‏‏‏‏‎‏‎‎‎‎‏‎‎‏‎‏‎‏‏‏‎‏‏‎‎‏‎‏‏‏‏‎‏‎‎‎‏‎‏‎‏‎‏‎‎‎‎‏‎‏‏‏‎‎‏‏‏‏‏‎‎‎Unknown‎‏‎‎‏‎"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‎‏‏‏‏‎‏‏‏‏‏‎‎‎‎‎‏‏‏‎‏‎‏‏‏‎‎‎‏‏‎‎‏‎‎‎‏‏‏‎‏‏‏‏‎‎‏‏‏‎‎‎‎‎‎‏‎‏‎‏‏‏‎Streaming audio to other device‎‏‎‎‏‎"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‎‏‏‎‎‎‏‏‏‎‏‏‏‏‏‏‏‎‎‏‏‏‏‏‎‏‎‎‏‏‎‏‎‎‏‏‎‎‎‎‎‎‎‎‏‎‎‏‏‎‎‏‎‎‏‏‎Hang up‎‏‎‎‏‎"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‎‏‏‎‎‎‎‏‏‏‎‏‎‎‎‎‏‏‎‎‏‏‏‏‎‏‏‏‎‎‏‎‏‎‏‏‎‎‏‏‎‎‎‏‎‎‏‏‎‏‏‎‏‏‎‏‎‏‎‏‏‎‎‎Switch here‎‏‎‎‏‎"</string>
 </resources>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index ab8f454..c0f4e17 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"Llamadas en segundo plano"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Llamadas desconectadas"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Apps de teléfono con fallas"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"Transmisión de llamadas"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"Si realizas esta llamada, finalizará la de <xliff:g id="OTHER_APP">%1$s</xliff:g>."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Elige cómo quieres realizar esta llamada"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Redireccionar la llamada mediante <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Bocina"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Externa"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Desconocido"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"Transmitiendo el audio a otro dispositivo"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Colgar"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Cambiar aquí"</string>
 </resources>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index 65ab627..20b80a5 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"Llamadas en segundo plano"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Llamadas interrumpidas"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Aplicaciones para teléfonos con fallos"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"Transmisión de llamadas"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"Si haces esta llamada, se finalizará la de <xliff:g id="OTHER_APP">%1$s</xliff:g>."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Elige cómo quieres hacer esta llamada"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Redirigir llamada con <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Altavoz"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Fuentes externas"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Desconocido"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"Transmitiendo audio a otro dispositivo"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Colgar"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Cambiar aquí"</string>
 </resources>
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
index 7d9ad7b..cac1fd6 100644
--- a/res/values-et/strings.xml
+++ b/res/values-et/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"Taustal olevad kõned"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Katkestatud kõned"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Kokkujooksnud telefonirakendused"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"Kõne voogesitus"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"Selle kõne tegemisel lõpetatakse pooleliolev kõne rakenduses <xliff:g id="OTHER_APP">%1$s</xliff:g>."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Valige, kuidas soovite helistada"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Suuna kõne ümber rakenduse <xliff:g id="OTHER_APP">%1$s</xliff:g> abil"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Kõlar"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Välised"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Teadmata"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"Heli voogesitamine teise seadmesse"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Lõpeta kõne"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Vaheta siia"</string>
 </resources>
diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml
index 64645a4..d1aa545 100644
--- a/res/values-eu/strings.xml
+++ b/res/values-eu/strings.xml
@@ -47,7 +47,7 @@
     <string name="respond_via_sms_failure_format" msgid="5198680980054596391">"Ezin izan da bidali mezua <xliff:g id="PHONE_NUMBER">%s</xliff:g> zenbakira."</string>
     <string name="enable_account_preference_title" msgid="6949224486748457976">"Deiak egiteko kontuak"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="3424338207838851646">"Larrialdi-deiak bakarrik egin daitezke."</string>
-    <string name="outgoing_call_not_allowed_no_permission" msgid="8590468836581488679">"Aplikazioak deitu ahal izan dezan, telefonoaren eginbidea erabiltzeko baimena behar du."</string>
+    <string name="outgoing_call_not_allowed_no_permission" msgid="8590468836581488679">"Aplikazioak irteerako deiak egin ahal izan ditzan, telefonoaren eginbidea erabiltzeko baimena behar du."</string>
     <string name="outgoing_call_error_no_phone_number_supplied" msgid="7665135102566099778">"Deitzeko, idatzi balio duen zenbaki bat."</string>
     <string name="duplicate_video_call_not_allowed" msgid="5754746140185781159">"Une honetan ezin da deirik gehitu."</string>
     <string name="no_vm_number" msgid="2179959110602180844">"Erantzungailuaren zenbakia falta da"</string>
@@ -94,12 +94,13 @@
     <string name="cant_call_due_to_ongoing_call" msgid="8004235328451385493">"Ezin da egin deia, beste dei bat abian delako <xliff:g id="OTHER_CALL">%1$s</xliff:g> zerbitzuan."</string>
     <string name="cant_call_due_to_ongoing_calls" msgid="6379163795277824868">"Ezin da egin deia, beste dei batzuk abian direlako <xliff:g id="OTHER_CALL">%1$s</xliff:g> zerbitzuan."</string>
     <string name="cant_call_due_to_ongoing_unknown_call" msgid="8243532328969433172">"Ezin da egin deia, beste dei bat abian delako beste aplikazio batean."</string>
-    <string name="notification_channel_incoming_call" msgid="5245550964701715662">"Jasotako deiak"</string>
+    <string name="notification_channel_incoming_call" msgid="5245550964701715662">"Sarrerako deiak"</string>
     <string name="notification_channel_missed_call" msgid="7168893015283909012">"Dei galduak"</string>
     <string name="notification_channel_call_blocking" msgid="2028807677868598710">"Deiak blokeatzeko aukera"</string>
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"Atzeko planoko deiak"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Deskonektatutako deiak"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Huts egin duten telefonoko aplikazioak"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"Deiak igortzea"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"Dei hau egiten baduzu, amaitu egingo da <xliff:g id="OTHER_APP">%1$s</xliff:g> aplikazioko deia."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Aukeratu dei hau egiteko modua"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Birbideratu deia <xliff:g id="OTHER_APP">%1$s</xliff:g> aplikazioaren bidez"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Bozgorailua"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Kanpokoa"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Ezezaguna"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"Audioa beste gailu batera igortzen ari da"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Amaitu deia"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Aldatu hona"</string>
 </resources>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index 83c8034..8d562ec 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"تماس‌های پس‌زمینه"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"تماس‌های قطع‌شده"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"برنامه‌های تلفن خراب"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"جاری‌سازی تماس"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"اگر این تماس را برقرار کنید، تماس <xliff:g id="OTHER_APP">%1$s</xliff:g> شما قطع می‌شود."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"انتخاب نحوه برقراری این تماس"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"هدایت تماس با استفاده از <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"بلندگو"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"خارجی"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"نامشخص"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"درحال جاری‌سازی صدا به دستگاه دیگر"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"قطع تماس"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"انتقال در اینجا انجام شود"</string>
 </resources>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index 4ade7d1..338e429 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"Taustapuhelut"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Katkaistut puhelut"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Kaatuneet puhelinsovellukset"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"Puhelunstriimaus"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"Tämän puhelun soittaminen päättää puhelun sovelluksessa <xliff:g id="OTHER_APP">%1$s</xliff:g>."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Valitse, miten puhelu soitetaan"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Uudelleenohjaa puhelu sovelluksella <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Kaiutin"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Ulkoinen"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Tuntematon"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"Audiota striimataan toiselle laitteelle"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Lopeta puhelu"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Vaihda puhelimeen"</string>
 </resources>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index 95b2069..aaf651f 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"Appels en arrière-plan"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Appels déconnectés"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Applications téléphoniques qui ont planté"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"Diffusion en continu d\'appels"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"Si vous passez cet appel, vous mettrez fin à l\'appel <xliff:g id="OTHER_APP">%1$s</xliff:g>."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Choisissez comment passer cet appel"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Rediriger l\'appel en utilisant <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Haut-parleur"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Externe"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Inconnu"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"Diffusion audio en continu vers un autre appareil en cours…"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Raccrocher"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Revenir à cet appareil"</string>
 </resources>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index 03f6d87..a14cbb1 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"Appels en arrière-plan"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Appels interrompus"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Applications téléphoniques ayant planté"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"Streaming de l\'appel"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"Si vous passez cet appel, vous mettrez fin à celui qui est en cours dans l\'application <xliff:g id="OTHER_APP">%1$s</xliff:g>."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Choisissez comment passer cet appel"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Rediriger l\'appel avec <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Haut-parleur"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Externe"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Inconnu"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"Streaming de l\'audio sur un autre appareil"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Raccrocher"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Passer ici"</string>
 </resources>
diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml
index a8443dd..8e82fce 100644
--- a/res/values-gl/strings.xml
+++ b/res/values-gl/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"Chamadas en segundo plano"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Chamadas desconectadas"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Fallaron as aplicacións de teléfono"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"Propagación de chamada"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"Ao facer esta chamada, finalizarase o túa chamada de <xliff:g id="OTHER_APP">%1$s</xliff:g>."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Escolle como facer esta chamada"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Redirixir a chamada con <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Altofalante"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Externo"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Descoñecido"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"Emitindo audio noutro dispositivo"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Colgar"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Volver aquí"</string>
 </resources>
diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml
index 4af6351..1b5c5ce 100644
--- a/res/values-gu/strings.xml
+++ b/res/values-gu/strings.xml
@@ -25,7 +25,7 @@
     <string name="notification_missedCallsMsg" msgid="5055782736170916682">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> ચૂકી ગયેલા કૉલ"</string>
     <string name="notification_missedCallTicker" msgid="6731461957487087769">"<xliff:g id="MISSED_CALL_FROM">%s</xliff:g> નો કૉલ ચૂકી ગયાં"</string>
     <string name="notification_missedCall_call_back" msgid="7900333283939789732">"કૉલ બેક"</string>
-    <string name="notification_missedCall_message" msgid="4054698824390076431">"સંદેશ"</string>
+    <string name="notification_missedCall_message" msgid="4054698824390076431">"મેસેજ"</string>
     <string name="notification_disconnectedCall_title" msgid="1790131923692416928">"ડિસ્કનેક્ટ કરેલો કૉલ"</string>
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"ઇમર્જન્સી કૉલને કારણે <xliff:g id="CALLER">%s</xliff:g>નો કૉલ ડિસ્કનેક્ટ કરવામાં આવ્યો છે."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"ઇમર્જન્સી કૉલને કારણે તમારો કૉલ ડિસ્કનેક્ટ કરવામાં આવ્યો છે."</string>
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"બૅકગ્રાઉન્ડ કૉલ"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"ડિસ્કનેક્ટ કરેલા કૉલ"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"ફોન ઍપ ક્રૅશ થઈ"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"કૉલ સ્ટ્રીમ કરી રહ્યાં છીએ"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"આ કૉલ કરવાથી તમારો <xliff:g id="OTHER_APP">%1$s</xliff:g> કૉલ સમાપ્ત થઈ જશે."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"આ કૉલ કેવી રીતે કરવો તે પસંદ કરો"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"<xliff:g id="OTHER_APP">%1$s</xliff:g>નો ઉપયોગ કરીને કૉલ રીડાયરેક્ટ કરો"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"સ્પીકર"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"બાહ્ય"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"અજાણ"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"ઑડિયોને અન્ય ડિવાઇસ પર સ્ટ્રીમ કરી રહ્યાં છીએ"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"સમાપ્ત કરો"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"અહીં સ્વિચ કરો"</string>
 </resources>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index 918051a..c32f582 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"बैकग्राउंड कॉल"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"डिसकनेक्ट किए गए कॉल"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"फ़ोन ऐप्लिकेशन जो बंद हो गए"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"कॉल स्ट्रीमिंग"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"इस कॉल को करने से आपका <xliff:g id="OTHER_APP">%1$s</xliff:g> कॉल खत्म हो जाएगा."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"चुनें कि आप इस कॉल को कैसे करना चाहते हैं"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"<xliff:g id="OTHER_APP">%1$s</xliff:g> का इस्तेमाल करके कॉल को दूसरे नंबर पर भेजें"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"स्पीकर"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"बाहरी सोर्स"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"कोई जानकारी नहीं है"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"ऑडियो को दूसरे डिवाइस पर स्ट्रीम किया जा रहा है"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"कॉल खत्म करें"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"यहां स्विच करें"</string>
 </resources>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index 02c91fb..d6b209e 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"Pozivi u pozadini"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Prekinuti pozivi"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Rušenja aplikacija telefona"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"Streaming poziva"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"Upućivanjem ovog poziva prekinut ćete poziv u aplikaciji <xliff:g id="OTHER_APP">%1$s</xliff:g>."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Odaberite kako ćete uputiti poziv"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Preusmjeri poziv putem aplikacije <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Zvučnik"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Vanjski izvori"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Nepoznato"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"Streaming zvuka na drugi uređaj"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Prekini vezu"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Promijeni ovdje"</string>
 </resources>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index cdda34a..63f04b6 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"Háttérbeli hívások"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Bontott hívások"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Telefonalkalmazások összeomlása"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"Hívás átvitele"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"Ha hívást indít, azzal megszakítja a(z) <xliff:g id="OTHER_APP">%1$s</xliff:g>-hívást."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"A hívás módjának kiválasztása"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Hívás átirányítása a következővel: <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Hangszóró"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Külső"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Ismeretlen"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"Hang átvitele másik eszközre"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Hívás befejezése"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Váltás itt"</string>
 </resources>
diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml
index d85d037..169ea36 100644
--- a/res/values-hy/strings.xml
+++ b/res/values-hy/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"Ֆոնային զանգեր"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Անջատված զանգեր"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Հեռախոսի հավելվածներ, որոնց աշխատանքը սխալի պատճառով խափանվել է"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"Զանգի հեռարձակում"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"Այս զանգը կատարելու դեպքում <xliff:g id="OTHER_APP">%1$s</xliff:g>-ի ընթացիկ զանգը կընդհատվի"</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Ընտրեք, թե ինչպես եք ուզում կատարել այս զանգը"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Վերահասցեավորել զանգը <xliff:g id="OTHER_APP">%1$s</xliff:g> հավելվածով"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Բարձրախոս"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Արտաքին"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Անհայտ"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"Աուդիոյի հեռարձակում այլ սարքում"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Ավարտել զանգը"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Անցնել այստեղ"</string>
 </resources>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index 84c0d39..1e51f7a 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"Panggilan telepon latar belakang"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Panggilan terputus"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Aplikasi telepon error"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"Streaming panggilan"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"Melakukan panggilan ini akan mengakhiri panggilan <xliff:g id="OTHER_APP">%1$s</xliff:g>."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Pilih cara melakukan panggilan ini"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Alihkan panggilan menggunakan <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Speaker"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Eksternal"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Tidak diketahui"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"Streaming audio ke perangkat lain"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Akhiri"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Beralih ke sini"</string>
 </resources>
diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml
index db7dbeb..7009b7c 100644
--- a/res/values-is/strings.xml
+++ b/res/values-is/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"Bakgrunnssímtöl"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Aftengd símtöl"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Hrun í símaforritum"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"Símtal í streymi"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"Ef þú hringir mun þessu símtali í <xliff:g id="OTHER_APP">%1$s</xliff:g> ljúka."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Veldu hvernig hringt er"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Framsenda símtal með <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Hátalari"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Ytra tæki"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Óþekkt"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"Streymir hljóði í annað tæki"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Leggja á"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Skipta hingað"</string>
 </resources>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index ad070d6..4a17d18 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"Chiamate in sottofondo"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Chiamate disconnesse"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"App per telefono arrestate in modo anomalo"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"Streaming chiamata"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"Se effettui questa chiamata, la chiamata di <xliff:g id="OTHER_APP">%1$s</xliff:g> verrà terminata."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Scegli come effettuare questa chiamata"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Reindirizza la chiamata utilizzando <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Vivavoce"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Esterno"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Sconosciuto"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"Streaming audio all\'altro dispositivo"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Riaggancia"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Passa qui"</string>
 </resources>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index d557599..05ec712 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"שיחות ברקע"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"שיחות שנותקו"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"אפליקציות טלפון שקרסו"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"העברת השיחה"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"ביצוע השיחה הזו יסיים את השיחה ב-<xliff:g id="OTHER_APP">%1$s</xliff:g>."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"איך להתקשר?"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"ניתוב דרך <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"רמקול"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"מכשיר חיצוני"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"לא ידוע"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"הקול מושמע במכשיר אחר"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"ניתוק"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"העברת השיחה בחזרה לטלפון"</string>
 </resources>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index 73b85d9..19387ff 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"バックグラウンドでの通話"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"通話の切断"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"通話アプリがクラッシュしたとき"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"通話ストリーミング"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"この通話を発信すると、<xliff:g id="OTHER_APP">%1$s</xliff:g> の通話が終了します。"</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"通話の発信方法を選択してください"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"<xliff:g id="OTHER_APP">%1$s</xliff:g> を使用して通話をリダイレクト"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"スピーカー"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"外部"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"不明"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"他のデバイスに音声をストリーミングしています"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"通話を終了"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"このデバイスに切り替える"</string>
 </resources>
diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml
index 33c5a47..d56873f 100644
--- a/res/values-ka/strings.xml
+++ b/res/values-ka/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"ზარები ფონში"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"გათიშული ზარები"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"ავარიულად გათიშული ტელეფონის აპები"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"ზარის სტრიმინგი"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"ამ ზარის განხორციელება თქვენს <xliff:g id="OTHER_APP">%1$s</xliff:g> ზარს დაასრულებს."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"აირჩიეთ, როგორ განათავსოთ ეს ზარი"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"ზარის გადამისამართება <xliff:g id="OTHER_APP">%1$s</xliff:g>-ის გამოყენებით"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"დინამიკი"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"გარე"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"უცნობი"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"მიმდინარეობს აუდიოს სტრიმინგი სხვა მოწყობილობაზე"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"გათიშვა"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"გადართვა"</string>
 </resources>
diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml
index 7c07654..399da20 100644
--- a/res/values-kk/strings.xml
+++ b/res/values-kk/strings.xml
@@ -69,16 +69,16 @@
     <string name="unblock_button" msgid="8732021675729981781">"Бөгеуден шығару"</string>
     <string name="add_blocked_dialog_body" msgid="8599974422407139255">"Қоңыраулары мен мәтіндік хабарлары бөгелетін нөмір"</string>
     <string name="add_blocked_number_hint" msgid="8769422085658041097">"Телефон нөмірі"</string>
-    <string name="block_button" msgid="485080149164258770">"Бөгеу"</string>
+    <string name="block_button" msgid="485080149164258770">"Блоктау"</string>
     <string name="non_primary_user" msgid="315564589279622098">"Бөгелген нөмірлерді тек құрылғы иесі көре және басқара алады."</string>
     <string name="delete_icon_description" msgid="5335959254954774373">"Бөгеуді алу"</string>
     <string name="blocked_numbers_butter_bar_title" msgid="582982373755950791">"Тыйым уақытша алынды"</string>
     <string name="blocked_numbers_butter_bar_body" msgid="1261213114919301485">"Төтенше жағдай нөмірін терген немесе мәтіндік хабар жіберген соң, төтенше жағдай қызметтері сізге хабарласа алуы үшін тыйым алынады."</string>
     <string name="blocked_numbers_butter_bar_button" msgid="2704456308072489793">"Қазір қайта қосу"</string>
-    <string name="blocked_numbers_number_blocked_message" msgid="4314736791180919167">"<xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g> бөгелген"</string>
+    <string name="blocked_numbers_number_blocked_message" msgid="4314736791180919167">"<xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g> блокталған"</string>
     <string name="blocked_numbers_number_unblocked_message" msgid="2933071624674945601">"<xliff:g id="UNBLOCKED_NUMBER">%1$s</xliff:g> бөгеуден шығарылды"</string>
     <string name="blocked_numbers_block_emergency_number_message" msgid="4198550501500893890">"Жедел қызмет нөмірін бөгеу мүмкін емес."</string>
-    <string name="blocked_numbers_number_already_blocked_message" msgid="2301270825735665458">"<xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g> бұрыннан бөгелген."</string>
+    <string name="blocked_numbers_number_already_blocked_message" msgid="2301270825735665458">"<xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g> бұрыннан блокталған."</string>
     <string name="toast_personal_call_msg" msgid="5817631570381795610">"Қоңырау шалу үшін жеке нөмір тергішті пайдалану"</string>
     <string name="notification_incoming_call" msgid="1233481138362230894">"<xliff:g id="CALL_VIA">%1$s</xliff:g> қоңырауы: <xliff:g id="CALL_FROM">%2$s</xliff:g>"</string>
     <string name="notification_incoming_video_call" msgid="5795968314037063900">"<xliff:g id="CALL_VIA">%1$s</xliff:g> бейне қоңырауы: <xliff:g id="CALL_FROM">%2$s</xliff:g>"</string>
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"Фондық қоңыраулар"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Ажыратылған қоңыраулар"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Бұзылған телефон қолданбалары"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"Қоңырауды трансляциялау"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"Жаңа қоңырау шалу <xliff:g id="OTHER_APP">%1$s</xliff:g> қоңырауын тоқтатады."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Қоңырау шалу әдісін таңдаңыз."</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Қоңырау бағытын <xliff:g id="OTHER_APP">%1$s</xliff:g> арқылы ауыстыру"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Динамик"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Сыртқы"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Белгісіз"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"Аудионы басқа құрылғыға трансляциялау"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Қоңырауды аяқтау"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Осы жерде ауысу"</string>
 </resources>
diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml
index 64e47ef..1c28d37 100644
--- a/res/values-km/strings.xml
+++ b/res/values-km/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"ការហៅនៅផ្ទៃខាងក្រោយ"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"ការហៅ​ទូរសព្ទដែលបាន​ផ្ដាច់"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"កម្មវិធី​ទូរសព្ទ​គាំង"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"មុខងារផ្សាយ​ការហៅទូរសព្ទ"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"ការ​ហៅ​ទូរសព្ទ​នេះ នឹង​បញ្ចប់​ការហៅ <xliff:g id="OTHER_APP">%1$s</xliff:g> របស់​អ្នក។"</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"ជ្រើសរើស​របៀប​ធ្វើ​ការហៅ​ទូរសព្ទ​នេះ"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"បញ្ជូន​ការហៅ​ទូរសព្ទ​បន្ត​ដោយ​ប្រើប្រាស់ <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"ឧបករណ៍​បំពង​សំឡេង"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"ខាង​ក្រៅ"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"មិន​ស្គាល់"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"កំពុង​ផ្សាយ​សំឡេង​ទៅឧបករណ៍​ផ្សេងទៀត"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"បញ្ចប់​ការហៅ​ទូរសព្ទ"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"ប្ដូរនៅទីនេះ"</string>
 </resources>
diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml
index 8109de2..cbaa203 100644
--- a/res/values-kn/strings.xml
+++ b/res/values-kn/strings.xml
@@ -53,7 +53,7 @@
     <string name="no_vm_number" msgid="2179959110602180844">"ಧ್ವನಿಮೇಲ್‌ ಸಂಖ್ಯೆಯು ಕಾಣೆಯಾಗಿದೆ"</string>
     <string name="no_vm_number_msg" msgid="1339245731058529388">"ಸಿಮ್‌ ಕಾರ್ಡ್‌ನಲ್ಲಿ ಯಾವುದೇ ಧ್ವನಿಮೇಲ್‌ ಸಂಖ್ಯೆಯನ್ನು ಸಂಗ್ರಹಿಸಿಲ್ಲ."</string>
     <string name="add_vm_number_str" msgid="5179510133063168998">"ಸಂಖ್ಯೆಯನ್ನು ಸೇರಿಸಿ"</string>
-    <string name="change_default_dialer_dialog_title" msgid="5861469279421508060">"<xliff:g id="NEW_APP">%s</xliff:g> ಅನ್ನು ನಿಮ್ಮ ಡಿಫಾಲ್ಟ್ ಫೋನ್ ಅಪ್ಲಿಕೇಶನ್ ಆಗಿ ಮಾಡುವುದೇ?"</string>
+    <string name="change_default_dialer_dialog_title" msgid="5861469279421508060">"<xliff:g id="NEW_APP">%s</xliff:g> ಅನ್ನು ನಿಮ್ಮ ಡಿಫಾಲ್ಟ್ ಫೋನ್ ಆ್ಯಪ್ ಆಗಿ ಮಾಡಬೇಕೆ?"</string>
     <string name="change_default_dialer_dialog_affirmative" msgid="8604665314757739550">"ಡಿಫಾಲ್ಟ್ ಹೊಂದಿಸಿ"</string>
     <string name="change_default_dialer_dialog_negative" msgid="8648669840052697821">"ರದ್ದುಮಾಡಿ"</string>
     <string name="change_default_dialer_warning_message" msgid="8461963987376916114">"<xliff:g id="NEW_APP">%s</xliff:g> ಗೆ ನಿಮ್ಮ ಕರೆಗಳ ಎಲ್ಲಾ ಅಂಶಗಳನ್ನು ನಿಯಂತ್ರಿಸಲು ಮತ್ತು ಕರೆಗಳನ್ನು ಮಾಡಲು ಸಾಧ್ಯವಾಗುತ್ತದೆ. ನೀವು ವಿಶ್ವಾಸವಿರಿಸಿರುವಂತಹ ಅಪ್ಲಿಕೇಶನ್‌ಗಳನ್ನು ಮಾತ್ರ ನಿಮ್ಮ ಡಿಫಾಲ್ಟ್ ಅಪ್ಲಿಕೇಶನ್ ಆಗಿ ಹೊಂದಿಸಬೇಕು."</string>
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"ಹಿನ್ನೆಲೆ ಕರೆಗಳು"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"ಡಿಸ್ಕನೆಕ್ಟ್ ಮಾಡಲಾದ ಕರೆಗಳು"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"ಕ್ರ್ಯಾಶ್ ಆಗಿರುವ ಫೋನ್ ಆ್ಯಪ್‌ಗಳು"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"ಕರೆ ಸ್ಟ್ರೀಮಿಂಗ್"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"ಈ ಕರೆಯನ್ನು ಮಾಡುವುದರಿಂದ ನಿಮ್ಮ <xliff:g id="OTHER_APP">%1$s</xliff:g> ಕರೆಯು ಅಂತ್ಯಗೊಳ್ಳುತ್ತದೆ."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"ಈ ಕರೆ ಮಾಡುವುದು ಹೇಗೆ ಎಂಬುದನ್ನು ಆಯ್ಕೆಮಾಡಿ"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"<xliff:g id="OTHER_APP">%1$s</xliff:g> ಬಳಸಿಕೊಂಡು ಕರೆಯನ್ನು ಮರುನಿರ್ದೇರ್ಶಿಸಿ"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"ಸ್ಪೀಕರ್"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"ಬಾಹ್ಯ"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"ಅಪರಿಚಿತ"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"ಇತರ ಸಾಧನಕ್ಕೆ ಆಡಿಯೊವನ್ನು ಸ್ಟ್ರೀಮ್ ಮಾಡಲಾಗುತ್ತಿದೆ"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"ಹ್ಯಾಂಗ್ ಅಪ್"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"ಇಲ್ಲಿಗೆ ಬದಲಾಯಿಸಿ"</string>
 </resources>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index 6b4c2f1..dc793e3 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"백그라운드 통화"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"연결 해제된 통화"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"다운된 전화 앱"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"통화 스트리밍"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"이 전화를 걸면 현재 <xliff:g id="OTHER_APP">%1$s</xliff:g>에서 진행 중인 통화가 종료됩니다."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"전화 걸 방법 선택"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"<xliff:g id="OTHER_APP">%1$s</xliff:g> 앱으로 전화 리디렉션"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"스피커"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"외부"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"알 수 없음"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"다른 기기로 오디오 스트리밍"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"전화 끊기"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"현재 기기로 전환"</string>
 </resources>
diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml
index aa8ce3e..43def8b 100644
--- a/res/values-ky/strings.xml
+++ b/res/values-ky/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"Фондогу чалуулар"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Ажыратылган чалуулар"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Катадан улам иштебей калган телефон колдонмолору"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"Чалууну берүү"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"Эгер чалып баштасаңыз, <xliff:g id="OTHER_APP">%1$s</xliff:g> чалууңуз аяктайт."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Чалуу жолун тандаңыз"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"<xliff:g id="OTHER_APP">%1$s</xliff:g> аркылуу чалуу багытын буруу"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Динамик"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Тышкы"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Белгисиз"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"Аудио башка түзмөккө берилүүдө"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Чалууну бүтүрүү"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Бул жерге которулуу"</string>
 </resources>
diff --git a/res/values-lo/strings.xml b/res/values-lo/strings.xml
index 45c2b70..ff79144 100644
--- a/res/values-lo/strings.xml
+++ b/res/values-lo/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"ການໂທໃນພື້ນຫຼັງ"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"ສາຍຖືກຕັດແລ້ວ"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"ແອັບໂທລະສັບຂັດຂ້ອງ"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"ການສະຕຣີມການໂທ"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"ການໂທສາຍນີ້ຈະເປັນການສິ້ນສຸດສາຍ <xliff:g id="OTHER_APP">%1$s</xliff:g> ຂອງທ່ານ."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"ເລືອກວິທີໂທສາຍນີ້"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"ປ່ຽນເສັ້ນທາງການໂທໂດຍໃຊ້ <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"ລຳໂພງ"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"ພາຍນອກ"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"ບໍ່ຮູ້ຈັກ"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"ສະຕຣີມສຽງໄປໃສ່ອຸປະກອນອື່ນ"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"ວາງສາຍ"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"ສະຫຼັບບ່ອນນີ້"</string>
 </resources>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index 5e8b1f2..9454431 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"Skambučiai fone"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Skambučiai atjungti"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Užstrigusios telefono programos"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"Srautinis skambučio perdavimas"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"Atliekant šį skambutį bus užbaigtas „<xliff:g id="OTHER_APP">%1$s</xliff:g>“ skambutis."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Pasirinkite, kaip norite skambinti"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Peradresuoti skambutį naudojant programą „<xliff:g id="OTHER_APP">%1$s</xliff:g>“"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Garsiakalbis"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Išoriniai šaltiniai"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Nežinoma"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"Srautinis garso perdavimas į kitą įrenginį"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Baigti skambutį"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Perjungti čia"</string>
 </resources>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index 0433037..5ebdd8e 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"Sarunas fonā"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Pārtrauktie zvani"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Avarējušās tālruņa lietotnes"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"Zvana straumēšana"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"Veicot šo zvanu, tiks beigts zvans lietotnē <xliff:g id="OTHER_APP">%1$s</xliff:g>."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Izvēlieties, kā veikt šo zvanu"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Novirzīt zvanu, izmantojot lietotni <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Skaļrunis"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Ārēja ierīce"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Nezināma ierīce"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"Notiek audio straumēšana uz citu ierīci."</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Beigt zvanu"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Pārslēgties šeit"</string>
 </resources>
diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml
index 4873380..57a3fce 100644
--- a/res/values-mk/strings.xml
+++ b/res/values-mk/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"Повици во заднина"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Прекинати повици"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Паднати апликации за телефон"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"Стримување повик"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"Ако се воспостави повиков, вашиот повик на <xliff:g id="OTHER_APP">%1$s</xliff:g> ќе заврши."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Изберете како да се воспостави повиков"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Пренасочи го повикот со <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -109,7 +110,7 @@
     <string name="phone_settings_number_not_in_contact_txt" msgid="2602249106007265757">"Броеви што не се наведени во „Контакти“"</string>
     <string name="phone_settings_number_not_in_contact_summary_txt" msgid="963327038085718969">"Блокирани броеви што не се наведени во вашите „Контакти“"</string>
     <string name="phone_settings_private_num_txt" msgid="6339272760338475619">"Приватно"</string>
-    <string name="phone_settings_private_num_summary_txt" msgid="6755758240544021037">"Блокирај повикувачи со сокриен број"</string>
+    <string name="phone_settings_private_num_summary_txt" msgid="6755758240544021037">"Блокирај повикувачи со скриен број"</string>
     <string name="phone_settings_payphone_txt" msgid="5003987966052543965">"Телефонска говорница"</string>
     <string name="phone_settings_payphone_summary_txt" msgid="3936631076065563665">"Блокирај повици од телефонски говорници"</string>
     <string name="phone_settings_unknown_txt" msgid="3577926178354772728">"Непознато"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Звучник"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Надворешно"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Непознато"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"Звукот се стримува на друг уред"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Спушти"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Префрли овде"</string>
 </resources>
diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml
index 9e6b8ca..a6d1626 100644
--- a/res/values-ml/strings.xml
+++ b/res/values-ml/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"പശ്ചാത്തല കോളുകൾ"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"വിച്ഛേദിക്കപ്പെട്ട കോളുകൾ"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"ക്രാഷായ ഫോൺ ആപ്പുകൾ"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"കോൾ സ്ട്രീമിംഗ്"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"ഈ കോൾ ചെയ്യുന്നത് നിങ്ങളുടെ <xliff:g id="OTHER_APP">%1$s</xliff:g> കോൾ അവസാനിക്കാനിടയാക്കും."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"ഈ കോൾ എങ്ങനെ ചെയ്യണമെന്ന് തിരഞ്ഞെടുക്കുക"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"<xliff:g id="OTHER_APP">%1$s</xliff:g> ഉപയോഗിച്ച് കോൾ റീഡയറക്‌റ്റ് ചെയ്യുക"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"സ്പീക്കർ"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"എക്സ്റ്റേണൽ സ്‌ട്രീമിംഗ്"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"അജ്ഞാതം"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"ഓഡിയോ മറ്റൊരു ഉപകരണത്തിലേക്ക് സ്‌ട്രീം ചെയ്യുന്നു"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"മാറ്റി വയ്‌ക്കുക"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"ഇവിടേക്ക് മാറുക"</string>
 </resources>
diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml
index 2c90998..70dde8a 100644
--- a/res/values-mn/strings.xml
+++ b/res/values-mn/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"Арын дуудлагууд"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Салсан дуудлагууд"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Гэмтсэн гар утасны аппууд"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"Дуудлага дамжуулах"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"Энэ дуудлагыг хийснээр таны <xliff:g id="OTHER_APP">%1$s</xliff:g> дуудлагыг дуусгана."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Энэ дуудлагыг хэрхэн хийхийг сонгох"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"<xliff:g id="OTHER_APP">%1$s</xliff:g>-г ашиглан дуудлагыг дахин чиглүүлэх"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Чанга яригч"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Гадны"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Тодорхойгүй"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"Бусад төхөөрөмж рүү аудио дамжуулж байна"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Таслах"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Ийшээ сэлгэх"</string>
 </resources>
diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml
index 263433d..c4438ae 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"बॅकग्राउंड कॉल"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"डिस्कनेक्ट केलेले कॉल"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"क्रॅश झालेली फोन ॲप्स"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"कॉल स्ट्रीमिंग"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"हा कॉल केल्याने तुमचा <xliff:g id="OTHER_APP">%1$s</xliff:g> कॉल समाप्त होईल."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"हा कॉल कसा करायचा ते निवडा"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"<xliff:g id="OTHER_APP">%1$s</xliff:g> वापरून कॉल रीडिरेक्ट करा"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"स्पीकर"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"बाह्य"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"अज्ञात"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"ऑडिओ हा दुसऱ्या डिव्हाइसवर स्ट्रीम करत आहे"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"बंद करा"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"येथे स्विच करा"</string>
 </resources>
diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml
index 4a8d554..355502c 100644
--- a/res/values-ms/strings.xml
+++ b/res/values-ms/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"Panggilan latar belakang"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Panggilan diputuskan sambungan"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Aplikasi telefon yang ranap"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"Penstriman panggilan"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"Membuat panggilan ini akan menamatkan panggilan <xliff:g id="OTHER_APP">%1$s</xliff:g> anda."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Pilih cara untuk membuat panggilan ini"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Ubah hala panggilan menggunakan <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Pembesar suara"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Luaran"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Tidak diketahui"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"Penstriman audio pada peranti lain"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Tamatkan panggilan"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Tukar di sini"</string>
 </resources>
diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml
index 3511bca..9ead5f4 100644
--- a/res/values-my/strings.xml
+++ b/res/values-my/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"နောက်ခံမှ ခေါ်ဆိုမှုများ"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"ပြတ်တောက်သွားသည့် ခေါ်ဆိုမှုများ"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"ရပ်တန့်သွားသော ဖုန်းအက်ပ်များ"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"ခေါ်ဆိုမှု တိုက်ရိုက်လွှင့်ခြင်း"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"ဤခေါ်ဆိုမှု ပြုလုပ်ပါက <xliff:g id="OTHER_APP">%1$s</xliff:g> သုံးပြီးပြောနေခြင်းကို ဖြတ်ပစ်ပါမည်။"</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"ဤခေါ်ဆိုမှု ပြုလုပ်ပုံကို ရွေးချယ်ပါ"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"ခေါ်ဆိုမှုကို <xliff:g id="OTHER_APP">%1$s</xliff:g> ဖြင့် တစ်ဆင့်ပြန်ညွှန်ရန်"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"စပီကာ"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"ပြင်ပ"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"မသိ"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"အသံကို အခြားစက်တွင် တိုက်ရိုက်လွှင့်နေသည်"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"ဖုန်းချရန်"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"ဤနေရာသို့ လွှဲပြောင်းရန်"</string>
 </resources>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index fb4dc97..8bebbff 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"Bakgrunnsanrop"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Frakoblede anrop"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Telefonapper som har krasjet"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"Anropsstrømming"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"Samtalen din i <xliff:g id="OTHER_APP">%1$s</xliff:g> avsluttes hvis du foretar dette anropet."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Velg hvordan du vil ringe"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Viderekoble anropet med <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Høyttaler"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Ekstern"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Ukjent"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"Strømmer lyden til en annen enhet"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Legg på"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Flytt hit"</string>
 </resources>
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
index 8c02676..44645dc 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"ब्याकग्राउन्डका कलहरू"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"विच्छेद गरिएका कल"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"फोनमा रहेका क्र्यास भएका एपहरू"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"कल स्ट्रिमिङ"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"यो कल गर्नुले तपाईंको <xliff:g id="OTHER_APP">%1$s</xliff:g> कल अन्त्य गर्दछ।"</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"यो कल गर्ने तरिका छनौट गर्नुहोस्"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"<xliff:g id="OTHER_APP">%1$s</xliff:g> प्रयोग गरी कल रिडाइरेक्ट गर्नुहोस्"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"स्पिकर"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"बाह्य"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"अज्ञात"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"अर्को डिभाइसमा अडियो स्ट्रिम गरिँदै छ"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"कल काट्नुहोस्"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"यहाँ गई बदल्नुहोस्"</string>
 </resources>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index 726ab60..8dfee81 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"Achtergrondgesprekken"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Beëindigde gesprekken"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Gecrashte telefoon-apps"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"Oproepstreaming"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"Als je dit gesprek start, wordt je <xliff:g id="OTHER_APP">%1$s</xliff:g>-gesprek beëindigd."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Kies hoe je dit gesprek wilt plaatsen"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Gesprek omleiden via <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Speaker"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Extern"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Onbekend"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"Audio streamen naar ander apparaat"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Ophangen"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Hiernaartoe schakelen"</string>
 </resources>
diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml
index c25ec86..787711b 100644
--- a/res/values-or/strings.xml
+++ b/res/values-or/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"ବ୍ୟାକ୍‌ଗ୍ରାଉଣ୍ଡ କଲ୍‌ଗୁଡ଼ିକ"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"ବିଚ୍ଛିନ୍ନ କରାଯାଇଥିବା କଲ୍‌ଗୁଡ଼ିକ"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"କ୍ରାସ୍ ହୋଇଥିବା ଫୋନ୍ ଆପ୍ସ"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"କଲ ଷ୍ଟ୍ରିମିଂ"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"ଏହି କଲ୍‌କୁ ସ୍ଥାପନ କରିବା ଦ୍ଵାରା ଆପଣଙ୍କର <xliff:g id="OTHER_APP">%1$s</xliff:g> କଲ୍ ସମାପ୍ତ ହୋ‌ଇଯିବ।"</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"ଏହି କଲ୍ କିପରି କରିବାକୁ ଚାହାନ୍ତି ବାଛନ୍ତୁ"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"<xliff:g id="OTHER_APP">%1$s</xliff:g> ବ୍ୟବହାର କରି କଲ୍ ରିଡାଇରେକ୍ଟ କରନ୍ତୁ"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"ସ୍ପିକର"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"ଏକ୍ସଟର୍ନଲ"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"ଅଜଣା"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"ଅନ୍ୟ ଡିଭାଇସରେ ଅଡିଓ ଷ୍ଟ୍ରିମ କରାଯାଉଛି"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"କଲ ସମାପ୍ତ କରନ୍ତୁ"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"ଏଠାରେ ସୁଇଚ କରନ୍ତୁ"</string>
 </resources>
diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml
index 65073e2..b96a1db 100644
--- a/res/values-pa/strings.xml
+++ b/res/values-pa/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"ਬੈਕਗ੍ਰਾਊਂਡ ਕਾਲਾਂ"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"ਡਿਸਕਨੈਕਟ ਕੀਤੀਆਂ ਕਾਲਾਂ"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"ਕ੍ਰੈਸ਼ ਹੋਈਆਂ ਫ਼ੋਨ ਐਪਾਂ"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"ਕਾਲ ਸਟ੍ਰੀਮਿੰਗ"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"ਇਹ ਕਾਲ ਕਰਨ ਨਾਲ ਤੁਹਾਡੀ <xliff:g id="OTHER_APP">%1$s</xliff:g> ਕਾਲ ਸਮਾਪਤ ਹੋ ਜਾਵੇਗੀ।"</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"ਚੁਣੋ ਕਿ ਕਾਲ ਕਿਵੇਂ ਕਰਨੀ ਹੈ"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"<xliff:g id="OTHER_APP">%1$s</xliff:g> ਦੀ ਵਰਤੋਂ ਕਰਕੇ ਕਾਲ ਰੀਡਾਇਰੈਕਟ ਕਰੋ"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"ਸਪੀਕਰ"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"ਬਾਹਰੀ"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"ਅਗਿਆਤ"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"ਆਡੀਓ ਨੂੰ ਕਿਸੇ ਹੋਰ ਡੀਵਾਈਸ \'ਤੇ ਸਟ੍ਰੀਮ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"ਕਾਲ ਸਮਾਪਤ ਕਰੋ"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"ਇੱਥੇ ਸਵਿੱਚ ਕਰੋ"</string>
 </resources>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index a10d29f..df5d29e 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"Połączenia w tle"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Przerwane połączenia"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Aplikacje telefoniczne po awarii"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"Strumieniowanie połączenia"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"Jeśli zadzwonisz, połączenie w aplikacji <xliff:g id="OTHER_APP">%1$s</xliff:g> zostanie zakończone."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Wybierz, jak chcesz zadzwonić"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Przekieruj połączenie za pomocą aplikacji <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Głośnik"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Zewnętrzne"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Brak informacji"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"Strumieniowanie dźwięku na inne urządzenie"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Rozłącz"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Przełącz tutaj"</string>
 </resources>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index b6db5c4..5fbe1d3 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -65,7 +65,7 @@
     <string name="blocked_numbers" msgid="8322134197039865180">"Números bloqueados"</string>
     <string name="blocked_numbers_msg" msgid="2797422132329662697">"Não irá receber chamadas ou mensagens de texto de números bloqueados."</string>
     <string name="block_number" msgid="3784343046852802722">"Adicionar um número"</string>
-    <string name="unblock_dialog_body" msgid="2723393535797217261">"Pretende desbloquear <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g>?"</string>
+    <string name="unblock_dialog_body" msgid="2723393535797217261">"Quer desbloquear <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g>?"</string>
     <string name="unblock_button" msgid="8732021675729981781">"Desbloquear"</string>
     <string name="add_blocked_dialog_body" msgid="8599974422407139255">"Bloquear chamadas e mensagens de texto de"</string>
     <string name="add_blocked_number_hint" msgid="8769422085658041097">"Número de telefone"</string>
@@ -100,8 +100,9 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"Chamadas em segundo plano"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Chamadas desligadas"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Apps Telefone com falhas"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"Streaming de chamadas"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"Ao efetuar esta chamada, irá terminar a chamada na app <xliff:g id="OTHER_APP">%1$s</xliff:g>."</string>
-    <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Escolha como pretende efetuar esta chamada"</string>
+    <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Escolha como quer efetuar esta chamada"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Redirecionar chamada através de <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
     <string name="alert_place_unredirect_outgoing_call" msgid="2467608535225764006">"Ligar com o meu número de telefone"</string>
     <string name="alert_redirect_outgoing_call_timeout" msgid="5568101425637373060">"Não é possível efetuar uma chamada através da app <xliff:g id="OTHER_APP">%1$s</xliff:g>. Experimente utilizar uma app de redirecionamento de chamadas diferente ou contactar o programador para obter ajuda."</string>
@@ -123,11 +124,14 @@
     <string name="developer_title" msgid="9146088855661672353">"Menu do programador de telecomunicações"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Não é possível atender chamadas durante uma chamada de emergência."</string>
     <string name="cancel" msgid="6733466216239934756">"Cancelar"</string>
-    <string name="back" msgid="6915955601805550206">"Voltar atrás"</string>
+    <string name="back" msgid="6915955601805550206">"Anterior"</string>
     <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"Auricular"</string>
     <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"Bluetooth"</string>
     <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"Auscultadores com microfone integrado com fios"</string>
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Altifalante"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Externo"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Desconhecido"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"A fazer stream de áudio para outro dispositivo"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Desligar"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Mudar aqui"</string>
 </resources>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index a5628c4..a7fc3c7 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -57,7 +57,7 @@
     <string name="change_default_dialer_dialog_affirmative" msgid="8604665314757739550">"Definir padrão"</string>
     <string name="change_default_dialer_dialog_negative" msgid="8648669840052697821">"Cancelar"</string>
     <string name="change_default_dialer_warning_message" msgid="8461963987376916114">"O <xliff:g id="NEW_APP">%s</xliff:g> poderá ligar e controlar todos os aspectos das chamadas. Defina como aplicativo Telefone padrão somente aqueles em que você confia."</string>
-    <string name="change_default_call_screening_dialog_title" msgid="5365787219927262408">"Usar o <xliff:g id="NEW_APP">%s</xliff:g> como seu app de seleção de chamadas padrão?"</string>
+    <string name="change_default_call_screening_dialog_title" msgid="5365787219927262408">"Usar o <xliff:g id="NEW_APP">%s</xliff:g> como seu app de filtro de ligações padrão?"</string>
     <string name="change_default_call_screening_warning_message_for_disable_old_app" msgid="2039830033533243164">"O <xliff:g id="OLD_APP">%s</xliff:g> não selecionará mais as chamadas."</string>
     <string name="change_default_call_screening_warning_message" msgid="9020537562292754269">"O <xliff:g id="NEW_APP">%s</xliff:g> poderá ver as informações sobre os autores das chamadas que não estão entre seus contatos e bloqueá-los. Defina como app de seleção de chamadas padrão somente aqueles em que você confia."</string>
     <string name="change_default_call_screening_dialog_affirmative" msgid="7162433828280058647">"Definir padrão"</string>
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"Chamadas em segundo plano"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Chamadas desconectadas"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Falha com os apps de telefone"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"Streaming de ligação"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"Se você ligar agora, sua chamada será encerrada no <xliff:g id="OTHER_APP">%1$s</xliff:g>."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Escolha como fazer esta chamada"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Redirecionar a chamada usando o <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Alto-falante"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Externo"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Desconhecido"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"Fazendo streaming de áudio para outro dispositivo"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Desligar"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Mudar para este dispositivo"</string>
 </resources>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index 2332d4d..8e485d0 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"Apeluri în fundal"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Apeluri deconectate"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Aplicații pentru telefon blocate"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"Streaming de apeluri"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"Dacă inițiezi acest apel, cel din <xliff:g id="OTHER_APP">%1$s</xliff:g> va fi încheiat."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Alege cum vrei să inițiezi apelul"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Redirecționezi apelul folosind <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Difuzor"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Extern"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Necunoscut"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"Streaming audio pe alt dispozitiv"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Încheie apelul"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Treci la alt cont aici"</string>
 </resources>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index 139108d..67ab2e9 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"Фоновые вызовы"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Прекращенные вызовы"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Приложения для телефона, работа которых прекращена из-за ошибки"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"Потоковая передача звонков"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"Если вы начнете этот звонок, вызов в <xliff:g id="OTHER_APP">%1$s</xliff:g> будет завершен."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Выберите, как хотите позвонить."</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Перенаправить вызов с использованием <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Динамик"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Внешнее устройство"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Неизвестно"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"Потоковая передача аудио на другое устройство"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Завершить"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Переключиться"</string>
 </resources>
diff --git a/res/values-si/strings.xml b/res/values-si/strings.xml
index e3faf49..71442e0 100644
--- a/res/values-si/strings.xml
+++ b/res/values-si/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"පසුබිම් ඇමතුම්"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"විසන්ධි කළ ඇමතුම්"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"බිඳ වැටුණු දුරකථන යෙදුම්"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"ඇමතුම් ප්‍රවාහය"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"මෙම ඇමතුම ගැනීම ඔබේ <xliff:g id="OTHER_APP">%1$s</xliff:g> ඇමතුම අවසන් කරනු ඇත."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"මෙම ඇමතුම ගන්නා ආකාරය තෝරන්න"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"<xliff:g id="OTHER_APP">%1$s</xliff:g> භාවිතයෙන් ඇමතුම ප්‍රතියොමු කරන්න"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"ස්පීකරය"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"බාහිර"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"නොදනී"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"වෙනත් උපාංගයකට ශ්‍රව්‍ය ප්‍රවාහ කිරීම"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"විසන්ධි කරන්න"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"මෙතැනට මාරු වෙන්න"</string>
 </resources>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index f7606ec..a001130 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"Hovory na pozadí"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Zrušené hovory"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Zrútené telefónne aplikácie"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"Streamovanie hovoru"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"Ak uskutočníte tento hovor, hovor cez <xliff:g id="OTHER_APP">%1$s</xliff:g> bude ukončený."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Vyberte, ako chcete tento hovor uskutočniť"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Presmerovať hovor cez aplikáciu <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Reproduktor"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Externé"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Neznáme"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"Streamovanie zvuku do iného zariadenia"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Zložiť"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Prepnúť sem"</string>
 </resources>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index 138524b..994bc7e 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"Klici v ozadju"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Prekinjeni klici"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Zrušene aplikacije za klicanje"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"Pretočno predvajanje klicev"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"Če opravite ta klic, bo končan klic prek aplikacije <xliff:g id="OTHER_APP">%1$s</xliff:g>."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Izberite, kako želite opraviti klic"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Preusmeri klic z aplikacijo <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Zvočnik"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Zunanje"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Neznano"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"Pretočno predvajanje zvoka v drugo napravo"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Prekini klic"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Preklopi sem"</string>
 </resources>
diff --git a/res/values-sq/strings.xml b/res/values-sq/strings.xml
index 0a36a40..89ae852 100644
--- a/res/values-sq/strings.xml
+++ b/res/values-sq/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"Telefonatat në sfond"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Telefonatat e shkëputura"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Aplikacionet e telefonit që kanë pësuar ndërprerje aksidentale"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"Transmetimi i telefonatave"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"Kryerja e kësaj telefonate do të mbyllë telefonatën tënde në <xliff:g id="OTHER_APP">%1$s</xliff:g>."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Zgjidh se si do ta kryesh këtë telefonatë"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Ridrejtoje telefonatën duke përdorur <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Altoparlant"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"E jashtme"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"E panjohur"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"Po transmetohet audioja te një pajisje tjetër"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Mbyll"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Ndërro këtu"</string>
 </resources>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index b846841..1134380 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"Позиви у позадини"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Прекинути позиви"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Апликације за телефонирање које су отказале"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"Стримовање позива"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"Ако упутите овај позив, завршићете <xliff:g id="OTHER_APP">%1$s</xliff:g> позив."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Изаберите како желите да упутите овај позив"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Преусмери позив помоћу: <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Звучник"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Екстерни"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Непознато"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"Звук се стримује на други уређај"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Прекини везу"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Пребаци овде"</string>
 </resources>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index acc6dc6..c6f6ec9 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"Bakgrundssamtal"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Frånkopplade samtal"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Kraschade telefonappar"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"Samtalsstreaming"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"Ringer du det här samtalet avslutas samtalet i <xliff:g id="OTHER_APP">%1$s</xliff:g>."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Välj hur du vill ringa samtalet"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Omdirigera samtal med <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Högtalare"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Extern"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Okänd"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"Streama ljud till en annan enhet"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Lägg på"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Koppla hit"</string>
 </resources>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index 84f7294..1b49990 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"Simu za chinichini"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Simu zilizokatwa"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Programu za simu zilizoacha kufanya kazi"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"Utiririshaji wa simu"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"Ukipiga simu hii, simu yako kwenye <xliff:g id="OTHER_APP">%1$s</xliff:g> itakatwa."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Chagua jinsi utakavyopiga simu hii"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Elekeza simu ukitumia <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Spika"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Ya nje"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Haijulikani"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"Inatiririsha sauti kwenye kifaa kingine"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Kata simu"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Badili hapa"</string>
 </resources>
diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml
index 18b5861..9f37d87 100644
--- a/res/values-ta/strings.xml
+++ b/res/values-ta/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"பின்னணி அழைப்புகள்"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"துண்டிக்கப்பட்ட அழைப்புகள்"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"சிதைவடைந்த மொபைல் ஆப்ஸ்"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"அழைப்பு ஸ்ட்ரீமிங்"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"புதிய அழைப்பைச் செய்தால், செயலில் உள்ள <xliff:g id="OTHER_APP">%1$s</xliff:g> அழைப்பு துண்டிக்கப்படும்."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"இந்த அழைப்பை எவ்வாறு மேற்கொள்ள வேண்டும் எனத் தேர்ந்தெடுக்கவும்"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"<xliff:g id="OTHER_APP">%1$s</xliff:g>ஐப் பயன்படுத்தி அழைப்பைத் திருப்பி விடு"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"ஸ்பீக்கர்"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"வெளிப்புறச் சாதனம்"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"தெரியவில்லை"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"வேறு சாதனத்திற்கு ஆடியோவை ஸ்ட்ரீம் செய்கிறது"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"அழைப்பைத் துண்டி"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"இங்கே மாற்று"</string>
 </resources>
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
index 98115db..8f8a23e 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -51,7 +51,7 @@
     <string name="outgoing_call_error_no_phone_number_supplied" msgid="7665135102566099778">"కాల్ చేయడానికి, చెల్లుబాటు అయ్యే నంబర్‌ను నమోదు చేయండి."</string>
     <string name="duplicate_video_call_not_allowed" msgid="5754746140185781159">"ఈ సమయంలో కాల్‌ను జోడించడం సాధ్యపడదు."</string>
     <string name="no_vm_number" msgid="2179959110602180844">"వాయిస్ మెయిల్ నంబర్ లేదు"</string>
-    <string name="no_vm_number_msg" msgid="1339245731058529388">"సిమ్ కార్డులో వాయిస్ మెయిల్ నంబర్ ఏదీ నిల్వ చేయబడలేదు."</string>
+    <string name="no_vm_number_msg" msgid="1339245731058529388">"సిమ్ కార్డులో వాయిస్ మెయిల్ నంబర్ ఏదీ స్టోరేజ్‌ చేయబడలేదు."</string>
     <string name="add_vm_number_str" msgid="5179510133063168998">"నంబర్‌ను జోడించండి"</string>
     <string name="change_default_dialer_dialog_title" msgid="5861469279421508060">"<xliff:g id="NEW_APP">%s</xliff:g>ను మీ ఆటోమేటిక్ ఫోన్ యాప్‌గా చేయాలా?"</string>
     <string name="change_default_dialer_dialog_affirmative" msgid="8604665314757739550">"ఆటోమేటిక్‌గా సెట్ చేయండి"</string>
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"బ్యాక్‌గ్రౌండ్ కాల్స్"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"డిస్‌కనెక్ట్ చేసిన కాల్స్"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"క్రాష్ అయిన ఫోన్ యాప్స్"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"కాల్ స్ట్రీమింగ్"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"ఈ కాల్ చేయడం వలన మీ <xliff:g id="OTHER_APP">%1$s</xliff:g> కాల్ ముగుస్తుంది."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"ఈ కాల్ ఎలా చేయాలో ఎంచుకోండి"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"<xliff:g id="OTHER_APP">%1$s</xliff:g> ఉపయోగించి కాల్ మళ్లించు"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"స్పీకర్"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"వెలుపలి"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"తెలియదు"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"ఆడియోను ఇతర పరికరానికి స్ట్రీమింగ్ చేయండి"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"ముగించండి"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"ఇక్కడకు స్విచ్ అవ్వండి"</string>
 </resources>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index 678af2d..b8dc9f0 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"การโทรในเบื้องหลัง"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"สายถูกตัด"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"แอปโทรศัพท์ขัดข้อง"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"การสตรีมการโทร"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"การโทรออกนี้จะวางสายใน <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"เลือกวิธีโทรออก"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"โอนสายโดยใช้ <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"ลำโพง"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"ภายนอก"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"ไม่ทราบ"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"กำลังสตรีมเสียงไปยังอุปกรณ์อื่นๆ"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"วางสาย"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"เปลี่ยนที่นี่"</string>
 </resources>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index 495c191..91e1b33 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"Mga tawag sa background"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Nadiskonektang mga tawag"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Nag-crash na mga phone app"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"Pag-stream ng tawag"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"Tatapusin ng pagtawag na ito ang iyong tawag sa <xliff:g id="OTHER_APP">%1$s</xliff:g>."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Piliin kung paano gagawin ang tawag na ito"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"I-redirect ang tawag gamit ang <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Speaker"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"External"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Hindi Alam"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"Naka-stream ang audio sa ibang device"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Mag-hang up"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Lumipat dito"</string>
 </resources>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index 1309682..0aa2e20 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"Arka plandaki aramalar"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Bağlantısı kesilen aramalar"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Kilitlenen telefon uygulamaları"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"Görüşme aktarımı"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"Bu çağrıyı yaptığınızda <xliff:g id="OTHER_APP">%1$s</xliff:g> çağrınız sona erecek."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Bu aramanın nasıl yapılacağını seçin"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"<xliff:g id="OTHER_APP">%1$s</xliff:g> uygulamasını kullanarak aramayı yönlendir"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Hoparlör"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Harici"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Bilinmiyor"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"Ses başka bir cihaza aktarılıyor"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Görüşmeyi bitir"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Buraya dön"</string>
 </resources>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index 7b81d25..a4d01d1 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"Виклики у фоновому режимі"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Припинені виклики"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Збої в додатках для дзвінків"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"Трансляція дзвінків"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"Якщо здійснити цей виклик, буде завершено виклик у додатку <xliff:g id="OTHER_APP">%1$s</xliff:g>."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Виберіть, як здійснити цей виклик"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Переспрямувати через додаток <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Гучний зв’язок"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Зовнішні джерела"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Невідомо"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"Аудіо транслюється на інший пристрій"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Завершити"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Перевести сюди"</string>
 </resources>
diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml
index 7033f36..6649f42 100644
--- a/res/values-ur/strings.xml
+++ b/res/values-ur/strings.xml
@@ -27,8 +27,8 @@
     <string name="notification_missedCall_call_back" msgid="7900333283939789732">"واپس کال کریں"</string>
     <string name="notification_missedCall_message" msgid="4054698824390076431">"پیغام"</string>
     <string name="notification_disconnectedCall_title" msgid="1790131923692416928">"کال غیر منسلک کر دیا گیا"</string>
-    <string name="notification_disconnectedCall_body" msgid="600491714584417536">"ہنگامی کال کی وجہ سے <xliff:g id="CALLER">%s</xliff:g> کی کال کو غیر منسلک کر دیا گیا ہے۔"</string>
-    <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"ہنگامی کال لگائے جانے کی وجہ سے آپ کی کال غیر منسلک ہوگئی ہے۔"</string>
+    <string name="notification_disconnectedCall_body" msgid="600491714584417536">"ایمرجنسی کال کی وجہ سے <xliff:g id="CALLER">%s</xliff:g> کی کال کو غیر منسلک کر دیا گیا ہے۔"</string>
+    <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"ایمرجنسی کال لگائے جانے کی وجہ سے آپ کی کال غیر منسلک ہوگئی ہے۔"</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"پس منظر کی کال"</string>
     <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> پس منظر میں کال پر کارروائی کر رہی ہے۔ یہ ایپ کال کے دوران آواز تک رسائی حاصل اور چلا سکتی ہے۔"</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> نے جواب دینا بند کر دیا"</string>
@@ -46,7 +46,7 @@
     <string name="respond_via_sms_confirmation_format" msgid="2932395476561267842">"پیغام <xliff:g id="PHONE_NUMBER">%s</xliff:g> کو بھیج دیا گیا۔"</string>
     <string name="respond_via_sms_failure_format" msgid="5198680980054596391">"<xliff:g id="PHONE_NUMBER">%s</xliff:g> پر پیغام نہیں بھیجا جا سکا۔"</string>
     <string name="enable_account_preference_title" msgid="6949224486748457976">"کالنگ اکاؤنٹس"</string>
-    <string name="outgoing_call_not_allowed_user_restriction" msgid="3424338207838851646">"صرف ہنگامی کالز کی اجازت ہے۔"</string>
+    <string name="outgoing_call_not_allowed_user_restriction" msgid="3424338207838851646">"صرف ایمرجنسی کالز کی اجازت ہے۔"</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="8590468836581488679">"یہ ایپلی کیشن فون کی اجازت کے بغیر باہر جانے والی کالیں نہیں کر سکتی۔"</string>
     <string name="outgoing_call_error_no_phone_number_supplied" msgid="7665135102566099778">"کال کرنے کیلئے، ایک درست نمبر درج کریں۔"</string>
     <string name="duplicate_video_call_not_allowed" msgid="5754746140185781159">"اس وقت کال شامل نہیں کی جا سکتی ہے۔"</string>
@@ -73,11 +73,11 @@
     <string name="non_primary_user" msgid="315564589279622098">"صرف آلہ کا مالک مسدود کردہ نمبرز کو دیکھ سکتا ہے اور ان کا نظم کر سکتا ہے۔"</string>
     <string name="delete_icon_description" msgid="5335959254954774373">"غیر مسدود کریں"</string>
     <string name="blocked_numbers_butter_bar_title" msgid="582982373755950791">"مسدود کرنا عارضی طور پر آف ہے"</string>
-    <string name="blocked_numbers_butter_bar_body" msgid="1261213114919301485">"جب آپ کوئی ایمرجنسی نمبر ڈائل کرتے یا اسے متن بھیجتے ہیں تو انسداد کو آ‌ف کر دیا جاتا ہے تاکہ ہنگامی سروسز آپ سے رابطہ کر سکیں۔"</string>
+    <string name="blocked_numbers_butter_bar_body" msgid="1261213114919301485">"جب آپ کوئی ایمرجنسی نمبر ڈائل کرتے یا اسے متن بھیجتے ہیں تو انسداد کو آ‌ف کر دیا جاتا ہے تاکہ ایمرجنسی سروسز آپ سے رابطہ کر سکیں۔"</string>
     <string name="blocked_numbers_butter_bar_button" msgid="2704456308072489793">"اب دوبارہ فعال کریں"</string>
     <string name="blocked_numbers_number_blocked_message" msgid="4314736791180919167">"<xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g> مسدود کر دیا گیا"</string>
     <string name="blocked_numbers_number_unblocked_message" msgid="2933071624674945601">"<xliff:g id="UNBLOCKED_NUMBER">%1$s</xliff:g> غیر مسدود کر دیا گیا"</string>
-    <string name="blocked_numbers_block_emergency_number_message" msgid="4198550501500893890">"ہنگامی نمبر مسدود کرنے سے قاصر۔"</string>
+    <string name="blocked_numbers_block_emergency_number_message" msgid="4198550501500893890">"ایمرجنسی نمبر مسدود کرنے سے قاصر۔"</string>
     <string name="blocked_numbers_number_already_blocked_message" msgid="2301270825735665458">"<xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g> پہلے ہی مسدود ہے۔"</string>
     <string name="toast_personal_call_msg" msgid="5817631570381795610">"کال کرنے کیلئے ذاتی ڈائلر استعمال ہو رہا ہے"</string>
     <string name="notification_incoming_call" msgid="1233481138362230894">"<xliff:g id="CALL_FROM">%2$s</xliff:g> کی جانب سے <xliff:g id="CALL_VIA">%1$s</xliff:g> کال"</string>
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"پس منظر کی کالز"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"منقطع کالز"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"کریشڈ فون ایپس"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"کال اسٹریمنگ"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"یہ کال کرنے سے <xliff:g id="OTHER_APP">%1$s</xliff:g> کال ختم ہو جائے گی۔"</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"یہ کال کرنے کا طریقہ منتخب کریں"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"<xliff:g id="OTHER_APP">%1$s</xliff:g> کے ذریعے کال کو ریڈائریکٹ کریں"</string>
@@ -118,10 +119,10 @@
     <string name="phone_settings_unavailable_summary_txt" msgid="8221686031038282633">"کالز کو مسدود کریں جہاں یہ نمبر دستیاب نہ ہو"</string>
     <string name="phone_strings_call_blocking_turned_off_notification_title_txt" msgid="2895809176537908791">"کال مسدود کرنا"</string>
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="1713632946174016619">"کال مسدود کرنا غیر فعال ہو گیا ہے"</string>
-    <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="6629412508584507377">"ہنگامی کال کی گئی"</string>
-    <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="3140411733995271126">"ہنگامی حالت میں جواب دہندگان کو آپ سے رابطہ کرنے کی اجازت دینے کیلئے کال مسدود کرنا غیر فعال ہو گیا ہے۔"</string>
-    <string name="developer_title" msgid="9146088855661672353">"ٹیلی کام ڈویلپر مینو"</string>
-    <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"ہنگامی کال کے دوران کالز نہیں لی جائیں گی۔"</string>
+    <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="6629412508584507377">"ایمرجنسی کال کی گئی"</string>
+    <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="3140411733995271126">"ایمرجنسی حالت میں جواب دہندگان کو آپ سے رابطہ کرنے کی اجازت دینے کیلئے کال مسدود کرنا غیر فعال ہو گیا ہے۔"</string>
+    <string name="developer_title" msgid="9146088855661672353">"ٹیلی کام ڈویلپر مینیو"</string>
+    <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"ایمرجنسی کال کے دوران کالز نہیں لی جائیں گی۔"</string>
     <string name="cancel" msgid="6733466216239934756">"منسوخ کریں"</string>
     <string name="back" msgid="6915955601805550206">"پیچھے"</string>
     <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"ایئر پیس"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"اسپیکر"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"خارجی"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"نامعلوم"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"دوسرے آلے پر آڈیو کی سلسلہ بندی کی جا رہی ہے"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"منقطع کریں"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"یہاں سوئچ کریں"</string>
 </resources>
diff --git a/res/values-uz/strings.xml b/res/values-uz/strings.xml
index 688b6a7..c6805ea 100644
--- a/res/values-uz/strings.xml
+++ b/res/values-uz/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"Orqa fondagi chaqiruvlar"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Tugatilgan chaqiruvlar"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Ishdan chiqqan telefon ilovalari"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"Chaqiruv translatsiyasi"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"Bu qo‘ng‘iroqni amalga oshirsangiz, <xliff:g id="OTHER_APP">%1$s</xliff:g> qo‘ng‘irog‘i tugatiladi."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Telefon qilish usulini tanlang"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Chaqiruv <xliff:g id="OTHER_APP">%1$s</xliff:g> orqali qayta uzatilsin"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Karnay"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Tashqi"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Noaniq"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"Audio translatsiyani boshqa qurilmaga olish"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Tugatish"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Shu yerga olish"</string>
 </resources>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index 0920d3b..5ae2e79 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"Cuộc gọi trong nền"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Các cuộc gọi bị ngắt kết nối"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Các ứng dụng điện thoại bị lỗi"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"Truyền trực tuyến cuộc gọi"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"Thực hiện cuộc gọi này sẽ kết thúc cuộc gọi <xliff:g id="OTHER_APP">%1$s</xliff:g> của bạn."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Chọn cách thực hiện cuộc gọi này"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Chuyển hướng cuộc gọi bằng cách sử dụng <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Loa"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Bên ngoài"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Không xác định"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"Đang truyền trực tuyến âm thanh tới thiết bị khác"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Kết thúc"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Chuyển đổi tại đây"</string>
 </resources>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index b926e55..1ef0a55 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -45,7 +45,7 @@
     <string name="respond_via_sms_edittext_dialog_title" msgid="6579353156073272157">"快速回复"</string>
     <string name="respond_via_sms_confirmation_format" msgid="2932395476561267842">"讯息已发送至 <xliff:g id="PHONE_NUMBER">%s</xliff:g>。"</string>
     <string name="respond_via_sms_failure_format" msgid="5198680980054596391">"未能将信息发送到 <xliff:g id="PHONE_NUMBER">%s</xliff:g>。"</string>
-    <string name="enable_account_preference_title" msgid="6949224486748457976">"通话帐号"</string>
+    <string name="enable_account_preference_title" msgid="6949224486748457976">"通话账号"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="3424338207838851646">"只能拨打紧急呼救电话。"</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="8590468836581488679">"此应用没有电话权限,无法拨出电话。"</string>
     <string name="outgoing_call_error_no_phone_number_supplied" msgid="7665135102566099778">"要拨打电话,请输入有效的电话号码。"</string>
@@ -90,7 +90,7 @@
     <string name="answering_ends_other_managed_video_call" msgid="1988508241432031327">"如果接听此来电,您当前的视频通话会中断。"</string>
     <string name="answer_incoming_call" msgid="2045888814782215326">"接听"</string>
     <string name="decline_incoming_call" msgid="922147089348451310">"拒接"</string>
-    <string name="cant_call_due_to_no_supported_service" msgid="1635626384149947077">"无法拨出电话,因为没有通话帐号支持拨打这类电话。"</string>
+    <string name="cant_call_due_to_no_supported_service" msgid="1635626384149947077">"无法拨出电话,因为没有通话账号支持拨打这类电话。"</string>
     <string name="cant_call_due_to_ongoing_call" msgid="8004235328451385493">"由于当前正在进行 <xliff:g id="OTHER_CALL">%1$s</xliff:g> 通话,因此无法拨打电话。"</string>
     <string name="cant_call_due_to_ongoing_calls" msgid="6379163795277824868">"由于当前正在进行 <xliff:g id="OTHER_CALL">%1$s</xliff:g> 通话,因此无法拨打电话。"</string>
     <string name="cant_call_due_to_ongoing_unknown_call" msgid="8243532328969433172">"由于当前正在通过其他应用通话,因此无法拨打电话。"</string>
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"后台通话"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"通话中断"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"手机应用崩溃"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"通话流式传输"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"拨打此电话将导致<xliff:g id="OTHER_APP">%1$s</xliff:g>通话结束。"</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"选择拨打此电话的方式"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"使用<xliff:g id="OTHER_APP">%1$s</xliff:g>转移呼叫"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"免提"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"外部"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"未知"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"将音频流式传输到其他设备"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"挂断"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"在此处切换"</string>
 </resources>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index e7b1a07..0140f26 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -28,11 +28,11 @@
     <string name="notification_missedCall_message" msgid="4054698824390076431">"短訊"</string>
     <string name="notification_disconnectedCall_title" msgid="1790131923692416928">"已中斷的通話"</string>
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"因撥打緊急電話緣故,與<xliff:g id="CALLER">%s</xliff:g>的通話已中斷。"</string>
-    <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"因撥打緊急電話緣故,您的通話已中斷。"</string>
+    <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"因撥打緊急電話緣故,你的通話已中斷。"</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"背景通話"</string>
     <string name="notification_audioProcessing_body" msgid="8811420157964118913">"「<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g>」正在處理背景中的通話。這個應用程式或會存取通話,或是在通話中播放音訊。"</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g>已停止回應"</string>
-    <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"您使用了裝置隨付的手機應用程式來通話"</string>
+    <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"你使用了裝置隨付的手機應用程式來通話"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"通話已靜音。"</string>
     <string name="accessibility_speakerphone_enabled" msgid="555386652061614267">"擴音器已啟用"</string>
     <string name="respond_via_sms_canned_response_1" msgid="6332561460870382561">"我現在不方便通話,有什麼事呢?"</string>
@@ -56,14 +56,14 @@
     <string name="change_default_dialer_dialog_title" msgid="5861469279421508060">"要將<xliff:g id="NEW_APP">%s</xliff:g>設為預設電話應用程式嗎?"</string>
     <string name="change_default_dialer_dialog_affirmative" msgid="8604665314757739550">"設為預設"</string>
     <string name="change_default_dialer_dialog_negative" msgid="8648669840052697821">"取消"</string>
-    <string name="change_default_dialer_warning_message" msgid="8461963987376916114">"「<xliff:g id="NEW_APP">%s</xliff:g>」將可撥打電話並控制所有相關功能。只有您信任的應用程式,才應設為預設手機應用程式。"</string>
+    <string name="change_default_dialer_warning_message" msgid="8461963987376916114">"「<xliff:g id="NEW_APP">%s</xliff:g>」將可撥打電話並控制所有相關功能。只有你信任的應用程式,才應設為預設手機應用程式。"</string>
     <string name="change_default_call_screening_dialog_title" msgid="5365787219927262408">"要將「<xliff:g id="NEW_APP">%s</xliff:g>」設為預設來電過濾應用程式嗎?"</string>
     <string name="change_default_call_screening_warning_message_for_disable_old_app" msgid="2039830033533243164">"「<xliff:g id="OLD_APP">%s</xliff:g>」無法再篩選來電。"</string>
-    <string name="change_default_call_screening_warning_message" msgid="9020537562292754269">"「<xliff:g id="NEW_APP">%s</xliff:g>」將可查看通訊錄以外來電者的相關資訊,並封鎖這些來電。只有您信任的應用程式才適合設為預設來電過濾應用程式。"</string>
+    <string name="change_default_call_screening_warning_message" msgid="9020537562292754269">"「<xliff:g id="NEW_APP">%s</xliff:g>」將可查看通訊錄以外來電者的相關資訊,並封鎖這些來電。只有你信任的應用程式才適合設為預設來電過濾應用程式。"</string>
     <string name="change_default_call_screening_dialog_affirmative" msgid="7162433828280058647">"設為預設"</string>
     <string name="change_default_call_screening_dialog_negative" msgid="1839266125623106342">"取消"</string>
     <string name="blocked_numbers" msgid="8322134197039865180">"已封鎖的號碼"</string>
-    <string name="blocked_numbers_msg" msgid="2797422132329662697">"您不會收到已封鎖號碼的來電或短訊。"</string>
+    <string name="blocked_numbers_msg" msgid="2797422132329662697">"你不會收到已封鎖號碼的來電或短訊。"</string>
     <string name="block_number" msgid="3784343046852802722">"新增號碼"</string>
     <string name="unblock_dialog_body" msgid="2723393535797217261">"要解除封鎖 <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g> 嗎?"</string>
     <string name="unblock_button" msgid="8732021675729981781">"解除封鎖"</string>
@@ -73,7 +73,7 @@
     <string name="non_primary_user" msgid="315564589279622098">"只有裝置擁有者可查看和管理已封鎖的號碼。"</string>
     <string name="delete_icon_description" msgid="5335959254954774373">"解除封鎖"</string>
     <string name="blocked_numbers_butter_bar_title" msgid="582982373755950791">"暫時關閉封鎖功能"</string>
-    <string name="blocked_numbers_butter_bar_body" msgid="1261213114919301485">"在您撥打或發短訊至緊急號碼後,封鎖功能會停用,以確保緊急服務可與您聯絡。"</string>
+    <string name="blocked_numbers_butter_bar_body" msgid="1261213114919301485">"在你撥打或發短訊至緊急號碼後,封鎖功能會停用,以確保緊急服務可與你聯絡。"</string>
     <string name="blocked_numbers_butter_bar_button" msgid="2704456308072489793">"立即重新啟用"</string>
     <string name="blocked_numbers_number_blocked_message" msgid="4314736791180919167">"已封鎖 <xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g>"</string>
     <string name="blocked_numbers_number_unblocked_message" msgid="2933071624674945601">"已解除對 <xliff:g id="UNBLOCKED_NUMBER">%1$s</xliff:g> 的封鎖"</string>
@@ -82,17 +82,17 @@
     <string name="toast_personal_call_msg" msgid="5817631570381795610">"使用個人撥號器撥打電話"</string>
     <string name="notification_incoming_call" msgid="1233481138362230894">"來自<xliff:g id="CALL_FROM">%2$s</xliff:g>的 <xliff:g id="CALL_VIA">%1$s</xliff:g> 通話"</string>
     <string name="notification_incoming_video_call" msgid="5795968314037063900">"來自<xliff:g id="CALL_FROM">%2$s</xliff:g>的 <xliff:g id="CALL_VIA">%1$s</xliff:g> 視像通話"</string>
-    <string name="answering_ends_other_call" msgid="8653544281903986641">"如果接聽,您的 <xliff:g id="CALL_VIA">%1$s</xliff:g> 通話將會結束"</string>
-    <string name="answering_ends_other_calls" msgid="3702302838456922535">"如果接聽,您的 <xliff:g id="CALL_VIA">%1$s</xliff:g> 通話將會結束"</string>
-    <string name="answering_ends_other_video_call" msgid="8572022039304239958">"如果接聽,您的 <xliff:g id="CALL_VIA">%1$s</xliff:g> 視像通話將會結束"</string>
-    <string name="answering_ends_other_managed_call" msgid="4031778317409881805">"如果接聽,您進行中的通話將會結束"</string>
-    <string name="answering_ends_other_managed_calls" msgid="3974069768615307659">"如果接聽,您進行中的通話將會結束"</string>
-    <string name="answering_ends_other_managed_video_call" msgid="1988508241432031327">"如果接聽,您進行中的視像通話將會結束"</string>
+    <string name="answering_ends_other_call" msgid="8653544281903986641">"如果接聽,你的 <xliff:g id="CALL_VIA">%1$s</xliff:g> 通話將會結束"</string>
+    <string name="answering_ends_other_calls" msgid="3702302838456922535">"如果接聽,你的 <xliff:g id="CALL_VIA">%1$s</xliff:g> 通話將會結束"</string>
+    <string name="answering_ends_other_video_call" msgid="8572022039304239958">"如果接聽,你的 <xliff:g id="CALL_VIA">%1$s</xliff:g> 視像通話將會結束"</string>
+    <string name="answering_ends_other_managed_call" msgid="4031778317409881805">"如果接聽,你進行中的通話將會結束"</string>
+    <string name="answering_ends_other_managed_calls" msgid="3974069768615307659">"如果接聽,你進行中的通話將會結束"</string>
+    <string name="answering_ends_other_managed_video_call" msgid="1988508241432031327">"如果接聽,你進行中的視像通話將會結束"</string>
     <string name="answer_incoming_call" msgid="2045888814782215326">"接聽"</string>
     <string name="decline_incoming_call" msgid="922147089348451310">"拒絕"</string>
     <string name="cant_call_due_to_no_supported_service" msgid="1635626384149947077">"沒有通話帳戶支援這類通話,因此無法撥打電話。"</string>
-    <string name="cant_call_due_to_ongoing_call" msgid="8004235328451385493">"由於您已在進行 <xliff:g id="OTHER_CALL">%1$s</xliff:g> 通話,因此無法撥打電話。"</string>
-    <string name="cant_call_due_to_ongoing_calls" msgid="6379163795277824868">"由於您已在進行 <xliff:g id="OTHER_CALL">%1$s</xliff:g> 通話,因此無法撥打電話。"</string>
+    <string name="cant_call_due_to_ongoing_call" msgid="8004235328451385493">"由於你已在進行 <xliff:g id="OTHER_CALL">%1$s</xliff:g> 通話,因此無法撥打電話。"</string>
+    <string name="cant_call_due_to_ongoing_calls" msgid="6379163795277824868">"由於你已在進行 <xliff:g id="OTHER_CALL">%1$s</xliff:g> 通話,因此無法撥打電話。"</string>
     <string name="cant_call_due_to_ongoing_unknown_call" msgid="8243532328969433172">"由於已在另一個應用程式中進行通話,因此無法撥打電話。"</string>
     <string name="notification_channel_incoming_call" msgid="5245550964701715662">"來電"</string>
     <string name="notification_channel_missed_call" msgid="7168893015283909012">"未接來電"</string>
@@ -100,11 +100,12 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"背景通話"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"已中斷的通話"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"當機的手機應用程式"</string>
-    <string name="alert_outgoing_call" msgid="5319895109298927431">"如果撥打此電話,您的 <xliff:g id="OTHER_APP">%1$s</xliff:g> 通話將會結束。"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"串流通話"</string>
+    <string name="alert_outgoing_call" msgid="5319895109298927431">"如果撥打此電話,你的 <xliff:g id="OTHER_APP">%1$s</xliff:g> 通話將會結束。"</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"選擇如何撥打此電話"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"使用「<xliff:g id="OTHER_APP">%1$s</xliff:g>」將通話重新導向"</string>
     <string name="alert_place_unredirect_outgoing_call" msgid="2467608535225764006">"使用我的電話號碼撥打"</string>
-    <string name="alert_redirect_outgoing_call_timeout" msgid="5568101425637373060">"<xliff:g id="OTHER_APP">%1$s</xliff:g>無法撥打電話。建議您使用其他通話重新導向應用程式,或向開發人員求助。"</string>
+    <string name="alert_redirect_outgoing_call_timeout" msgid="5568101425637373060">"<xliff:g id="OTHER_APP">%1$s</xliff:g>無法撥打電話。建議你使用其他通話重新導向應用程式,或向開發人員求助。"</string>
     <string name="phone_settings_call_blocking_txt" msgid="7311523114822507178">"來電封鎖"</string>
     <string name="phone_settings_number_not_in_contact_txt" msgid="2602249106007265757">"不在通訊錄中的號碼"</string>
     <string name="phone_settings_number_not_in_contact_summary_txt" msgid="963327038085718969">"封鎖不在通訊錄中的號碼"</string>
@@ -119,7 +120,7 @@
     <string name="phone_strings_call_blocking_turned_off_notification_title_txt" msgid="2895809176537908791">"來電封鎖"</string>
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="1713632946174016619">"已停用來電封鎖功能"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="6629412508584507377">"已撥緊急電話"</string>
-    <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="3140411733995271126">"已停用來電封鎖功能,以便救援人員與您聯絡。"</string>
+    <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="3140411733995271126">"已停用來電封鎖功能,以便救援人員與你聯絡。"</string>
     <string name="developer_title" msgid="9146088855661672353">"電信開發商選單"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"使用緊急電話期間無法接聽電話。"</string>
     <string name="cancel" msgid="6733466216239934756">"取消"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"喇叭"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"外部"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"不明"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"正在串流音訊至其他裝置"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"結束通話"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"在這裡切換"</string>
 </resources>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index 21b8ae9..eeb98b5 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"背景通話"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"通話中斷"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"通話應用程式異常終止"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"通話串流"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"撥打這通電話將結束你的「<xliff:g id="OTHER_APP">%1$s</xliff:g>」通話。"</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"選擇撥打這通電話的方式"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"使用「<xliff:g id="OTHER_APP">%1$s</xliff:g>」轉接電話"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"喇叭"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"外部"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"不明"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"正在將音訊串流到其他裝置"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"掛斷"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"切換到這部裝置"</string>
 </resources>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index fbde58b..faee0d9 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -100,6 +100,7 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"Amakholi angemuva"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Amakholi anqanyuliwe"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Izinhlelo zokusebenza ezikhubazekile zefoni"</string>
+    <string name="notification_channel_call_streaming" msgid="5100510699787538991">"Ukusakaza ikholi"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"Ukwenza le kholi kuzoqeda enye ikholi yakho ye-<xliff:g id="OTHER_APP">%1$s</xliff:g>."</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Khetha ukuthi uyibeka kanjani le kholi"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Qondisa kabusha ikholi usebenzisa i-<xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
@@ -130,4 +131,7 @@
     <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Isipikha"</string>
     <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Okungaphandle"</string>
     <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Akwaziwa"</string>
+    <string name="call_streaming_notification_body" msgid="502216105683378263">"Sakaza umsindo kwenye idivayisi"</string>
+    <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Beka phansi"</string>
+    <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Shintsha lapha"</string>
 </resources>
diff --git a/res/values/config.xml b/res/values/config.xml
index b0e50b0..c38a6ec 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -49,8 +49,10 @@
     <bool name="grant_location_permission_enabled">false</bool>
 
     <!-- When true, a simple full intensity on/off vibration pattern will be used when calls ring.
-         When false, a fancy vibration pattern which ramps up and down will be used.
-         Devices should overlay this value based on the type of vibration hardware they employ. -->
+
+         When false, the vibration effect serialized in the raw `default_ringtone_vibration_effect`
+         resource (under `frameworks/base/core/res/res/raw/`) is used. Devices should overlay this
+         value based on the type of vibration hardware they employ. -->
     <bool name="use_simple_vibration_pattern">false</bool>
 
     <!-- Threshold for the X+Y component of gravity needed for the device orientation to be
@@ -73,4 +75,8 @@
     <!-- 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>
+
+    <!-- When true, the options in the call blocking settings to block unavailable and unknown
+     callers are combined into a single toggle. -->
+    <bool name="combine_options_to_block_unavailable_and_unknown_callers">true</bool>
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index d67df4b..ec278f0 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -321,6 +321,10 @@
     <string name="notification_channel_disconnected_calls">Disconnected calls</string>
     <!-- Notification channel name for a channel containing crashed phone apps service notifications. -->
     <string name="notification_channel_in_call_service_crash">Crashed phone apps</string>
+    <!-- Notification channel name for a channel containing notifications related to call streaming.
+         Call streaming is a feature where an app can use another device like a tablet to see and
+         control a call taking place on their phone. -->
+    <string name="notification_channel_call_streaming">Call streaming</string>
 
     <!-- Alert dialog content used to inform the user that placing a new outgoing call will end the
          ongoing call in the app "other_app". -->
@@ -395,4 +399,20 @@
     <string name="callendpoint_name_streaming">External</string>
     <!-- The user-visible name of the unknown new type CallEndpoint -->
     <string name="callendpoint_name_unknown">Unknown</string>
+
+    <!-- The content of a notification shown when a call is being streamed to another device.
+         Call streaming is a feature where a user can see and interact with a call from another
+         device like a tablet while the call takes place on their phone. -->
+    <string name="call_streaming_notification_body">Streaming audio to other device</string>
+    <!-- A notification action which is shown when a call is being streamed to another device.
+         Tapping the action will hang up the call.
+         Call streaming is a feature where a user can see and interact with a call from another
+         device like a tablet while the call takes place on their phone. -->
+    <string name="call_streaming_notification_action_hang_up">Hang up</string>
+    <!-- A notification action which is shown when a call is being streamed to another device.
+         Tapping the action will move the call back to the phone from the device it is being
+         streamed to.
+         Call streaming is a feature where a user can see and interact with a call from another
+         device like a tablet while the call takes place on their phone. -->
+    <string name="call_streaming_notification_action_switch_here">Switch here</string>
 </resources>
diff --git a/res/xml/activity_blocked_numbers.xml b/res/xml/activity_blocked_numbers.xml
index e77184d..b6298e9 100644
--- a/res/xml/activity_blocked_numbers.xml
+++ b/res/xml/activity_blocked_numbers.xml
@@ -41,8 +41,8 @@
                     android:layout_height="wrap_content"
                     android:text="@string/non_primary_user"
                     android:paddingTop="@dimen/blocked_numbers_large_padding"
-                    android:paddingLeft="@dimen/blocked_numbers_large_padding"
-                    android:paddingRight="@dimen/blocked_numbers_large_padding"
+                    android:paddingStart="@dimen/blocked_numbers_large_padding"
+                    android:paddingEnd="@dimen/blocked_numbers_large_padding"
                     style="@style/BlockedNumbersTextPrimary2"
                     android:visibility="gone" />
 
@@ -62,8 +62,8 @@
                         android:layout_width="match_parent"
                         android:layout_height="wrap_content"
                         android:paddingTop="@dimen/blocked_numbers_large_padding"
-                        android:paddingLeft="@dimen/blocked_numbers_large_padding"
-                        android:paddingRight="@dimen/blocked_numbers_large_padding">
+                        android:paddingStart="@dimen/blocked_numbers_large_padding"
+                        android:paddingEnd="@dimen/blocked_numbers_large_padding">
 
                     <TextView
                             android:layout_width="wrap_content"
diff --git a/res/xml/blocking_suppressed_butterbar.xml b/res/xml/blocking_suppressed_butterbar.xml
index 8b941b9..2947340 100644
--- a/res/xml/blocking_suppressed_butterbar.xml
+++ b/res/xml/blocking_suppressed_butterbar.xml
@@ -25,19 +25,19 @@
             android:id="@+id/icon"
             android:layout_height="wrap_content"
             android:layout_width="wrap_content"
-            android:layout_alignParentLeft="true"
+            android:layout_alignParentStart="true"
             android:paddingTop="@dimen/blocked_numbers_large_padding"
-            android:paddingRight="@dimen/blocked_numbers_large_padding"
-            android:paddingLeft="@dimen/blocked_numbers_large_padding"
+            android:paddingEnd="@dimen/blocked_numbers_large_padding"
+            android:paddingStart="@dimen/blocked_numbers_large_padding"
             android:src="@drawable/ic_status_blocked_orange_40dp"/>
 
     <TextView
             android:id="@+id/title"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:layout_toRightOf="@id/icon"
+            android:layout_toEndOf="@id/icon"
             android:paddingTop="@dimen/blocked_numbers_large_padding"
-            android:paddingRight="@dimen/blocked_numbers_large_padding"
+            android:paddingEnd="@dimen/blocked_numbers_large_padding"
             android:text="@string/blocked_numbers_butter_bar_title"
             style="@style/BlockedNumbersTextPrimary2" />
 
@@ -45,11 +45,11 @@
             android:id="@+id/description"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:layout_toRightOf="@id/icon"
+            android:layout_toEndOf="@id/icon"
             android:layout_below="@id/title"
             android:paddingTop="@dimen/blocked_numbers_large_padding"
             android:paddingBottom="@dimen/blocked_numbers_large_padding"
-            android:paddingRight="@dimen/blocked_numbers_large_padding"
+            android:paddingEnd="@dimen/blocked_numbers_large_padding"
             android:text="@string/blocked_numbers_butter_bar_body"
             style="@style/BlockedNumbersTextSecondary" />
 
@@ -57,9 +57,9 @@
             android:id="@+id/reenable_button"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:layout_toRightOf="@id/icon"
+            android:layout_toEndOf="@id/icon"
             android:layout_below="@id/description"
-            android:paddingRight="@dimen/blocked_numbers_large_padding"
+            android:paddingEnd="@dimen/blocked_numbers_large_padding"
             android:text="@string/blocked_numbers_butter_bar_button"
             style="@style/BlockedNumbersButton"
             android:background="?android:attr/selectableItemBackgroundBorderless" />
diff --git a/src/com/android/server/telecom/AsyncRingtonePlayer.java b/src/com/android/server/telecom/AsyncRingtonePlayer.java
index 24a65b7..912305b 100644
--- a/src/com/android/server/telecom/AsyncRingtonePlayer.java
+++ b/src/com/android/server/telecom/AsyncRingtonePlayer.java
@@ -18,9 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.media.AudioAttributes;
 import android.media.Ringtone;
-import android.media.RingtoneManager;
 import android.media.VolumeShaper;
 import android.net.Uri;
 import android.os.Handler;
@@ -32,12 +30,21 @@
 import com.android.internal.os.SomeArgs;
 import com.android.internal.util.Preconditions;
 
+import java.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.function.BiConsumer;
+import java.util.function.Supplier;
+
 /**
  * Plays the default ringtone. Uses {@link Ringtone} in a separate thread so that this class can be
  * used from the main thread.
  */
 @VisibleForTesting
 public class AsyncRingtonePlayer {
+    // Maximum amount of time we will delay playing a ringtone while waiting for audio routing to
+    // be ready.
+    private static final int PLAY_DELAY_TIMEOUT_MS = 1000;
     // Message codes used with the ringtone thread.
     private static final int EVENT_PLAY = 1;
     private static final int EVENT_STOP = 2;
@@ -48,6 +55,23 @@
     /** The current ringtone. Only used by the ringtone thread. */
     private Ringtone mRingtone;
 
+    /**
+     * Set to true if we are setting up to play or are currently playing. False if we are stopping
+     * or have stopped playing.
+     */
+    private boolean mIsPlaying = false;
+
+    /**
+     * Set to true if BT HFP is active and audio is connected.
+     */
+    private boolean mIsBtActive = false;
+
+    /**
+     * A list of pending ringing ready latches, which are used to delay the ringing command until
+     * audio paths are set and ringing is ready.
+     */
+    private final ArrayList<CountDownLatch> mPendingRingingLatches = new ArrayList<>();
+
     public AsyncRingtonePlayer() {
         // Empty
     }
@@ -57,20 +81,83 @@
      * If {@link VolumeShaper.Configuration} is specified, it is applied to the ringtone to change
      * the volume of the ringtone as it plays.
      *
-     * @param ringtone The {@link Ringtone}.
+     * @param ringtoneSupplier The {@link Ringtone} factory.
+     * @param ringtoneConsumer The {@link Ringtone} post-creation callback (to start the vibration).
+     * @param isHfpDeviceConnected True if there is a HFP BT device connected, false otherwise.
      */
-    public void play(@NonNull Ringtone ringtone) {
+    public void play(@NonNull Supplier<Ringtone> ringtoneSupplier,
+            BiConsumer<Ringtone, Boolean> ringtoneConsumer,  boolean isHfpDeviceConnected) {
         Log.d(this, "Posting play.");
+        mIsPlaying = true;
         SomeArgs args = SomeArgs.obtain();
-        args.arg1 = ringtone;
-        args.arg2 = Log.createSubsession();
+        args.arg1 = ringtoneSupplier;
+        args.arg2 = ringtoneConsumer;
+        args.arg3 = Log.createSubsession();
+        args.arg4 = prepareRingingReadyLatch(isHfpDeviceConnected);
         postMessage(EVENT_PLAY, true /* shouldCreateHandler */, args);
     }
 
     /** Stops playing the ringtone. */
     public void stop() {
         Log.d(this, "Posting stop.");
+        mIsPlaying = false;
         postMessage(EVENT_STOP, false /* shouldCreateHandler */, null);
+        // Clear any pending ringing latches so that we do not have to wait for its timeout to pass
+        // before calling stop.
+        clearPendingRingingLatches();
+    }
+
+    /**
+     * Called when the BT HFP profile active state changes.
+     * @param isBtActive A BT device is connected and audio is active.
+     */
+    public void updateBtActiveState(boolean isBtActive) {
+        Log.i(this, "updateBtActiveState: " + isBtActive);
+        synchronized (mPendingRingingLatches) {
+            mIsBtActive = isBtActive;
+            if (isBtActive) mPendingRingingLatches.forEach(CountDownLatch::countDown);
+        }
+    }
+
+    /**
+     * Prepares a new ringing ready latch and tracks it in a list. Once the ready latch has been
+     * used, {@link #removePendingRingingReadyLatch(CountDownLatch)} must be called on this latch.
+     * @param isHfpDeviceConnected true if there is a HFP device connected.
+     * @return the newly prepared CountDownLatch
+     */
+    private CountDownLatch prepareRingingReadyLatch(boolean isHfpDeviceConnected) {
+        CountDownLatch latch = new CountDownLatch(1);
+        synchronized (mPendingRingingLatches) {
+            // We only want to delay ringing if BT is connected but not active yet.
+            boolean isDelayRequired = isHfpDeviceConnected && !mIsBtActive;
+            Log.i(this, "prepareRingingReadyLatch:"
+                    + " connected=" + isHfpDeviceConnected
+                    + ", BT active=" + mIsBtActive
+                    + ", isDelayRequired=" + isDelayRequired);
+            if (!isDelayRequired) latch.countDown();
+            mPendingRingingLatches.add(latch);
+        }
+        return latch;
+    }
+
+    /**
+     * Remove a ringing ready latch that has been used and is no longer pending.
+     * @param l The latch to remove.
+     */
+    private void removePendingRingingReadyLatch(CountDownLatch l) {
+        synchronized (mPendingRingingLatches) {
+            mPendingRingingLatches.remove(l);
+        }
+    }
+
+    /**
+     * Count down all pending ringing ready latches and then clear the list.
+     */
+    private void clearPendingRingingLatches() {
+        synchronized (mPendingRingingLatches) {
+            mPendingRingingLatches.forEach(CountDownLatch::countDown);
+            mPendingRingingLatches.clear();
+        }
     }
 
     /**
@@ -122,30 +209,64 @@
      * Starts the actual playback of the ringtone. Executes on ringtone-thread.
      */
     private void handlePlay(SomeArgs args) {
-        Ringtone ringtone = (Ringtone) args.arg1;
-        Session session = (Session) args.arg2;
+        Supplier<Ringtone> ringtoneSupplier = (Supplier<Ringtone>) args.arg1;
+        BiConsumer<Ringtone, Boolean> ringtoneConsumer = (BiConsumer<Ringtone, Boolean>) args.arg2;
+        Session session = (Session) args.arg3;
+        CountDownLatch ringingReadyLatch = (CountDownLatch) args.arg4;
         args.recycle();
 
         Log.continueSession(session, "ARP.hP");
         try {
-            // don't bother with any of this if there is an EVENT_STOP waiting.
+            // Don't bother with any of this if there is an EVENT_STOP waiting, but give the
+            // consumer a chance to do anything no matter what.
             if (mHandler.hasMessages(EVENT_STOP)) {
+                Log.i(this, "handlePlay: skipping play early due to pending STOP");
+                removePendingRingingReadyLatch(ringingReadyLatch);
+                ringtoneConsumer.accept(null, /* stopped= */ true);
                 return;
             }
-
-            ThreadUtil.checkNotOnMainThread();
-
-            setRingtone(ringtone);
-            Uri uri = mRingtone.getUri();
-            String uriString = (uri != null ? uri.toSafeString() : "");
-            Log.i(this, "handlePlay: Play ringtone. Uri: " + uriString);
-            mRingtone.setLooping(true);
-            if (mRingtone.isPlaying()) {
-                Log.d(this, "Ringtone already playing.");
-                return;
+            Ringtone ringtone = null;
+            boolean hasStopped = false;
+            try {
+                try {
+                    Log.i(this, "handlePlay: delay ring for ready signal...");
+                    boolean reachedZero = ringingReadyLatch.await(PLAY_DELAY_TIMEOUT_MS,
+                            TimeUnit.MILLISECONDS);
+                    Log.i(this, "handlePlay: ringing ready, timeout=" + !reachedZero);
+                } catch (InterruptedException e) {
+                    Log.w(this, "handlePlay: latch exception: " + e);
+                }
+                ringtone = ringtoneSupplier.get();
+                // Ringtone supply can be slow or stop command could have been issued while waiting
+                // for BT to move to CONNECTED state. Re-check for stop event.
+                if (mHandler.hasMessages(EVENT_STOP)) {
+                    Log.i(this, "handlePlay: skipping play due to pending STOP");
+                    hasStopped = true;
+                    if (ringtone != null) ringtone.stop();  // proactively release the ringtone.
+                    return;
+                }
+                // setRingtone even if null - it also stops any current ringtone to be consistent
+                // with the overall state.
+                setRingtone(ringtone);
+                if (mRingtone == null) {
+                    // The ringtoneConsumer can still vibrate at this stage.
+                    Log.w(this, "No ringtone was found bail out from playing.");
+                    return;
+                }
+                Uri uri = mRingtone.getUri();
+                String uriString = (uri != null ? uri.toSafeString() : "");
+                Log.i(this, "handlePlay: Play ringtone. Uri: " + uriString);
+                mRingtone.setLooping(true);
+                if (mRingtone.isPlaying()) {
+                    Log.d(this, "Ringtone already playing.");
+                    return;
+                }
+                mRingtone.play();
+                Log.i(this, "Play ringtone, looping.");
+            } finally {
+                removePendingRingingReadyLatch(ringingReadyLatch);
+                ringtoneConsumer.accept(ringtone, hasStopped);
             }
-            mRingtone.play();
-            Log.i(this, "Play ringtone, looping.");
         } finally {
             Log.cancelSubsession(session);
         }
@@ -172,11 +293,15 @@
         }
     }
 
+    /**
+     * @return true if we are currently preparing or playing a ringtone, false if we are not.
+     */
     public boolean isPlaying() {
-        return mRingtone != null;
+        return mIsPlaying;
     }
 
     private void setRingtone(@Nullable Ringtone ringtone) {
+        Log.i(this, "setRingtone: ringtone null="  + (ringtone == null));
         // Make sure that any previously created instance of Ringtone is stopped so the MediaPlayer
         // can be released, before replacing mRingtone with a new instance. This is always created
         // as a looping Ringtone, so if not stopped it will keep playing on the background.
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index 3d62076..c9557f2 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -17,11 +17,13 @@
 package com.android.server.telecom;
 
 import static android.provider.CallLog.Calls.MISSED_REASON_NOT_MISSED;
+import static android.telephony.TelephonyManager.EVENT_DISPLAY_EMERGENCY_MESSAGE;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.graphics.Bitmap;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
@@ -29,6 +31,7 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.OutcomeReceiver;
 import android.os.ParcelFileDescriptor;
 import android.os.Parcelable;
 import android.os.RemoteException;
@@ -38,9 +41,11 @@
 import android.provider.CallLog;
 import android.provider.ContactsContract.Contacts;
 import android.telecom.BluetoothCallQualityReport;
+import android.telecom.CallAttributes;
 import android.telecom.CallAudioState;
 import android.telecom.CallDiagnosticService;
 import android.telecom.CallDiagnostics;
+import android.telecom.CallException;
 import android.telecom.CallerInfo;
 import android.telecom.Conference;
 import android.telecom.Connection;
@@ -68,9 +73,14 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telecom.IVideoProvider;
 import com.android.internal.util.Preconditions;
+import com.android.server.telecom.flags.FeatureFlags;
+import com.android.server.telecom.flags.Flags;
 import com.android.server.telecom.stats.CallFailureCause;
 import com.android.server.telecom.stats.CallStateChangedAtomWriter;
 import com.android.server.telecom.ui.ToastFactory;
+import com.android.server.telecom.voip.TransactionManager;
+import com.android.server.telecom.voip.VerifyCallStateChangeTransaction;
+import com.android.server.telecom.voip.VoipCallTransactionResult;
 
 import java.io.IOException;
 import java.text.SimpleDateFormat;
@@ -116,6 +126,24 @@
 
     private static final char NO_DTMF_TONE = '\0';
 
+
+    /**
+     * Listener for CallState changes which can be leveraged by a Transaction.
+     */
+    public interface CallStateListener {
+        void onCallStateChanged(int newCallState);
+    }
+
+    public List<CallStateListener> mCallStateListeners = new ArrayList<>();
+
+    public void addCallStateListener(CallStateListener newListener) {
+        mCallStateListeners.add(newListener);
+    }
+
+    public boolean removeCallStateListener(CallStateListener newListener) {
+        return mCallStateListeners.remove(newListener);
+    }
+
     /**
      * Listener for events on the call.
      */
@@ -281,18 +309,25 @@
                 @Override
                 public void onCallerInfoQueryComplete(Uri handle, CallerInfo callerInfo) {
                     synchronized (mLock) {
-                        Call.this.setCallerInfo(handle, callerInfo);
+                        Call call = Call.this;
+                        if (call != null) {
+                            call.setCallerInfo(handle, callerInfo);
+                        }
                     }
                 }
 
                 @Override
                 public void onContactPhotoQueryComplete(Uri handle, CallerInfo callerInfo) {
                     synchronized (mLock) {
-                        Call.this.setCallerInfo(handle, callerInfo);
+                        Call call = Call.this;
+                        if (call != null) {
+                            call.setCallerInfo(handle, callerInfo);
+                        }
                     }
                 }
             };
 
+    private final boolean mIsModifyStatePermissionGranted;
     /**
      * One of CALL_DIRECTION_INCOMING, CALL_DIRECTION_OUTGOING, or CALL_DIRECTION_UNKNOWN
      */
@@ -316,6 +351,12 @@
      */
     private long mCreationTimeMillis;
 
+    /**
+     * The elapsed realtime millis when this call was created; this can be used to determine how
+     * long has elapsed since the call was first created.
+     */
+    private long mCreationElapsedRealtimeMillis;
+
     /** The time this call was made active. */
     private long mConnectTimeMillis = 0;
 
@@ -354,7 +395,7 @@
 
     private PhoneAccountHandle mRemotePhoneAccountHandle;
 
-    private UserHandle mInitiatingUser;
+    private UserHandle mAssociatedUser;
 
     private final Handler mHandler = new Handler(Looper.getMainLooper());
 
@@ -398,6 +439,13 @@
     private int mCallerDisplayNamePresentation;
 
     /**
+     * The remote connection service which is attempted or already connecting this call. This is set
+     * to a non-null value only when a connection manager phone account is in use. When set, this
+     * will correspond to the target phone account of the {@link Call}.
+     */
+    private ConnectionServiceWrapper mRemoteConnectionService;
+
+    /**
      * The connection service which is attempted or already connecting this call.
      */
     private ConnectionServiceWrapper mConnectionService;
@@ -406,6 +454,16 @@
 
     private boolean mIsEmergencyCall;
 
+    /**
+     * Flag indicating if ECBM is active for the target phone account. This only applies to MT calls
+     * in the scenario of work profiles (when the profile is paused and the user has only registered
+     * a work sim). Normally, MT calls made to the work sim should be rejected when the work apps
+     * are paused. However, when the admin makes a MO ecall, ECBM should be enabled for that sim to
+     * allow non-emergency MT calls. MO calls don't apply because the phone account would be
+     * rejected from selection if the owner is not placing the call.
+     */
+    private boolean mIsInECBM;
+
     // The Call is considered an emergency call for testing, but will not actually connect to
     // emergency services.
     private boolean mIsTestEmergencyCall;
@@ -516,6 +574,7 @@
     private boolean mWasHighDefAudio = false;
     private boolean mWasWifi = false;
     private boolean mWasVolte = false;
+    private boolean mDestroyed = false;
 
     // For conferences which support merge/swap at their level, we retain a notion of an active
     // call. This is used for BluetoothPhoneService.  In order to support hold/merge, it must have
@@ -555,6 +614,25 @@
     private boolean mIsSelfManaged = false;
 
     private boolean mIsTransactionalCall = false;
+    private CallingPackageIdentity mCallingPackageIdentity = new CallingPackageIdentity();
+
+    /**
+     * CallingPackageIdentity is responsible for storing properties about the calling package that
+     * initiated the call. For example, if MyVoipApp requests to add a call with Telecom, we can
+     * store their UID and PID when we are still bound to that package.
+     */
+    public static class CallingPackageIdentity {
+        public int mCallingPackageUid = -1;
+        public int mCallingPackagePid = -1;
+
+        public CallingPackageIdentity() {
+        }
+
+        CallingPackageIdentity(Bundle extras) {
+            mCallingPackageUid = extras.getInt(CallAttributes.CALLER_UID_KEY, -1);
+            mCallingPackagePid = extras.getInt(CallAttributes.CALLER_PID_KEY, -1);
+        }
+    }
 
     /**
      * Indicates whether this call is streaming.
@@ -719,6 +797,8 @@
      */
     private CompletableFuture<Boolean> mDisconnectFuture;
 
+    private FeatureFlags mFlags;
+
     /**
      * Persists the specified parameters and initializes the new instance.
      * @param context The context.
@@ -750,11 +830,12 @@
             boolean shouldAttachToExistingConnection,
             boolean isConference,
             ClockProxy clockProxy,
-            ToastFactory toastFactory) {
+            ToastFactory toastFactory,
+            FeatureFlags featureFlags) {
         this(callId, context, callsManager, lock, repository, phoneNumberUtilsAdapter,
                handle, null, gatewayInfo, connectionManagerPhoneAccountHandle,
                targetPhoneAccountHandle, callDirection, shouldAttachToExistingConnection,
-               isConference, clockProxy, toastFactory);
+               isConference, clockProxy, toastFactory, featureFlags);
 
     }
 
@@ -774,8 +855,9 @@
             boolean shouldAttachToExistingConnection,
             boolean isConference,
             ClockProxy clockProxy,
-            ToastFactory toastFactory) {
-
+            ToastFactory toastFactory,
+            FeatureFlags featureFlags) {
+        mFlags = featureFlags;
         mId = callId;
         mConnectionId = callId;
         mState = (isConference && callDirection != CALL_DIRECTION_INCOMING &&
@@ -792,8 +874,8 @@
                 ? PhoneNumberUtils.extractPostDialPortion(handle.getSchemeSpecificPart()) : "";
         mGatewayInfo = gatewayInfo;
         setConnectionManagerPhoneAccount(connectionManagerPhoneAccountHandle);
-        setTargetPhoneAccount(targetPhoneAccountHandle);
         mCallDirection = callDirection;
+        setTargetPhoneAccount(targetPhoneAccountHandle);
         mIsConference = isConference;
         mShouldAttachToExistingConnection = shouldAttachToExistingConnection
                 || callDirection == CALL_DIRECTION_INCOMING;
@@ -801,10 +883,13 @@
         mClockProxy = clockProxy;
         mToastFactory = toastFactory;
         mCreationTimeMillis = mClockProxy.currentTimeMillis();
+        mCreationElapsedRealtimeMillis = mClockProxy.elapsedRealtime();
         mMissedReason = MISSED_REASON_NOT_MISSED;
         mStartRingTime = 0;
 
         mCallStateChangedAtomWriter.setExistingCallCount(callsManager.getCalls().size());
+        mIsModifyStatePermissionGranted =
+                isModifyPhoneStatePermissionGranted(getDelegatePhoneAccountHandle());
     }
 
     /**
@@ -824,6 +909,7 @@
      * connection, regardless of whether it's incoming or outgoing.
      * @param connectTimeMillis The connection time of the call.
      * @param clockProxy
+     * @param featureFlags The telecom feature flags.
      */
     Call(
             String callId,
@@ -842,11 +928,13 @@
             long connectTimeMillis,
             long connectElapsedTimeMillis,
             ClockProxy clockProxy,
-            ToastFactory toastFactory) {
+            ToastFactory toastFactory,
+            FeatureFlags featureFlags) {
         this(callId, context, callsManager, lock, repository,
                 phoneNumberUtilsAdapter, handle, gatewayInfo,
                 connectionManagerPhoneAccountHandle, targetPhoneAccountHandle, callDirection,
-                shouldAttachToExistingConnection, isConference, clockProxy, toastFactory);
+                shouldAttachToExistingConnection, isConference, clockProxy, toastFactory,
+                featureFlags);
 
         mConnectTimeMillis = connectTimeMillis;
         mConnectElapsedTimeMillis = connectElapsedTimeMillis;
@@ -891,6 +979,9 @@
     }
 
     public void destroy() {
+        if (mDestroyed) {
+            return;
+        }
         // We should not keep these bitmaps around because the Call objects may be held for logging
         // purposes.
         // TODO: Make a container object that only stores the information we care about for Logging.
@@ -901,6 +992,7 @@
         closeRttStreams();
 
         Log.addEvent(this, LogUtils.Events.DESTROYED);
+        mDestroyed = true;
     }
 
     private void closeRttStreams() {
@@ -961,6 +1053,9 @@
         s.append(SimpleDateFormat.getDateTimeInstance().format(new Date(getCreationTimeMillis())));
         s.append("]");
         s.append(isIncoming() ? "(MT - incoming)" : "(MO - outgoing)");
+        s.append("(User=");
+        s.append(getAssociatedUser());
+        s.append(")");
         s.append("\n\t");
 
         PhoneAccountHandle targetPhoneAccountHandle = getTargetPhoneAccount();
@@ -1282,6 +1377,12 @@
                 Log.addEvent(this, event, stringData);
             }
 
+            if (mFlags.transactionalCsVerifier()) {
+                for (CallStateListener listener : mCallStateListeners) {
+                    listener.onCallStateChanged(newState);
+                }
+            }
+
             mCallStateChangedAtomWriter
                     .setDisconnectCause(getDisconnectCause())
                     .setSelfManaged(isSelfManaged())
@@ -1564,6 +1665,21 @@
     }
 
     /**
+     * @return {@code true} if the target phone account is in ECBM.
+     */
+    public boolean isInECBM() {
+        return mIsInECBM;
+    }
+
+    /**
+     * Set if the target phone account is in ECBM.
+     * @param isInEcbm {@code true} if target phone account is in ECBM, {@code false} otherwise.
+     */
+    public void setIsInECBM(boolean isInECBM) {
+        mIsInECBM = isInECBM;
+    }
+
+    /**
      * @return {@code true} if the network has identified this call as an emergency call.
      */
     public boolean isNetworkIdentifiedEmergencyCall() {
@@ -1654,6 +1770,11 @@
     public void setTargetPhoneAccount(PhoneAccountHandle accountHandle) {
         if (!Objects.equals(mTargetPhoneAccountHandle, accountHandle)) {
             mTargetPhoneAccountHandle = accountHandle;
+            // Update the last MO emergency call in the helper, if applicable.
+            if (isEmergencyCall() && !isIncoming()) {
+                mCallsManager.getEmergencyCallHelper().setLastOutgoingEmergencyCallPAH(
+                        accountHandle);
+            }
             for (Listener l : mListeners) {
                 l.onTargetPhoneAccountChanged(this);
             }
@@ -1666,15 +1787,17 @@
             mCallStateChangedAtomWriter.setUid(
                     accountHandle.getComponentName().getPackageName(),
                     mContext.getPackageManager());
+            // Set the associated user for the call for MT calls based on the target phone account.
+            UserHandle associatedUser = UserUtil.getAssociatedUserForCall(
+                    mFlags.workProfileAssociatedUser(),
+                    mCallsManager.getPhoneAccountRegistrar(), mCallsManager.getCurrentUserHandle(),
+                    accountHandle);
+            if (isIncoming() && !associatedUser.equals(mAssociatedUser)) {
+                setAssociatedUser(associatedUser);
+            }
         }
     }
 
-    public UserHandle getUserHandleFromTargetPhoneAccount() {
-        return mTargetPhoneAccountHandle == null
-                ? mCallsManager.getCurrentUserHandle() :
-                mTargetPhoneAccountHandle.getUserHandle();
-    }
-
     public PhoneAccount getPhoneAccountFromHandle() {
         if (getTargetPhoneAccount() == null) {
             return null;
@@ -1830,6 +1953,17 @@
         setConnectionProperties(getConnectionProperties());
     }
 
+    public void setCallingPackageIdentity(Bundle extras) {
+        mCallingPackageIdentity = new CallingPackageIdentity(extras);
+        // These extras should NOT be propagated to Dialer and should be removed.
+        extras.remove(CallAttributes.CALLER_PID_KEY);
+        extras.remove(CallAttributes.CALLER_UID_KEY);
+    }
+
+    public CallingPackageIdentity getCallingPackageIdentity() {
+        return mCallingPackageIdentity;
+    }
+
     public void setTransactionServiceWrapper(TransactionalServiceWrapper service) {
         mTransactionalService = service;
     }
@@ -1908,7 +2042,7 @@
         if (phoneAccount != null) {
             final UserHandle userHandle;
             if (phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_MULTI_USER)) {
-                userHandle = mInitiatingUser;
+                userHandle = mAssociatedUser;
             } else {
                 userHandle = mTargetPhoneAccountHandle.getUserHandle();
             }
@@ -2019,8 +2153,12 @@
         return mCreationTimeMillis;
     }
 
-    public void setCreationTimeMillis(long time) {
-        mCreationTimeMillis = time;
+    /**
+     * @return The elapsed realtime millis when the call was created; ONLY useful for determining
+     * how long has elapsed since the call was first created.
+     */
+    public long getCreationElapsedRealtimeMillis() {
+        return mCreationElapsedRealtimeMillis;
     }
 
     public long getConnectTimeMillis() {
@@ -2234,11 +2372,25 @@
 
     @VisibleForTesting
     public void setConnectionService(ConnectionServiceWrapper service) {
+        setConnectionService(service, null);
+    }
+
+    @VisibleForTesting
+    public void setConnectionService(
+            ConnectionServiceWrapper service,
+            ConnectionServiceWrapper remoteService
+    ) {
         Preconditions.checkNotNull(service);
 
         clearConnectionService();
 
         service.incrementAssociatedCallCount();
+
+        if (mFlags.updatedRcsCallCountTracking() && remoteService != null) {
+            remoteService.incrementAssociatedCallCount();
+            mRemoteConnectionService = remoteService;
+        }
+
         mConnectionService = service;
         mAnalytics.setCallConnectionService(service.getComponentName().flattenToShortString());
         mConnectionService.addCall(this);
@@ -2246,10 +2398,12 @@
 
     /**
      * Perform an in-place replacement of the {@link ConnectionServiceWrapper} for this Call.
-     * Removes the call from its former {@link ConnectionServiceWrapper}, ensuring that the
-     * ConnectionService is NOT unbound if the call count hits zero.
-     * This is used by the {@link ConnectionServiceWrapper} when handling {@link Connection} and
-     * {@link Conference} additions via a ConnectionManager.
+     * Removes the call from its former {@link ConnectionServiceWrapper}, while still ensuring the
+     * former {@link ConnectionServiceWrapper} is tracked as the mRemoteConnectionService for this
+     * call so that the associatedCallCount of that {@link ConnectionServiceWrapper} is accurately
+     * tracked until it is supposed to be unbound.
+     * This method is used by the {@link ConnectionServiceWrapper} when handling {@link Connection}
+     * and {@link Conference} additions via a ConnectionManager.
      * The original {@link android.telecom.ConnectionService} will directly add external calls and
      * conferences to Telecom as well as the ConnectionManager, which will add to Telecom.  In these
      * cases since its first added to via the original CS, we want to change the CS responsible for
@@ -2262,9 +2416,18 @@
 
         if (mConnectionService != null) {
             ConnectionServiceWrapper serviceTemp = mConnectionService;
+
+            if (mFlags.updatedRcsCallCountTracking()) {
+                // Continue to track the former CS for this call so that it doesn't unbind early:
+                mRemoteConnectionService = serviceTemp;
+            }
+
             mConnectionService = null;
             serviceTemp.removeCall(this);
-            serviceTemp.decrementAssociatedCallCount(true /*isSuppressingUnbind*/);
+
+            if (!mFlags.updatedRcsCallCountTracking()) {
+                serviceTemp.decrementAssociatedCallCount(true /*isSuppressingUnbind*/);
+            }
         }
 
         service.incrementAssociatedCallCount();
@@ -2278,6 +2441,8 @@
     void clearConnectionService() {
         if (mConnectionService != null) {
             ConnectionServiceWrapper serviceTemp = mConnectionService;
+            ConnectionServiceWrapper remoteServiceTemp = mRemoteConnectionService;
+            mRemoteConnectionService = null;
             mConnectionService = null;
             serviceTemp.removeCall(this);
 
@@ -2288,6 +2453,10 @@
             // necessary, but cleaning up mConnectionService prior to triggering an unbind is good
             // to do.
             decrementAssociatedCallCount(serviceTemp);
+
+            if (mFlags.updatedRcsCallCountTracking() && remoteServiceTemp != null) {
+                decrementAssociatedCallCount(remoteServiceTemp);
+            }
         }
     }
 
@@ -2306,7 +2475,7 @@
             return;
         }
         mCreateConnectionProcessor = new CreateConnectionProcessor(this, mRepository, this,
-                phoneAccountRegistrar, mContext);
+                phoneAccountRegistrar, mContext, mFlags);
         mCreateConnectionProcessor.process();
     }
 
@@ -2819,11 +2988,19 @@
         hold(null /* reason */);
     }
 
+    /**
+     * This method requests the ConnectionService or TransactionalService hosting the call to put
+     * the call on hold
+     */
     public void hold(String reason) {
         if (mState == CallState.ACTIVE) {
             if (mTransactionalService != null) {
                 mTransactionalService.onSetInactive(this);
             } else if (mConnectionService != null) {
+                if (mFlags.transactionalCsVerifier()) {
+                    awaitCallStateChangeAndMaybeDisconnectCall(CallState.ON_HOLD, isSelfManaged(),
+                            "hold");
+                }
                 mConnectionService.hold(this);
             } else {
                 Log.e(this, new NullPointerException(),
@@ -2834,6 +3011,27 @@
     }
 
     /**
+     * helper that can be used for any callback that requests a call state change and wants to
+     * verify the change
+     */
+    public void awaitCallStateChangeAndMaybeDisconnectCall(int targetCallState,
+            boolean shouldDisconnectUponTimeout, String callingMethod) {
+        TransactionManager tm = TransactionManager.getInstance();
+        tm.addTransaction(new VerifyCallStateChangeTransaction(mCallsManager,
+                this, targetCallState, shouldDisconnectUponTimeout), new OutcomeReceiver<>() {
+            @Override
+            public void onResult(VoipCallTransactionResult result) {
+            }
+
+            @Override
+            public void onError(CallException e) {
+                Log.i(this, "awaitCallStateChangeAndMaybeDisconnectCall: %s: onError"
+                        + " due to CallException=[%s]", callingMethod, e);
+            }
+        });
+    }
+
+    /**
      * Releases the call from hold if it is currently active.
      */
     @VisibleForTesting
@@ -2960,6 +3158,12 @@
                     Connection.EXTRA_REMOTE_PHONE_ACCOUNT_HANDLE));
         }
 
+        if (mExtras.containsKey(TelecomManager.EXTRA_DO_NOT_LOG_CALL)) {
+            if (source != SOURCE_CONNECTION_SERVICE || !mIsModifyStatePermissionGranted) {
+                mExtras.remove(TelecomManager.EXTRA_DO_NOT_LOG_CALL);
+            }
+        }
+
         // If the change originated from an InCallService, notify the connection service.
         if (source == SOURCE_INCALL_SERVICE) {
             Log.addEvent(this, LogUtils.Events.ICS_EXTRAS_CHANGED);
@@ -2974,6 +3178,15 @@
         }
     }
 
+    private boolean isModifyPhoneStatePermissionGranted(PhoneAccountHandle phoneAccountHandle) {
+        if (phoneAccountHandle == null) {
+            return false;
+        }
+        String packageName = phoneAccountHandle.getComponentName().getPackageName();
+        return PackageManager.PERMISSION_GRANTED == mContext.getPackageManager().checkPermission(
+                android.Manifest.permission.MODIFY_PHONE_STATE, packageName);
+    }
+
     /**
      * Removes extras from the extras bundle associated with this {@link Call}.
      *
@@ -3584,7 +3797,8 @@
         }
 
         String newName = callerInfo.getName();
-        boolean contactNameChanged = mCallerInfo == null || !mCallerInfo.getName().equals(newName);
+        boolean contactNameChanged = mCallerInfo == null ||
+                !Objects.equals(mCallerInfo.getName(), newName);
 
         mCallerInfo = callerInfo;
         Log.i(this, "CallerInfo received for %s: %s", Log.piiHandle(mHandle), callerInfo);
@@ -3659,6 +3873,7 @@
         if (mTransactionalService != null) {
             Log.i(this, "stopRtt: called on TransactionalService. doing nothing");
         } else if (mConnectionService != null) {
+            Log.addEvent(this, LogUtils.Events.REQUEST_RTT, "stop");
             mConnectionService.stopRtt(this);
         } else {
             // If this gets called by the in-call app before the connection service is set, we'll
@@ -3672,6 +3887,7 @@
             Log.i(this, "sendRttRequest: called on TransactionalService. doing nothing");
             return;
         }
+        Log.addEvent(this, LogUtils.Events.REQUEST_RTT, "start");
         createRttStreams();
         mConnectionService.startRtt(this, getInCallToCsRttPipeForCs(), getCsToInCallRttPipeForCs());
     }
@@ -3695,12 +3911,14 @@
 
     public void onRttConnectionFailure(int reason) {
         Log.i(this, "Got RTT initiation failure with reason %d", reason);
+        Log.addEvent(this, LogUtils.Events.ON_RTT_FAILED, "reason="  + reason);
         for (Listener l : mListeners) {
             l.onRttInitiationFailure(this, reason);
         }
     }
 
     public void onRemoteRttRequest() {
+        Log.addEvent(this, LogUtils.Events.ON_RTT_REQUEST);
         if (isRttCall()) {
             Log.w(this, "Remote RTT request on a call that's already RTT");
             return;
@@ -3725,6 +3943,8 @@
             Log.i(this, "handleRttRequestResponse: called on TransactionalService. doing nothing");
             return;
         }
+        Log.addEvent(this, LogUtils.Events.RESPOND_TO_RTT_REQUEST, "id=" + id + ", accept="
+                + accept);
         if (accept) {
             createRttStreams();
             Log.i(this, "RTT request %d accepted.", id);
@@ -3907,6 +4127,10 @@
         return mCallDirection == CALL_DIRECTION_UNKNOWN;
     }
 
+    public boolean isOutgoing() {
+        return mCallDirection == CALL_DIRECTION_OUTGOING;
+    }
+
     /**
      * Determines if this call is in a disconnecting state.
      *
@@ -3926,19 +4150,26 @@
     }
 
     /**
-     * @return user handle of user initiating the outgoing call.
+     * It's possible that the target phone account isn't set when a user hasn't selected a
+     * default sim to place a call. Instead of using the user from the target phone account to
+     * associate the user with a call, we'll use mAssociatedUser instead. For MT calls, we will
+     * continue to use the target phone account user (as it's always set) and for MO calls, we will
+     * use the initiating user instead.
+     *
+     * @return user handle of user associated with the call.
      */
-    public UserHandle getInitiatingUser() {
-        return mInitiatingUser;
+    public UserHandle getAssociatedUser() {
+        return mAssociatedUser;
     }
 
     /**
-     * Set the user handle of user initiating the outgoing call.
-     * @param initiatingUser
+     * Set the user handle of user associated with the call.
+     * @param associatedUser
      */
-    public void setInitiatingUser(UserHandle initiatingUser) {
-        Preconditions.checkNotNull(initiatingUser);
-        mInitiatingUser = initiatingUser;
+    public void setAssociatedUser(UserHandle associatedUser) {
+        Log.i(this, "Setting associated user for call: %s", associatedUser);
+        Preconditions.checkNotNull(associatedUser);
+        mAssociatedUser = associatedUser;
     }
 
     static int getStateFromConnectionState(int state) {
@@ -4001,7 +4232,8 @@
 
     public void setRttMode(int mode) {
         mRttMode = mode;
-        // TODO: hook this up to CallAudioManager
+        Log.addEvent(this, LogUtils.Events.SET_RRT_MODE, "mode=" + mode);
+        // TODO: hook this up to CallAudioManager.
     }
 
     /**
@@ -4032,6 +4264,15 @@
      * @param extras The extras.
      */
     public void onConnectionEvent(String event, Bundle extras) {
+        if (mIsTransactionalCall) {
+            // send the Event directly to the ICS via the InCallController listener
+            for (Listener l : mListeners) {
+                l.onConnectionEvent(this, event, extras);
+            }
+            // Don't run the below block since it applies to Calls that are attached to a
+            // ConnectionService
+            return;
+        }
         // 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);
@@ -4076,6 +4317,12 @@
                 l.onReceivedCallQualityReport(this, callQuality);
             }
         } else {
+            if (event.equals(EVENT_DISPLAY_EMERGENCY_MESSAGE) && !isEmergencyCall()) {
+                Log.w(this, "onConnectionEvent: EVENT_DISPLAY_EMERGENCY_MESSAGE is sent "
+                        + "without an emergency call");
+                return;
+            }
+
             for (Listener l : mListeners) {
                 l.onConnectionEvent(this, event, extras);
             }
@@ -4392,6 +4639,7 @@
     }
 
     public void setStartFailCause(CallFailureCause cause) {
+        Log.i(this, "setStartFailCause: cause = %s; callId = %s", cause, this.getId());
         mCallStateChangedAtomWriter.setStartFailCause(cause);
     }
 
@@ -4515,7 +4763,7 @@
             throw new UnsupportedOperationException(
                     "Can't streaming call created by non voip apps");
         }
-
+        Log.addEvent(this, LogUtils.Events.START_STREAMING);
         synchronized (mLock) {
             if (mIsStreaming) {
                 // ignore
@@ -4535,7 +4783,7 @@
                 // ignore
                 return;
             }
-
+            Log.addEvent(this, LogUtils.Events.STOP_STREAMING);
             mIsStreaming = false;
             for (Listener listener : mListeners) {
                 listener.onCallStreamingStateChanged(this, false /** isStreaming */);
diff --git a/src/com/android/server/telecom/CallAnomalyWatchdog.java b/src/com/android/server/telecom/CallAnomalyWatchdog.java
index d3dbbc9..045671e 100644
--- a/src/com/android/server/telecom/CallAnomalyWatchdog.java
+++ b/src/com/android/server/telecom/CallAnomalyWatchdog.java
@@ -43,6 +43,8 @@
  * Watchdog class responsible for detecting potential anomalous conditions for {@link Call}s.
  */
 public class CallAnomalyWatchdog extends CallsManagerListenerBase implements Call.Listener {
+    private final EmergencyCallDiagnosticLogger mEmergencyCallDiagnosticLogger;
+
     /**
      * Class used to track the call state as it pertains to the watchdog. The watchdog cares about
      * both the call state and whether a {@link ConnectionService} has finished creating the
@@ -146,15 +148,21 @@
 
     public CallAnomalyWatchdog(ScheduledExecutorService executorService,
             TelecomSystem.SyncRoot lock,
-            Timeouts.Adapter timeoutAdapter, ClockProxy clockProxy) {
+            Timeouts.Adapter timeoutAdapter, ClockProxy clockProxy,
+            EmergencyCallDiagnosticLogger emergencyCallDiagnosticLogger) {
         mScheduledExecutorService = executorService;
         mLock = lock;
         mTimeoutAdapter = timeoutAdapter;
         mClockProxy = clockProxy;
+        mEmergencyCallDiagnosticLogger = emergencyCallDiagnosticLogger;
     }
 
+    /**
+     * Start tracking a call that we're waiting for a ConnectionService to create.
+     * @param call the call.
+     */
     @Override
-    public void onCallCreated(Call call) {
+    public void onStartCreateConnection(Call call) {
         maybeTrackCall(call);
         call.addListener(this);
     }
@@ -165,23 +173,24 @@
     }
 
     /**
+     * Override of {@link CallsManagerListenerBase} to track when calls have failed to be created by
+     * a ConnectionService.  These calls should no longer be tracked by the CallAnomalyWatchdog.
+     * @param call the call
+     */
+    @Override
+    public void onCreateConnectionFailed(Call call) {
+        Log.i(this, "onCreateConnectionFailed: call=%s", call.toString());
+        stopTrackingCall(call);
+    }
+
+    /**
      * Override of {@link CallsManagerListenerBase} to track when calls are removed
      * @param call the call
      */
     @Override
     public void onCallRemoved(Call call) {
-        if (mScheduledFutureMap.containsKey(call)) {
-            ScheduledFuture<?> existingTimeout = mScheduledFutureMap.get(call);
-            existingTimeout.cancel(false /* cancelIfRunning */);
-            mScheduledFutureMap.remove(call);
-        }
-        if (mCallsPendingDestruction.contains(call)) {
-            mCallsPendingDestruction.remove(call);
-        }
-        if (mWatchdogCallStateMap.containsKey(call)) {
-            mWatchdogCallStateMap.remove(call);
-        }
-        call.removeListener(this);
+        Log.i(this, "onCallRemoved: call=%s", call.toString());
+        stopTrackingCall(call);
     }
 
     /**
@@ -192,7 +201,10 @@
      * @param newState the new state
      */
     @Override
-    public void onCallStateChanged(Call call, int oldState, int newState) { maybeTrackCall(call); }
+    public void onCallStateChanged(Call call, int oldState, int newState) {
+        Log.i(this, "onCallStateChanged: call=%s", call.toString());
+        maybeTrackCall(call);
+    }
 
     /**
      * Override of {@link Call.Listener} so we can capture successful creation of calls.
@@ -211,7 +223,8 @@
      */
     @Override
     public void onFailedOutgoingCall(Call call, DisconnectCause disconnectCause) {
-        maybeTrackCall(call);
+        Log.i(this, "onFailedOutgoingCall: call=%s", call.toString());
+        stopTrackingCall(call);
     }
 
     /**
@@ -229,7 +242,27 @@
      */
     @Override
     public void onFailedIncomingCall(Call call) {
-        maybeTrackCall(call);
+        Log.i(this, "onFailedIncomingCall: call=%s", call.toString());
+        stopTrackingCall(call);
+    }
+
+    /**
+     * Helper method used to stop CallAnomalyWatchdog from tracking or destroying the call.
+     * @param call the call.
+     */
+    private void stopTrackingCall(Call call) {
+        if (mScheduledFutureMap.containsKey(call)) {
+            ScheduledFuture<?> existingTimeout = mScheduledFutureMap.get(call);
+            existingTimeout.cancel(false /* cancelIfRunning */);
+            mScheduledFutureMap.remove(call);
+        }
+        if (mCallsPendingDestruction.contains(call)) {
+            mCallsPendingDestruction.remove(call);
+        }
+        if (mWatchdogCallStateMap.containsKey(call)) {
+            mWatchdogCallStateMap.remove(call);
+        }
+        call.removeListener(this);
     }
 
     /**
@@ -273,7 +306,7 @@
         }
     }
 
-    private long getTimeoutMillis(Call call, WatchdogCallState state) {
+    public long getTimeoutMillis(Call call, WatchdogCallState state) {
         boolean isVoip = call.getIsVoipAudioMode();
         boolean isEmergency = call.isEmergencyCall();
 
@@ -328,6 +361,7 @@
                         mAnomalyReporter.reportAnomaly(
                                 WATCHDOG_DISCONNECTED_STUCK_EMERGENCY_CALL_UUID,
                                 WATCHDOG_DISCONNECTED_STUCK_EMERGENCY_CALL_MSG);
+                        mEmergencyCallDiagnosticLogger.reportStuckCall(call);
                     } else {
                         mAnomalyReporter.reportAnomaly(
                                 WATCHDOG_DISCONNECTED_STUCK_CALL_UUID,
diff --git a/src/com/android/server/telecom/CallAudioCommunicationDeviceTracker.java b/src/com/android/server/telecom/CallAudioCommunicationDeviceTracker.java
new file mode 100644
index 0000000..5fc2414
--- /dev/null
+++ b/src/com/android/server/telecom/CallAudioCommunicationDeviceTracker.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2022 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.BluetoothDevice;
+import android.content.Context;
+import android.media.AudioDeviceInfo;
+import android.media.AudioManager;
+import android.telecom.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.telecom.bluetooth.BluetoothRouteManager;
+import com.android.server.telecom.flags.Flags;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Semaphore;
+
+/**
+ * Helper class used to keep track of the requested communication device within Telecom for audio
+ * use cases. Handles the set/clear communication use case logic for all audio routes (speaker, BT,
+ * headset, and earpiece). For BT devices, this handles switches between hearing aids, SCO, and LE
+ * audio (also takes into account switching between multiple LE audio devices).
+ */
+public class CallAudioCommunicationDeviceTracker {
+
+    // Use -1 indicates device is not set for any communication use case
+    private static final int sAUDIO_DEVICE_TYPE_INVALID = -1;
+    // Possible bluetooth audio device types
+    private static final Set<Integer> sBT_AUDIO_DEVICE_TYPES = Set.of(
+            AudioDeviceInfo.TYPE_BLE_HEADSET,
+            AudioDeviceInfo.TYPE_HEARING_AID,
+            AudioDeviceInfo.TYPE_BLUETOOTH_SCO
+    );
+    private AudioManager mAudioManager;
+    private BluetoothRouteManager mBluetoothRouteManager;
+    private int mAudioDeviceType = sAUDIO_DEVICE_TYPE_INVALID;
+    // Keep track of the locally requested BT audio device if set
+    private String mBtAudioDevice = null;
+    private final Semaphore mLock =  new Semaphore(1);
+
+    public CallAudioCommunicationDeviceTracker(Context context) {
+        mAudioManager = context.getSystemService(AudioManager.class);
+    }
+
+    public void setBluetoothRouteManager(BluetoothRouteManager bluetoothRouteManager) {
+        mBluetoothRouteManager = bluetoothRouteManager;
+    }
+
+    public boolean isAudioDeviceSetForType(int audioDeviceType) {
+        return mAudioDeviceType == audioDeviceType;
+    }
+
+    public int getCurrentLocallyRequestedCommunicationDevice() {
+       return mAudioDeviceType;
+    }
+
+    @VisibleForTesting
+    public void setTestCommunicationDevice(int audioDeviceType) {
+        mAudioDeviceType = audioDeviceType;
+    }
+
+    public void clearBtCommunicationDevice() {
+        if (mBtAudioDevice == null) {
+            Log.i(this, "No bluetooth device was set for communication that can be cleared.");
+            return;
+        }
+        // If mBtAudioDevice is set, we know a BT audio device was set for communication so
+        // mAudioDeviceType corresponds to a BT device type (e.g. hearing aid, SCO, LE).
+        clearCommunicationDevice(mAudioDeviceType);
+    }
+
+    /*
+     * Sets the communication device for the passed in audio device type, if it's available for
+     * communication use cases. Tries to clear any communication device which was previously
+     * requested for communication before setting the new device.
+     * @param audioDeviceTypes The supported audio device types for the device.
+     * @param btDevice The bluetooth device to connect to (only used for switching between multiple
+     *        LE audio devices).
+     * @return {@code true} if the device was set for communication, {@code false} if the device
+     * wasn't set.
+     */
+    public boolean setCommunicationDevice(int audioDeviceType,
+            BluetoothDevice btDevice) {
+        if (Flags.communicationDeviceProtectedByLock()) {
+            mLock.tryAcquire();
+        }
+        // There is only one audio device type associated with each type of BT device.
+        boolean isBtDevice = sBT_AUDIO_DEVICE_TYPES.contains(audioDeviceType);
+        Log.i(this, "setCommunicationDevice: type = %s, isBtDevice = %s, btDevice = %s",
+                audioDeviceType, isBtDevice, btDevice);
+
+        // Account for switching between multiple LE audio devices.
+        boolean handleLeAudioDeviceSwitch = btDevice != null
+                && !btDevice.getAddress().equals(mBtAudioDevice);
+        if ((audioDeviceType == mAudioDeviceType
+                || isUsbHeadsetType(audioDeviceType, mAudioDeviceType))
+                && !handleLeAudioDeviceSwitch) {
+            Log.i(this, "Communication device is already set for this audio type");
+            return false;
+        }
+
+        AudioDeviceInfo activeDevice = null;
+        List<AudioDeviceInfo> devices = mAudioManager.getAvailableCommunicationDevices();
+        if (devices.size() == 0) {
+            Log.w(this, "No communication devices available");
+            return false;
+        }
+
+        for (AudioDeviceInfo device : devices) {
+            Log.i(this, "Available device type: " + device.getType());
+            // Ensure that we do not select the same BT LE audio device for communication.
+            if ((audioDeviceType == device.getType()
+                    || isUsbHeadsetType(audioDeviceType, device.getType()))
+                    && !device.getAddress().equals(mBtAudioDevice)) {
+                activeDevice = device;
+                break;
+            }
+        }
+
+        if (activeDevice == null) {
+            Log.i(this, "No active device of type(s) %s available",
+                    audioDeviceType == AudioDeviceInfo.TYPE_WIRED_HEADSET
+                            ? Arrays.asList(AudioDeviceInfo.TYPE_WIRED_HEADSET,
+                                    AudioDeviceInfo.TYPE_USB_HEADSET)
+                            : audioDeviceType);
+            return false;
+        }
+
+        // Force clear previous communication device, if one was set, before setting the new device.
+        if (mAudioDeviceType != sAUDIO_DEVICE_TYPE_INVALID) {
+            clearCommunicationDevice(mAudioDeviceType);
+        }
+
+        // Turn activeDevice ON.
+        boolean result = mAudioManager.setCommunicationDevice(activeDevice);
+        if (!result) {
+            Log.w(this, "Could not set active device");
+        } else {
+            Log.i(this, "Active device set");
+            mAudioDeviceType = activeDevice.getType();
+            if (isBtDevice) {
+                mBtAudioDevice = activeDevice.getAddress();
+                if (audioDeviceType == AudioDeviceInfo.TYPE_BLE_HEADSET) {
+                    mBluetoothRouteManager.onAudioOn(mBtAudioDevice);
+                }
+            } else if (Flags.communicationDeviceProtectedByLock()) {
+                // Clear BT device if it's still stored. Handles race condition for when a non-BT
+                // device is set for communication shortly after a BT (LE) device is set for
+                // communication but the selection hasn't been cleared yet.
+                mBtAudioDevice = null;
+            }
+        }
+        if (Flags.communicationDeviceProtectedByLock()) {
+            mLock.release();
+        }
+        return result;
+    }
+
+    /*
+     * Clears the communication device for the passed in audio device types, given that the device
+     * has previously been set for communication.
+     * @param audioDeviceTypes The supported audio device types for the device.
+     */
+    public void clearCommunicationDevice(int audioDeviceType) {
+        if (Flags.communicationDeviceProtectedByLock()) {
+            mLock.tryAcquire();
+        }
+        // There is only one audio device type associated with each type of BT device.
+        boolean isBtDevice = sBT_AUDIO_DEVICE_TYPES.contains(audioDeviceType);
+        Log.i(this, "clearCommunicationDevice: type = %s, isBtDevice = %s",
+                audioDeviceType, isBtDevice);
+
+        if (audioDeviceType != mAudioDeviceType
+                && !isUsbHeadsetType(audioDeviceType, mAudioDeviceType)) {
+            Log.i(this, "Unable to clear communication device of type(s), %s. "
+                    + "Device does not correspond to the locally requested device type.",
+                    audioDeviceType == AudioDeviceInfo.TYPE_WIRED_HEADSET
+                            ? Arrays.asList(AudioDeviceInfo.TYPE_WIRED_HEADSET,
+                                    AudioDeviceInfo.TYPE_USB_HEADSET)
+                            : audioDeviceType
+            );
+            return;
+        }
+
+        if (mAudioManager == null) {
+            Log.i(this, "clearCommunicationDevice: mAudioManager is null");
+            return;
+        }
+
+        // Clear device and reset locally saved device type.
+        mAudioManager.clearCommunicationDevice();
+        mAudioDeviceType = sAUDIO_DEVICE_TYPE_INVALID;
+
+        if (isBtDevice && mBtAudioDevice != null) {
+            // Signal that BT audio was lost for device.
+            mBluetoothRouteManager.onAudioLost(mBtAudioDevice);
+            mBtAudioDevice = null;
+        }
+        if (Flags.communicationDeviceProtectedByLock()) {
+            mLock.release();
+        }
+    }
+
+    private boolean isUsbHeadsetType(int audioDeviceType, int sourceType) {
+        return audioDeviceType != AudioDeviceInfo.TYPE_WIRED_HEADSET
+                ? false : sourceType == AudioDeviceInfo.TYPE_USB_HEADSET;
+    }
+}
diff --git a/src/com/android/server/telecom/CallAudioManager.java b/src/com/android/server/telecom/CallAudioManager.java
index 8cac314..96bf2c6 100644
--- a/src/com/android/server/telecom/CallAudioManager.java
+++ b/src/com/android/server/telecom/CallAudioManager.java
@@ -23,7 +23,6 @@
 import android.os.UserHandle;
 import android.telecom.CallAudioState;
 import android.telecom.Log;
-import android.telecom.PhoneAccount;
 import android.telecom.VideoProfile;
 import android.util.SparseArray;
 
@@ -31,11 +30,14 @@
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.telecom.CallAudioModeStateMachine.MessageArgs.Builder;
 import com.android.server.telecom.bluetooth.BluetoothStateReceiver;
+import com.android.server.telecom.flags.FeatureFlags;
 
 import java.util.Collection;
 import java.util.HashSet;
 import java.util.Set;
 import java.util.LinkedHashSet;
+import java.util.stream.Collectors;
+
 
 public class CallAudioManager extends CallsManagerListenerBase {
 
@@ -52,7 +54,7 @@
     private final Set<Call> mCalls;
     private final SparseArray<LinkedHashSet<Call>> mCallStateToCalls;
 
-    private final CallAudioRouteStateMachine mCallAudioRouteStateMachine;
+    private final CallAudioRouteAdapter mCallAudioRouteAdapter;
     private final CallAudioModeStateMachine mCallAudioModeStateMachine;
     private final BluetoothStateReceiver mBluetoothStateReceiver;
     private final CallsManager mCallsManager;
@@ -60,6 +62,7 @@
     private final Ringer mRinger;
     private final RingbackPlayer mRingbackPlayer;
     private final DtmfLocalTonePlayer mDtmfLocalTonePlayer;
+    private final FeatureFlags mFeatureFlags;
 
     private Call mStreamingCall;
     private Call mForegroundCall;
@@ -67,14 +70,15 @@
     private boolean mIsDisconnectedTonePlaying = false;
     private InCallTonePlayer mHoldTonePlayer;
 
-    public CallAudioManager(CallAudioRouteStateMachine callAudioRouteStateMachine,
+    public CallAudioManager(CallAudioRouteAdapter callAudioRouteAdapter,
             CallsManager callsManager,
             CallAudioModeStateMachine callAudioModeStateMachine,
             InCallTonePlayer.Factory playerFactory,
             Ringer ringer,
             RingbackPlayer ringbackPlayer,
             BluetoothStateReceiver bluetoothStateReceiver,
-            DtmfLocalTonePlayer dtmfLocalTonePlayer) {
+            DtmfLocalTonePlayer dtmfLocalTonePlayer,
+            FeatureFlags featureFlags) {
         mActiveDialingOrConnectingCalls = new LinkedHashSet<>(1);
         mRingingCalls = new LinkedHashSet<>(1);
         mHoldingCalls = new LinkedHashSet<>(1);
@@ -92,7 +96,7 @@
             put(CallState.AUDIO_PROCESSING, mAudioProcessingCalls);
         }};
 
-        mCallAudioRouteStateMachine = callAudioRouteStateMachine;
+        mCallAudioRouteAdapter = callAudioRouteAdapter;
         mCallAudioModeStateMachine = callAudioModeStateMachine;
         mCallsManager = callsManager;
         mPlayerFactory = playerFactory;
@@ -100,10 +104,11 @@
         mRingbackPlayer = ringbackPlayer;
         mBluetoothStateReceiver = bluetoothStateReceiver;
         mDtmfLocalTonePlayer = dtmfLocalTonePlayer;
+        mFeatureFlags = featureFlags;
 
         mPlayerFactory.setCallAudioManager(this);
         mCallAudioModeStateMachine.setCallAudioManager(this);
-        mCallAudioRouteStateMachine.setCallAudioManager(this);
+        mCallAudioRouteAdapter.setCallAudioManager(this);
     }
 
     @Override
@@ -116,7 +121,7 @@
             // State did not change, so no need to do anything.
             return;
         }
-        Log.d(LOG_TAG, "Call state changed for TC@%s: %s -> %s", call.getId(),
+        Log.i(this, "onCallStateChanged: Call state changed for TC@%s: %s -> %s", call.getId(),
                 CallState.toString(oldState), CallState.toString(newState));
 
         removeCallFromAllBins(call);
@@ -146,6 +151,9 @@
 
     @Override
     public void onCallRemoved(Call call) {
+        if (mStreamingCall == call) {
+            mStreamingCall = null;
+        }
         if (shouldIgnoreCallForAudio(call)) {
             return; // Don't do audio handling for calls in a conference, or external calls.
         }
@@ -217,7 +225,7 @@
                 // When pulling a video call, automatically enable the speakerphone.
                 Log.d(LOG_TAG, "Switching to speaker because external video call %s was pulled." +
                         call.getId());
-                mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+                mCallAudioRouteAdapter.sendMessageWithSessionInfo(
                         CallAudioRouteStateMachine.SWITCH_SPEAKER);
             }
         }
@@ -238,7 +246,7 @@
                         makeArgsForModeStateMachine());
             } else {
                 Log.w(LOG_TAG, "Unexpected streaming call request for call %s while call "
-                        + "s is streaming.", call.getId(), mStreamingCall.getId());
+                        + "%s is streaming.", call.getId(), mStreamingCall.getId());
             }
         } else {
             if (mStreamingCall == call) {
@@ -299,7 +307,7 @@
                 VideoProfile.isReceptionEnabled(newVideoState);
 
         if (isUpgradeRequest) {
-            mPlayerFactory.createPlayer(InCallTonePlayer.TONE_VIDEO_UPGRADE).startTone();
+            mPlayerFactory.createPlayer(call, InCallTonePlayer.TONE_VIDEO_UPGRADE).startTone();
         }
     }
 
@@ -308,7 +316,7 @@
             // We only play tones for foreground calls.
             return;
         }
-        mPlayerFactory.createPlayer(InCallTonePlayer.TONE_RTT_REQUEST).startTone();
+        mPlayerFactory.createPlayer(call, InCallTonePlayer.TONE_RTT_REQUEST).startTone();
     }
 
     /**
@@ -321,7 +329,7 @@
      */
     @Override
     public void onHoldToneRequested(Call call) {
-        maybePlayHoldTone();
+        maybePlayHoldTone(call);
     }
 
     @Override
@@ -369,7 +377,7 @@
     @Override
     public void onConnectionServiceChanged(Call call, ConnectionServiceWrapper oldCs,
             ConnectionServiceWrapper newCs) {
-        mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+        mCallAudioRouteAdapter.sendMessageWithSessionInfo(
                 CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE);
     }
 
@@ -387,13 +395,13 @@
             Log.d(LOG_TAG, "Switching to speaker because call %s transitioned video state from %s" +
                     " to %s", call.getId(), VideoProfile.videoStateToString(previousVideoState),
                     VideoProfile.videoStateToString(newVideoState));
-            mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+            mCallAudioRouteAdapter.sendMessageWithSessionInfo(
                     CallAudioRouteStateMachine.SWITCH_SPEAKER);
         }
     }
 
     public CallAudioState getCallAudioState() {
-        return mCallAudioRouteStateMachine.getCurrentCallAudioState();
+        return mCallAudioRouteAdapter.getCurrentCallAudioState();
     }
 
     public Call getPossiblyHeldForegroundCall() {
@@ -414,7 +422,7 @@
             Log.v(this, "ignoring toggleMute for emergency call");
             return;
         }
-        mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+        mCallAudioRouteAdapter.sendMessageWithSessionInfo(
                 CallAudioRouteStateMachine.TOGGLE_MUTE);
     }
 
@@ -434,7 +442,7 @@
             Log.v(this, "ignoring mute for emergency call");
         }
 
-        mCallAudioRouteStateMachine.sendMessageWithSessionInfo(shouldMute
+        mCallAudioRouteAdapter.sendMessageWithSessionInfo(shouldMute
                 ? CallAudioRouteStateMachine.MUTE_ON : CallAudioRouteStateMachine.MUTE_OFF);
     }
 
@@ -450,23 +458,23 @@
         Log.v(this, "setAudioRoute, route: %s", CallAudioState.audioRouteToString(route));
         switch (route) {
             case CallAudioState.ROUTE_BLUETOOTH:
-                mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+                mCallAudioRouteAdapter.sendMessageWithSessionInfo(
                         CallAudioRouteStateMachine.USER_SWITCH_BLUETOOTH, 0, bluetoothAddress);
                 return;
             case CallAudioState.ROUTE_SPEAKER:
-                mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+                mCallAudioRouteAdapter.sendMessageWithSessionInfo(
                         CallAudioRouteStateMachine.USER_SWITCH_SPEAKER);
                 return;
             case CallAudioState.ROUTE_WIRED_HEADSET:
-                mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+                mCallAudioRouteAdapter.sendMessageWithSessionInfo(
                         CallAudioRouteStateMachine.USER_SWITCH_HEADSET);
                 return;
             case CallAudioState.ROUTE_EARPIECE:
-                mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+                mCallAudioRouteAdapter.sendMessageWithSessionInfo(
                         CallAudioRouteStateMachine.USER_SWITCH_EARPIECE);
                 return;
             case CallAudioState.ROUTE_WIRED_OR_EARPIECE:
-                mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+                mCallAudioRouteAdapter.sendMessageWithSessionInfo(
                         CallAudioRouteStateMachine.USER_SWITCH_BASELINE_ROUTE,
                         CallAudioRouteStateMachine.NO_INCLUDE_BLUETOOTH_IN_BASELINE);
                 return;
@@ -481,7 +489,7 @@
      */
     void switchBaseline() {
         Log.i(this, "switchBaseline");
-        mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+        mCallAudioRouteAdapter.sendMessageWithSessionInfo(
                 CallAudioRouteStateMachine.USER_SWITCH_BASELINE_ROUTE,
                 CallAudioRouteStateMachine.INCLUDE_BLUETOOTH_IN_BASELINE);
     }
@@ -494,7 +502,7 @@
         boolean allCallSilenced = true;
         synchronized (mCallsManager.getLock()) {
             for (Call call : mRingingCalls) {
-                UserHandle userFromCall = call.getUserHandleFromTargetPhoneAccount();
+                UserHandle userFromCall = call.getAssociatedUser();
                 // Do not try to silence calls when calling user is different from the phone account
                 // user, the account does not have CAPABILITY_MULTI_USER enabled, or if the user
                 // does not have the INTERACT_ACROSS_USERS permission enabled.
@@ -525,7 +533,7 @@
         synchronized (mCallsManager.getLock()) {
             Call localForegroundCall = mForegroundCall;
             boolean result = mRinger.startRinging(localForegroundCall,
-                    mCallAudioRouteStateMachine.isHfpDeviceAvailable());
+                    mCallAudioRouteAdapter.isHfpDeviceAvailable());
             if (result) {
                 localForegroundCall.setStartRingTime();
             }
@@ -558,7 +566,7 @@
 
     @VisibleForTesting
     public void setCallAudioRouteFocusState(int focusState) {
-        mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+        mCallAudioRouteAdapter.sendMessageWithSessionInfo(
                 CallAudioRouteStateMachine.SWITCH_FOCUS, focusState);
     }
 
@@ -568,8 +576,8 @@
     }
 
     @VisibleForTesting
-    public CallAudioRouteStateMachine getCallAudioRouteStateMachine() {
-        return mCallAudioRouteStateMachine;
+    public CallAudioRouteAdapter getCallAudioRouteAdapter() {
+        return mCallAudioRouteAdapter;
     }
 
     @VisibleForTesting
@@ -606,9 +614,9 @@
         mCallAudioModeStateMachine.dump(pw);
         pw.decreaseIndent();
 
-        pw.println("CallAudioRouteStateMachine:");
+        pw.println("mCallAudioRouteAdapter:");
         pw.increaseIndent();
-        mCallAudioRouteStateMachine.dump(pw);
+        mCallAudioRouteAdapter.dump(pw);
         pw.decreaseIndent();
 
         pw.println("BluetoothDeviceManager:");
@@ -620,7 +628,7 @@
     }
 
     @VisibleForTesting
-    public void setIsTonePlaying(boolean isTonePlaying) {
+    public void setIsTonePlaying(Call call, boolean isTonePlaying) {
         Log.i(this, "setIsTonePlaying; isTonePlaying=%b", isTonePlaying);
         mIsTonePlaying = isTonePlaying;
         mCallAudioModeStateMachine.sendMessageWithArgs(
@@ -629,7 +637,7 @@
                 makeArgsForModeStateMachine());
 
         if (!isTonePlaying && mIsDisconnectedTonePlaying) {
-            mCallsManager.onDisconnectedTonePlaying(false);
+            mCallsManager.onDisconnectedTonePlaying(call, false);
             mIsDisconnectedTonePlaying = false;
         }
     }
@@ -758,6 +766,7 @@
 
     private void updateForegroundCall() {
         Call oldForegroundCall = mForegroundCall;
+
         if (mActiveDialingOrConnectingCalls.size() > 0) {
             // Give preference for connecting calls over active/dialing for foreground-ness.
             Call possibleConnectingCall = null;
@@ -766,8 +775,27 @@
                     possibleConnectingCall = call;
                 }
             }
-            mForegroundCall = possibleConnectingCall == null ?
-                    mActiveDialingOrConnectingCalls.iterator().next() : possibleConnectingCall;
+            if (mFeatureFlags.ensureAudioModeUpdatesOnForegroundCallChange()) {
+                // Prefer a connecting call
+                if (possibleConnectingCall != null) {
+                    mForegroundCall = possibleConnectingCall;
+                } else {
+                    // Next, prefer an active or dialing call which is not in the process of being
+                    // disconnected.
+                    mForegroundCall = mActiveDialingOrConnectingCalls
+                            .stream()
+                            .filter(c -> (c.getState() == CallState.ACTIVE
+                                    || c.getState() == CallState.DIALING)
+                                    && !c.isLocallyDisconnecting())
+                            .findFirst()
+                            // If we can't find one, then just fall back to the first one.
+                            .orElse(mActiveDialingOrConnectingCalls.iterator().next());
+                }
+            } else {
+                // Legacy (buggy) behavior.
+                mForegroundCall = possibleConnectingCall == null ?
+                        mActiveDialingOrConnectingCalls.iterator().next() : possibleConnectingCall;
+            }
         } else if (mRingingCalls.size() > 0) {
             mForegroundCall = mRingingCalls.iterator().next();
         } else if (mHoldingCalls.size() > 0) {
@@ -775,12 +803,27 @@
         } else {
             mForegroundCall = null;
         }
-
+        Log.i(this, "updateForegroundCall; oldFg=%s, newFg=%s, aDC=%s, ring=%s, hold=%s",
+                (oldForegroundCall == null ? "none" : oldForegroundCall.getId()),
+                (mForegroundCall == null ? "none" : mForegroundCall.getId()),
+                mActiveDialingOrConnectingCalls.stream().map(c -> c.getId()).collect(
+                        Collectors.joining(",")),
+                mRingingCalls.stream().map(c -> c.getId()).collect(Collectors.joining(",")),
+                mHoldingCalls.stream().map(c -> c.getId()).collect(Collectors.joining(","))
+        );
         if (mForegroundCall != oldForegroundCall) {
-            mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+            mCallAudioRouteAdapter.sendMessageWithSessionInfo(
                     CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE);
+
+            if (mForegroundCall != null
+                    && mFeatureFlags.ensureAudioModeUpdatesOnForegroundCallChange()) {
+                // Ensure the voip audio mode for the new foreground call is taken into account.
+                mCallAudioModeStateMachine.sendMessageWithArgs(
+                        CallAudioModeStateMachine.FOREGROUND_VOIP_MODE_CHANGE,
+                        makeArgsForModeStateMachine());
+            }
             mDtmfLocalTonePlayer.onForegroundCallChanged(oldForegroundCall, mForegroundCall);
-            maybePlayHoldTone();
+            maybePlayHoldTone(oldForegroundCall);
         }
     }
 
@@ -792,7 +835,7 @@
                 .setHasHoldingCalls(mHoldingCalls.size() > 0)
                 .setHasAudioProcessingCalls(mAudioProcessingCalls.size() > 0)
                 .setIsTonePlaying(mIsTonePlaying)
-                .setIsStreaming(mStreamingCall != null)
+                .setIsStreaming((mStreamingCall != null) && (!mStreamingCall.isDisconnected()))
                 .setForegroundCallIsVoip(
                         mForegroundCall != null && isCallVoip(mForegroundCall))
                 .setSession(Log.createSubsession()).build();
@@ -883,9 +926,9 @@
             Log.d(this, "Found a disconnected call with tone to play %d.", toneToPlay);
 
             if (toneToPlay != InCallTonePlayer.TONE_INVALID) {
-                boolean didToneStart = mPlayerFactory.createPlayer(toneToPlay).startTone();
+                boolean didToneStart = mPlayerFactory.createPlayer(call, toneToPlay).startTone();
                 if (didToneStart) {
-                    mCallsManager.onDisconnectedTonePlaying(true);
+                    mCallsManager.onDisconnectedTonePlaying(call, true);
                     mIsDisconnectedTonePlaying = true;
                 }
             }
@@ -905,10 +948,11 @@
     /**
      * Determines if a hold tone should be played and then starts or stops it accordingly.
      */
-    private void maybePlayHoldTone() {
+    private void maybePlayHoldTone(Call call) {
         if (shouldPlayHoldTone()) {
             if (mHoldTonePlayer == null) {
-                mHoldTonePlayer = mPlayerFactory.createPlayer(InCallTonePlayer.TONE_CALL_WAITING);
+                mHoldTonePlayer = mPlayerFactory.createPlayer(call,
+                        InCallTonePlayer.TONE_CALL_WAITING);
                 mHoldTonePlayer.startTone();
             }
         } else {
diff --git a/src/com/android/server/telecom/CallAudioModeStateMachine.java b/src/com/android/server/telecom/CallAudioModeStateMachine.java
index 3ced36d..71956a1 100644
--- a/src/com/android/server/telecom/CallAudioModeStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioModeStateMachine.java
@@ -16,6 +16,8 @@
 
 package com.android.server.telecom;
 
+import android.media.AudioAttributes;
+import android.media.AudioFocusRequest;
 import android.media.AudioManager;
 import android.os.Looper;
 import android.os.Message;
@@ -29,6 +31,7 @@
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
+import com.android.server.telecom.flags.FeatureFlags;
 
 public class CallAudioModeStateMachine extends StateMachine {
     /**
@@ -38,11 +41,29 @@
     private LocalLog mLocalLog = new LocalLog(20);
     public static class Factory {
         public CallAudioModeStateMachine create(SystemStateHelper systemStateHelper,
-                AudioManager am) {
-            return new CallAudioModeStateMachine(systemStateHelper, am);
+                AudioManager am, FeatureFlags featureFlags,
+                CallAudioCommunicationDeviceTracker callAudioCommunicationDeviceTracker) {
+            return new CallAudioModeStateMachine(systemStateHelper, am,
+                    featureFlags, callAudioCommunicationDeviceTracker);
         }
     }
 
+    private static final AudioAttributes RING_AUDIO_ATTRIBUTES = new AudioAttributes.Builder()
+            .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
+            .setLegacyStreamType(AudioManager.STREAM_RING)
+            .build();
+    public static final AudioFocusRequest RING_AUDIO_FOCUS_REQUEST = new AudioFocusRequest
+            .Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT)
+            .setAudioAttributes(RING_AUDIO_ATTRIBUTES).build();
+
+    private static final AudioAttributes CALL_AUDIO_ATTRIBUTES = new AudioAttributes.Builder()
+            .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
+            .setLegacyStreamType(AudioManager.STREAM_VOICE_CALL)
+            .build();
+    public static final AudioFocusRequest CALL_AUDIO_FOCUS_REQUEST = new AudioFocusRequest
+            .Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT)
+            .setAudioAttributes(CALL_AUDIO_ATTRIBUTES).build();
+
     public static class MessageArgs {
         public boolean hasActiveOrDialingCalls;
         public boolean hasRingingCalls;
@@ -212,6 +233,8 @@
     public static final String STREAMING_STATE_NAME = StreamingFocusState.class.getSimpleName();
     public static final String COMMS_STATE_NAME = VoipCallFocusState.class.getSimpleName();
 
+    private AudioFocusRequest mCurrentAudioFocusRequest = null;
+
     private class BaseState extends State {
         @Override
         public boolean processMessage(Message msg) {
@@ -256,8 +279,20 @@
             Log.i(LOG_TAG, "Audio focus entering UNFOCUSED state");
             mLocalLog.log("Enter UNFOCUSED");
             if (mIsInitialized) {
-                mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.NO_FOCUS);
-                mAudioManager.setMode(AudioManager.MODE_NORMAL);
+                // Clear any communication device that was requested previously.
+                if (mFeatureFlags.callAudioCommunicationDeviceRefactor()) {
+                    mCommunicationDeviceTracker.clearCommunicationDevice(mCommunicationDeviceTracker
+                            .getCurrentLocallyRequestedCommunicationDevice());
+                }
+                if (mFeatureFlags.setAudioModeBeforeAbandonFocus()) {
+                    mAudioManager.setMode(AudioManager.MODE_NORMAL);
+                    mCallAudioManager.setCallAudioRouteFocusState(
+                            CallAudioRouteStateMachine.NO_FOCUS);
+                } else {
+                    mCallAudioManager.setCallAudioRouteFocusState(
+                            CallAudioRouteStateMachine.NO_FOCUS);
+                    mAudioManager.setMode(AudioManager.MODE_NORMAL);
+                }
                 mLocalLog.log("Mode MODE_NORMAL");
                 mMostRecentMode = AudioManager.MODE_NORMAL;
                 // Don't release focus here -- wait until we get a signal that any other audio
@@ -310,7 +345,14 @@
                     return HANDLED;
                 case AUDIO_OPERATIONS_COMPLETE:
                     Log.i(LOG_TAG, "Abandoning audio focus: now UNFOCUSED");
-                    mAudioManager.abandonAudioFocusForCall();
+                    if (mFeatureFlags.telecomResolveHiddenDependencies()) {
+                        if (mCurrentAudioFocusRequest != null) {
+                            mAudioManager.abandonAudioFocusRequest(mCurrentAudioFocusRequest);
+                            mCurrentAudioFocusRequest = null;
+                        }
+                    } else {
+                        mAudioManager.abandonAudioFocusForCall();
+                    }
                     return HANDLED;
                 default:
                     // The forced focus switch commands are handled by BaseState.
@@ -381,7 +423,14 @@
                     return HANDLED;
                 case AUDIO_OPERATIONS_COMPLETE:
                     Log.i(LOG_TAG, "Abandoning audio focus: now AUDIO_PROCESSING");
-                    mAudioManager.abandonAudioFocusForCall();
+                    if (mFeatureFlags.telecomResolveHiddenDependencies()) {
+                        if (mCurrentAudioFocusRequest != null) {
+                            mAudioManager.abandonAudioFocusRequest(mCurrentAudioFocusRequest);
+                            mCurrentAudioFocusRequest = null;
+                        }
+                    } else {
+                        mAudioManager.abandonAudioFocusForCall();
+                    }
                     return HANDLED;
                 default:
                     // The forced focus switch commands are handled by BaseState.
@@ -406,8 +455,13 @@
                 }
 
                 if (mCallAudioManager.startRinging()) {
-                    mAudioManager.requestAudioFocusForCall(
-                        AudioManager.STREAM_RING, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
+                    if (mFeatureFlags.telecomResolveHiddenDependencies()) {
+                        mCurrentAudioFocusRequest = RING_AUDIO_FOCUS_REQUEST;
+                        mAudioManager.requestAudioFocus(RING_AUDIO_FOCUS_REQUEST);
+                    } else {
+                        mAudioManager.requestAudioFocusForCall(
+                                AudioManager.STREAM_RING, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
+                    }
                     // Do not set MODE_RINGTONE if we were previously in the CALL_SCREENING mode --
                     // this trips up the audio system.
                     if (mAudioManager.getMode() != AudioManager.MODE_CALL_SCREENING) {
@@ -504,8 +558,13 @@
         public void enter() {
             Log.i(LOG_TAG, "Audio focus entering SIM CALL state");
             mLocalLog.log("Enter SIM_CALL");
-            mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_VOICE_CALL,
-                    AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
+            if (mFeatureFlags.telecomResolveHiddenDependencies()) {
+                mCurrentAudioFocusRequest = CALL_AUDIO_FOCUS_REQUEST;
+                mAudioManager.requestAudioFocus(CALL_AUDIO_FOCUS_REQUEST);
+            } else {
+                mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_VOICE_CALL,
+                        AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
+            }
             mAudioManager.setMode(AudioManager.MODE_IN_CALL);
             mLocalLog.log("Mode MODE_IN_CALL");
             mMostRecentMode = AudioManager.MODE_IN_CALL;
@@ -587,8 +646,13 @@
         public void enter() {
             Log.i(LOG_TAG, "Audio focus entering VOIP CALL state");
             mLocalLog.log("Enter VOIP_CALL");
-            mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_VOICE_CALL,
-                    AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
+            if (mFeatureFlags.telecomResolveHiddenDependencies()) {
+                mCurrentAudioFocusRequest = CALL_AUDIO_FOCUS_REQUEST;
+                mAudioManager.requestAudioFocus(CALL_AUDIO_FOCUS_REQUEST);
+            } else {
+                mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_VOICE_CALL,
+                        AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
+            }
             mAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
             mLocalLog.log("Mode MODE_IN_COMMUNICATION");
             mMostRecentMode = AudioManager.MODE_IN_COMMUNICATION;
@@ -665,15 +729,17 @@
         @Override
         public void enter() {
             Log.i(LOG_TAG, "Audio focus entering streaming state");
-            mAudioManager.setMode(AudioManager.MODE_CALL_REDIRECT);
+            mLocalLog.log("Enter Streaming");
+            mLocalLog.log("Mode MODE_COMMUNICATION_REDIRECT");
+            mAudioManager.setMode(AudioManager.MODE_COMMUNICATION_REDIRECT);
             mMostRecentMode = AudioManager.MODE_NORMAL;
             mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.ACTIVE_FOCUS);
-            mCallAudioManager.getCallAudioRouteStateMachine().sendMessageWithSessionInfo(
+            mCallAudioManager.getCallAudioRouteAdapter().sendMessageWithSessionInfo(
                     CallAudioRouteStateMachine.STREAMING_FORCE_ENABLED);
         }
 
         private void preExit() {
-            mCallAudioManager.getCallAudioRouteStateMachine().sendMessageWithSessionInfo(
+            mCallAudioManager.getCallAudioRouteAdapter().sendMessageWithSessionInfo(
                     CallAudioRouteStateMachine.STREAMING_FORCE_DISABLED);
         }
 
@@ -685,7 +751,8 @@
             MessageArgs args = (MessageArgs) msg.obj;
             switch (msg.what) {
                 case NO_MORE_ACTIVE_OR_DIALING_CALLS:
-                    // Do nothing.
+                    // Switch to either ringing, holding, or inactive
+                    transitionTo(calculateProperStateFromArgs(args));
                     return HANDLED;
                 case NO_MORE_RINGING_CALLS:
                     // Do nothing.
@@ -739,8 +806,13 @@
         public void enter() {
             Log.i(LOG_TAG, "Audio focus entering TONE/HOLDING state");
             mLocalLog.log("Enter TONE/HOLDING");
-            mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_VOICE_CALL,
-                    AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
+            if (mFeatureFlags.telecomResolveHiddenDependencies()) {
+                mCurrentAudioFocusRequest = CALL_AUDIO_FOCUS_REQUEST;
+                mAudioManager.requestAudioFocus(CALL_AUDIO_FOCUS_REQUEST);
+            } else {
+                mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_VOICE_CALL,
+                        AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
+            }
             mAudioManager.setMode(mMostRecentMode);
             mLocalLog.log("Mode " + mMostRecentMode);
             mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.ACTIVE_FOCUS);
@@ -812,16 +884,21 @@
     private final AudioManager mAudioManager;
     private final SystemStateHelper mSystemStateHelper;
     private CallAudioManager mCallAudioManager;
+    private FeatureFlags mFeatureFlags;
+    private CallAudioCommunicationDeviceTracker mCommunicationDeviceTracker;
 
     private int mMostRecentMode;
     private boolean mIsInitialized = false;
 
     public CallAudioModeStateMachine(SystemStateHelper systemStateHelper,
-            AudioManager audioManager) {
+            AudioManager audioManager, FeatureFlags featureFlags,
+            CallAudioCommunicationDeviceTracker callAudioCommunicationDeviceTracker) {
         super(CallAudioModeStateMachine.class.getSimpleName());
         mAudioManager = audioManager;
         mSystemStateHelper = systemStateHelper;
         mMostRecentMode = AudioManager.MODE_NORMAL;
+        mFeatureFlags = featureFlags;
+        mCommunicationDeviceTracker = callAudioCommunicationDeviceTracker;
 
         createStates();
     }
@@ -830,11 +907,14 @@
      * Used for testing
      */
     public CallAudioModeStateMachine(SystemStateHelper systemStateHelper,
-            AudioManager audioManager, Looper looper) {
+            AudioManager audioManager, Looper looper, FeatureFlags featureFlags,
+            CallAudioCommunicationDeviceTracker communicationDeviceTracker) {
         super(CallAudioModeStateMachine.class.getSimpleName(), looper);
         mAudioManager = audioManager;
         mSystemStateHelper = systemStateHelper;
         mMostRecentMode = AudioManager.MODE_NORMAL;
+        mFeatureFlags = featureFlags;
+        mCommunicationDeviceTracker = communicationDeviceTracker;
 
         createStates();
     }
diff --git a/src/com/android/server/telecom/CallAudioRouteAdapter.java b/src/com/android/server/telecom/CallAudioRouteAdapter.java
new file mode 100644
index 0000000..7f7b43c
--- /dev/null
+++ b/src/com/android/server/telecom/CallAudioRouteAdapter.java
@@ -0,0 +1,19 @@
+package com.android.server.telecom;
+
+import android.os.Handler;
+import android.telecom.CallAudioState;
+
+import com.android.internal.util.IndentingPrintWriter;
+
+public interface CallAudioRouteAdapter {
+    void initialize();
+    void sendMessageWithSessionInfo(int message);
+    void sendMessageWithSessionInfo(int message, int arg);
+    void sendMessageWithSessionInfo(int message, int arg, String data);
+    void sendMessage(int message, Runnable r);
+    void setCallAudioManager(CallAudioManager callAudioManager);
+    CallAudioState getCurrentCallAudioState();
+    boolean isHfpDeviceAvailable();
+    Handler getAdapterHandler();
+    void dump(IndentingPrintWriter pw);
+}
diff --git a/src/com/android/server/telecom/CallAudioRouteController.java b/src/com/android/server/telecom/CallAudioRouteController.java
new file mode 100644
index 0000000..f8c49bb
--- /dev/null
+++ b/src/com/android/server/telecom/CallAudioRouteController.java
@@ -0,0 +1,64 @@
+package com.android.server.telecom;
+
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.telecom.CallAudioState;
+
+import com.android.internal.util.IndentingPrintWriter;
+
+public class CallAudioRouteController implements CallAudioRouteAdapter {
+    private Handler mHandler;
+
+    public CallAudioRouteController() {
+        HandlerThread handlerThread = new HandlerThread(this.getClass().getSimpleName());
+        handlerThread.start();
+        mHandler = new Handler(handlerThread.getLooper());
+    }
+    @Override
+    public void initialize() {
+    }
+
+    @Override
+    public void sendMessageWithSessionInfo(int message) {
+    }
+
+    @Override
+    public void sendMessageWithSessionInfo(int message, int arg) {
+
+    }
+
+    @Override
+    public void sendMessageWithSessionInfo(int message, int arg, String data) {
+
+    }
+
+    @Override
+    public void sendMessage(int message, Runnable r) {
+
+    }
+
+    @Override
+    public void setCallAudioManager(CallAudioManager callAudioManager) {
+    }
+
+    @Override
+    public CallAudioState getCurrentCallAudioState() {
+        return null;
+    }
+
+    @Override
+    public boolean isHfpDeviceAvailable() {
+        return false;
+    }
+
+    @Override
+    public Handler getAdapterHandler() {
+        return mHandler;
+    }
+
+    @Override
+    public void dump(IndentingPrintWriter pw) {
+
+    }
+}
diff --git a/src/com/android/server/telecom/CallAudioRoutePeripheralAdapter.java b/src/com/android/server/telecom/CallAudioRoutePeripheralAdapter.java
index d96f953..8a87c22 100644
--- a/src/com/android/server/telecom/CallAudioRoutePeripheralAdapter.java
+++ b/src/com/android/server/telecom/CallAudioRoutePeripheralAdapter.java
@@ -25,16 +25,19 @@
 public class CallAudioRoutePeripheralAdapter implements WiredHeadsetManager.Listener,
         DockManager.Listener, BluetoothRouteManager.BluetoothStateListener {
 
-    private final CallAudioRouteStateMachine mCallAudioRouteStateMachine;
+    private final CallAudioRouteAdapter mCallAudioAdapter;
     private final BluetoothRouteManager mBluetoothRouteManager;
+    private final AsyncRingtonePlayer mRingtonePlayer;
 
     public CallAudioRoutePeripheralAdapter(
-            CallAudioRouteStateMachine callAudioRouteStateMachine,
+            CallAudioRouteAdapter callAudioRouteAdapter,
             BluetoothRouteManager bluetoothManager,
             WiredHeadsetManager wiredHeadsetManager,
-            DockManager dockManager) {
-        mCallAudioRouteStateMachine = callAudioRouteStateMachine;
+            DockManager dockManager,
+            AsyncRingtonePlayer ringtonePlayer) {
+        mCallAudioAdapter = callAudioRouteAdapter;
         mBluetoothRouteManager = bluetoothManager;
+        mRingtonePlayer = ringtonePlayer;
 
         mBluetoothRouteManager.setListener(this);
         wiredHeadsetManager.addListener(this);
@@ -57,37 +60,47 @@
 
     @Override
     public void onBluetoothDeviceListChanged() {
-        mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+        mCallAudioAdapter.sendMessageWithSessionInfo(
                 CallAudioRouteStateMachine.BLUETOOTH_DEVICE_LIST_CHANGED);
     }
 
     @Override
     public void onBluetoothActiveDevicePresent() {
-        mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+        mCallAudioAdapter.sendMessageWithSessionInfo(
                 CallAudioRouteStateMachine.BT_ACTIVE_DEVICE_PRESENT);
     }
 
     @Override
     public void onBluetoothActiveDeviceGone() {
-        mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+        mCallAudioAdapter.sendMessageWithSessionInfo(
                 CallAudioRouteStateMachine.BT_ACTIVE_DEVICE_GONE);
     }
 
     @Override
     public void onBluetoothAudioConnected() {
-        mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+        mRingtonePlayer.updateBtActiveState(true);
+        mCallAudioAdapter.sendMessageWithSessionInfo(
+                CallAudioRouteStateMachine.BT_AUDIO_CONNECTED);
+    }
+
+    @Override
+    public void onBluetoothAudioConnecting() {
+        mRingtonePlayer.updateBtActiveState(false);
+        // Pretend like audio is connected when communicating w/ CARSM.
+        mCallAudioAdapter.sendMessageWithSessionInfo(
                 CallAudioRouteStateMachine.BT_AUDIO_CONNECTED);
     }
 
     @Override
     public void onBluetoothAudioDisconnected() {
-        mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+        mRingtonePlayer.updateBtActiveState(false);
+        mCallAudioAdapter.sendMessageWithSessionInfo(
                 CallAudioRouteStateMachine.BT_AUDIO_DISCONNECTED);
     }
 
     @Override
     public void onUnexpectedBluetoothStateChange() {
-        mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+        mCallAudioAdapter.sendMessageWithSessionInfo(
                 CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE);
     }
 
@@ -98,17 +111,17 @@
     @Override
     public void onWiredHeadsetPluggedInChanged(boolean oldIsPluggedIn, boolean newIsPluggedIn) {
         if (!oldIsPluggedIn && newIsPluggedIn) {
-            mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+            mCallAudioAdapter.sendMessageWithSessionInfo(
                     CallAudioRouteStateMachine.CONNECT_WIRED_HEADSET);
         } else if (oldIsPluggedIn && !newIsPluggedIn){
-            mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+            mCallAudioAdapter.sendMessageWithSessionInfo(
                     CallAudioRouteStateMachine.DISCONNECT_WIRED_HEADSET);
         }
     }
 
     @Override
     public void onDockChanged(boolean isDocked) {
-        mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+        mCallAudioAdapter.sendMessageWithSessionInfo(
                 isDocked ? CallAudioRouteStateMachine.CONNECT_DOCK
                         : CallAudioRouteStateMachine.DISCONNECT_DOCK
         );
diff --git a/src/com/android/server/telecom/CallAudioRouteStateMachine.java b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
index e87c298..c0bb50e 100644
--- a/src/com/android/server/telecom/CallAudioRouteStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
@@ -28,6 +28,7 @@
 import android.media.AudioManager;
 import android.media.IAudioService;
 import android.os.Binder;
+import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
@@ -44,11 +45,13 @@
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
 import com.android.server.telecom.bluetooth.BluetoothRouteManager;
+import com.android.server.telecom.flags.FeatureFlags;
 
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.Executor;
 
 /**
  * This class describes the available routes of a call as a state machine.
@@ -71,7 +74,7 @@
  *     from a wired headset
  * mIsMuted: a boolean indicating whether the audio is muted
  */
-public class CallAudioRouteStateMachine extends StateMachine {
+public class CallAudioRouteStateMachine extends StateMachine implements CallAudioRouteAdapter {
 
     public static class Factory {
         public CallAudioRouteStateMachine create(
@@ -81,14 +84,20 @@
                 WiredHeadsetManager wiredHeadsetManager,
                 StatusBarNotifier statusBarNotifier,
                 CallAudioManager.AudioServiceFactory audioServiceFactory,
-                int earpieceControl) {
+                int earpieceControl,
+                Executor asyncTaskExecutor,
+                CallAudioCommunicationDeviceTracker communicationDeviceTracker,
+                FeatureFlags featureFlags) {
             return new CallAudioRouteStateMachine(context,
                     callsManager,
                     bluetoothManager,
                     wiredHeadsetManager,
                     statusBarNotifier,
                     audioServiceFactory,
-                    earpieceControl);
+                    earpieceControl,
+                    asyncTaskExecutor,
+                    communicationDeviceTracker,
+                    featureFlags);
         }
     }
     /** Values for CallAudioRouteStateMachine constructor's earPieceRouting arg. */
@@ -368,6 +377,10 @@
         public void enter() {
             super.enter();
             setSpeakerphoneOn(false);
+            if (mFeatureFlags.callAudioCommunicationDeviceRefactor()) {
+                mCommunicationDeviceTracker.setCommunicationDevice(
+                        AudioDeviceInfo.TYPE_BUILTIN_EARPIECE, null);
+            }
             CallAudioState newState = new CallAudioState(mIsMuted, ROUTE_EARPIECE,
                     mAvailableRoutes, null,
                     mBluetoothRouteManager.getConnectedDevices());
@@ -398,6 +411,10 @@
                 case SWITCH_BLUETOOTH:
                 case USER_SWITCH_BLUETOOTH:
                     if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0) {
+                        if (mFeatureFlags.callAudioCommunicationDeviceRefactor()) {
+                            mCommunicationDeviceTracker.clearCommunicationDevice(
+                                    AudioDeviceInfo.TYPE_BUILTIN_EARPIECE);
+                        }
                         if (mAudioFocusType == ACTIVE_FOCUS
                                 || mBluetoothRouteManager.isInbandRingingEnabled()) {
                             String address = (msg.obj instanceof SomeArgs) ?
@@ -414,6 +431,10 @@
                 case SWITCH_HEADSET:
                 case USER_SWITCH_HEADSET:
                     if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) {
+                        if (mFeatureFlags.callAudioCommunicationDeviceRefactor()) {
+                            mCommunicationDeviceTracker.clearCommunicationDevice(
+                                    AudioDeviceInfo.TYPE_BUILTIN_EARPIECE);
+                        }
                         transitionTo(mActiveHeadsetRoute);
                     } else {
                         Log.w(this, "Ignoring switch to headset command. Not available.");
@@ -423,6 +444,10 @@
                     // fall through; we want to switch to speaker mode when docked and in a call.
                 case SWITCH_SPEAKER:
                 case USER_SWITCH_SPEAKER:
+                    if (mFeatureFlags.callAudioCommunicationDeviceRefactor()) {
+                        mCommunicationDeviceTracker.clearCommunicationDevice(
+                                AudioDeviceInfo.TYPE_BUILTIN_EARPIECE);
+                    }
                     setSpeakerphoneOn(true);
                     // fall through
                 case SPEAKER_ON:
@@ -576,6 +601,10 @@
         public void enter() {
             super.enter();
             setSpeakerphoneOn(false);
+            if (mFeatureFlags.callAudioCommunicationDeviceRefactor()) {
+                mCommunicationDeviceTracker.setCommunicationDevice(
+                        AudioDeviceInfo.TYPE_WIRED_HEADSET, null);
+            }
             CallAudioState newState = new CallAudioState(mIsMuted, ROUTE_WIRED_HEADSET,
                     mAvailableRoutes, null, mBluetoothRouteManager.getConnectedDevices());
             setSystemAudioState(newState, true);
@@ -597,6 +626,10 @@
                 case SWITCH_EARPIECE:
                 case USER_SWITCH_EARPIECE:
                     if ((mAvailableRoutes & ROUTE_EARPIECE) != 0) {
+                        if (mFeatureFlags.callAudioCommunicationDeviceRefactor()) {
+                            mCommunicationDeviceTracker.clearCommunicationDevice(
+                                    AudioDeviceInfo.TYPE_WIRED_HEADSET);
+                        }
                         transitionTo(mActiveEarpieceRoute);
                     } else {
                         Log.w(this, "Ignoring switch to earpiece command. Not available.");
@@ -612,6 +645,10 @@
                                 || mBluetoothRouteManager.isInbandRingingEnabled()) {
                             String address = (msg.obj instanceof SomeArgs) ?
                                     (String) ((SomeArgs) msg.obj).arg2 : null;
+                            if (mFeatureFlags.callAudioCommunicationDeviceRefactor()) {
+                                mCommunicationDeviceTracker.clearCommunicationDevice(
+                                        AudioDeviceInfo.TYPE_WIRED_HEADSET);
+                            }
                             // Omit transition to ActiveBluetoothRoute until actual connection.
                             setBluetoothOn(address);
                         } else {
@@ -628,6 +665,10 @@
                     return HANDLED;
                 case SWITCH_SPEAKER:
                 case USER_SWITCH_SPEAKER:
+                    if (mFeatureFlags.callAudioCommunicationDeviceRefactor()) {
+                        mCommunicationDeviceTracker.clearCommunicationDevice(
+                                AudioDeviceInfo.TYPE_WIRED_HEADSET);
+                    }
                     setSpeakerphoneOn(true);
                     // fall through
                 case SPEAKER_ON:
@@ -790,6 +831,17 @@
         public void enter() {
             super.enter();
             setSpeakerphoneOn(false);
+            // Try arbitrarily connecting to BT audio if we haven't already. This handles
+            // the edge case of when the audio route is in a quiescent route while in-call and
+            // the BT connection fails to be set. Previously, the logic was to setBluetoothOn in
+            // ACTIVE_FOCUS but the route would still remain in a quiescent route, so instead we
+            // should be transitioning directly into the active route.
+            if (mFeatureFlags.callAudioCommunicationDeviceRefactor()) {
+                setBluetoothOn(null);
+            }
+            if (mFeatureFlags.updateRouteMaskWhenBtConnected()) {
+                mAvailableRoutes |= ROUTE_BLUETOOTH;
+            }
             CallAudioState newState = new CallAudioState(mIsMuted, ROUTE_BLUETOOTH,
                     mAvailableRoutes, mBluetoothRouteManager.getBluetoothAudioConnectedDevice(),
                     mBluetoothRouteManager.getConnectedDevices());
@@ -890,8 +942,13 @@
                 case SWITCH_FOCUS:
                     if (msg.arg1 == NO_FOCUS) {
                         // Only disconnect audio here instead of routing away from BT entirely.
-                        mBluetoothRouteManager.disconnectAudio();
-                        reinitialize();
+                        if (mFeatureFlags.transitRouteBeforeAudioDisconnectBt()) {
+                            transitionTo(mQuiescentBluetoothRoute);
+                            mBluetoothRouteManager.disconnectAudio();
+                        } else {
+                            mBluetoothRouteManager.disconnectAudio();
+                            reinitialize();
+                        }
                         mCallAudioManager.notifyAudioOperationsComplete();
                     } else if (msg.arg1 == RINGING_FOCUS
                             && !mBluetoothRouteManager.isInbandRingingEnabled()) {
@@ -1016,6 +1073,9 @@
         public void enter() {
             super.enter();
             mHasUserExplicitlyLeftBluetooth = false;
+            if (mFeatureFlags.resetMuteWhenEnteringQuiescentBtRoute()) {
+                setMuteOn(false);
+            }
             updateInternalCallAudioState();
         }
 
@@ -1062,7 +1122,13 @@
                     return HANDLED;
                 case SWITCH_FOCUS:
                     if (msg.arg1 == ACTIVE_FOCUS) {
-                        setBluetoothOn(null);
+                        // It is possible that the connection to BT will fail while in-call, in
+                        // which case, we want to transition into the active route.
+                        if (mFeatureFlags.callAudioCommunicationDeviceRefactor()) {
+                            transitionTo(mActiveBluetoothRoute);
+                        } else {
+                            setBluetoothOn(null);
+                        }
                     } else if (msg.arg1 == RINGING_FOCUS) {
                         if (mBluetoothRouteManager.isInbandRingingEnabled()) {
                             setBluetoothOn(null);
@@ -1212,7 +1278,13 @@
                     // Expected, since we just transitioned here
                     return HANDLED;
                 case SPEAKER_OFF:
-                    sendInternalMessage(SWITCH_BASELINE_ROUTE, INCLUDE_BLUETOOTH_IN_BASELINE);
+                    // Check if we already requested to connect to other devices and just waiting
+                    // for their response. In some cases, this SPEAKER_OFF message may come in
+                    // before the response, we can just ignore the message here to not re-evaluate
+                    // the baseline route incorrectly
+                    if (!mBluetoothRouteManager.isBluetoothAudioConnectedOrPending()) {
+                        sendInternalMessage(SWITCH_BASELINE_ROUTE, INCLUDE_BLUETOOTH_IN_BASELINE);
+                    }
                     return HANDLED;
                 case SWITCH_FOCUS:
                     if (msg.arg1 == NO_FOCUS) {
@@ -1479,6 +1551,8 @@
     private final QuiescentSpeakerRoute mQuiescentSpeakerRoute = new QuiescentSpeakerRoute();
     private final StreamingState mStreamingState = new StreamingState();
 
+    private final Executor mAsyncTaskExecutor;
+
     /**
      * A few pieces of hidden state. Used to avoid exponential explosion of number of explicit
      * states
@@ -1509,6 +1583,8 @@
     private CallAudioState mLastKnownCallAudioState;
 
     private CallAudioManager mCallAudioManager;
+    private CallAudioCommunicationDeviceTracker mCommunicationDeviceTracker;
+    private FeatureFlags mFeatureFlags;
 
     public CallAudioRouteStateMachine(
             Context context,
@@ -1517,7 +1593,10 @@
             WiredHeadsetManager wiredHeadsetManager,
             StatusBarNotifier statusBarNotifier,
             CallAudioManager.AudioServiceFactory audioServiceFactory,
-            int earpieceControl) {
+            int earpieceControl,
+            Executor asyncTaskExecutor,
+            CallAudioCommunicationDeviceTracker communicationDeviceTracker,
+            FeatureFlags featureFlags) {
         super(NAME);
         mContext = context;
         mCallsManager = callsManager;
@@ -1527,7 +1606,9 @@
         mStatusBarNotifier = statusBarNotifier;
         mAudioServiceFactory = audioServiceFactory;
         mLock = callsManager.getLock();
-
+        mAsyncTaskExecutor = asyncTaskExecutor;
+        mCommunicationDeviceTracker = communicationDeviceTracker;
+        mFeatureFlags = featureFlags;
         createStates(earpieceControl);
     }
 
@@ -1539,7 +1620,9 @@
             WiredHeadsetManager wiredHeadsetManager,
             StatusBarNotifier statusBarNotifier,
             CallAudioManager.AudioServiceFactory audioServiceFactory,
-            int earpieceControl, Looper looper) {
+            int earpieceControl, Looper looper, Executor asyncTaskExecutor,
+            CallAudioCommunicationDeviceTracker communicationDeviceTracker,
+            FeatureFlags featureFlags) {
         super(NAME, looper);
         mContext = context;
         mCallsManager = callsManager;
@@ -1549,7 +1632,9 @@
         mStatusBarNotifier = statusBarNotifier;
         mAudioServiceFactory = audioServiceFactory;
         mLock = callsManager.getLock();
-
+        mAsyncTaskExecutor = asyncTaskExecutor;
+        mCommunicationDeviceTracker = communicationDeviceTracker;
+        mFeatureFlags = featureFlags;
         createStates(earpieceControl);
     }
 
@@ -1622,15 +1707,23 @@
         mAvailableRoutes = mDeviceSupportedRoutes & getCurrentCallSupportedRoutes();
         mIsMuted = initState.isMuted();
         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));
+        IntentFilter micMuteChangedFilter = new IntentFilter(
+                AudioManager.ACTION_MICROPHONE_MUTE_CHANGED);
+        micMuteChangedFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
+        mContext.registerReceiver(mMuteChangeReceiver, micMuteChangedFilter);
+
+        IntentFilter muteChangedFilter = new IntentFilter(AudioManager.STREAM_MUTE_CHANGED_ACTION);
+        muteChangedFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
+        mContext.registerReceiver(mMuteChangeReceiver, muteChangedFilter);
+
+        IntentFilter speakerChangedFilter = new IntentFilter(
+                AudioManager.ACTION_SPEAKERPHONE_STATE_CHANGED);
+        speakerChangedFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
+        mContext.registerReceiver(mSpeakerPhoneChangeReceiver, speakerChangedFilter);
 
         mStatusBarNotifier.notifyMute(initState.isMuted());
-        mStatusBarNotifier.notifySpeakerphone(initState.getRoute() == CallAudioState.ROUTE_SPEAKER);
+        // We used to call mStatusBarNotifier.notifySpeakerphone, but that makes no sense as there
+        // is never a call at this boot (init) time.
         setInitialState(mRouteCodeToQuiescentState.get(initState.getRoute()));
         start();
     }
@@ -1658,6 +1751,11 @@
         sendMessage(message, arg, 0, args);
     }
 
+    @Override
+    public void sendMessage(int message, Runnable r) {
+        super.sendMessage(message, r);
+    }
+
     /**
      * This is for state-independent changes in audio route (i.e. muting or runnables)
      * @param msg that couldn't be handled.
@@ -1687,9 +1785,19 @@
                 }
                 return;
             case UPDATE_SYSTEM_AUDIO_ROUTE:
-                updateInternalCallAudioState();
-                updateRouteForForegroundCall();
-                resendSystemAudioState();
+                if (mFeatureFlags.availableRoutesNeverUpdatedAfterSetSystemAudioState()) {
+                    // Ensure available routes is updated.
+                    updateRouteForForegroundCall();
+                    // Ensure current audio state gets updated to take this into account.
+                    updateInternalCallAudioState();
+                    // Either resend the current audio state as it stands, or update to reflect any
+                    // changes put into place based on mAvailableRoutes
+                    setSystemAudioState(mCurrentCallAudioState, true);
+                } else {
+                    updateInternalCallAudioState();
+                    updateRouteForForegroundCall();
+                    resendSystemAudioState();
+                }
                 return;
             case RUN_RUNNABLE:
                 java.lang.Runnable r = (java.lang.Runnable) msg.obj;
@@ -1714,7 +1822,7 @@
     }
 
     public void dumpPendingMessages(IndentingPrintWriter pw) {
-        getHandler().getLooper().dump(pw::println, "");
+        getAdapterHandler().getLooper().dump(pw::println, "");
     }
 
     public boolean isHfpDeviceAvailable() {
@@ -1723,26 +1831,22 @@
 
     private void setSpeakerphoneOn(boolean on) {
         Log.i(this, "turning speaker phone %s", on);
-        AudioDeviceInfo speakerDevice = null;
-        for (AudioDeviceInfo info : mAudioManager.getAvailableCommunicationDevices()) {
-            if (info.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) {
-                speakerDevice = info;
-                break;
-            }
-        }
+        final boolean hasAnyCalls = mCallsManager.hasAnyCalls();
+        // These APIs are all via two-way binder calls so can potentially block Telecom.  Since none
+        // of this has to happen in the Telecom lock we'll offload it to the async executor.
         boolean speakerOn = false;
-        if (speakerDevice != null && on) {
-            boolean result = mAudioManager.setCommunicationDevice(speakerDevice);
-            if (result) {
-                speakerOn = true;
+        if (mFeatureFlags.callAudioCommunicationDeviceRefactor()) {
+            if (on) {
+                speakerOn = mCommunicationDeviceTracker.setCommunicationDevice(
+                        AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, null);
+            } else {
+                mCommunicationDeviceTracker.clearCommunicationDevice(
+                        AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
             }
         } else {
-            AudioDeviceInfo curDevice = mAudioManager.getCommunicationDevice();
-            if (curDevice != null && curDevice.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) {
-                mAudioManager.clearCommunicationDevice();
-            }
+            speakerOn = processLegacySpeakerCommunicationDevice(on);
         }
-        mStatusBarNotifier.notifySpeakerphone(speakerOn);
+        mStatusBarNotifier.notifySpeakerphone(hasAnyCalls && speakerOn);
     }
 
     private void setBluetoothOn(String address) {
@@ -1840,6 +1944,11 @@
         setSystemAudioState(mLastKnownCallAudioState, true);
     }
 
+    @VisibleForTesting
+    public CallAudioState getLastKnownCallAudioState() {
+        return mLastKnownCallAudioState;
+    }
+
     private void setSystemAudioState(CallAudioState newCallAudioState, boolean force) {
         synchronized (mLock) {
             Log.i(this, "setSystemAudioState: changing from %s to %s", mLastKnownCallAudioState,
@@ -1957,6 +2066,58 @@
         return false;
     }
 
+    private boolean isWatchActiveOrOnlyWatchesAvailable() {
+        if (!mFeatureFlags.ignoreAutoRouteToWatchDevice()) {
+            Log.i(this, "isWatchActiveOrOnlyWatchesAvailable: Flag is disabled.");
+            return false;
+        }
+
+        boolean containsWatchDevice = false;
+        boolean containsNonWatchDevice = false;
+        Collection<BluetoothDevice> connectedBtDevices =
+                mBluetoothRouteManager.getConnectedDevices();
+
+        for (BluetoothDevice connectedDevice: connectedBtDevices) {
+            if (mBluetoothRouteManager.isWatch(connectedDevice)) {
+                containsWatchDevice = true;
+            } else {
+                containsNonWatchDevice = true;
+            }
+        }
+
+        // Don't ignore switch if watch is already the active device.
+        boolean isActiveDeviceWatch = mBluetoothRouteManager.isWatch(
+                mBluetoothRouteManager.getBluetoothAudioConnectedDevice());
+        Log.i(this, "isWatchActiveOrOnlyWatchesAvailable: contains watch: %s, contains "
+                + "non-wearable device: %s, is active device a watch: %s.",
+                containsWatchDevice, containsNonWatchDevice, isActiveDeviceWatch);
+        return containsWatchDevice && !containsNonWatchDevice && !isActiveDeviceWatch;
+    }
+
+    private boolean processLegacySpeakerCommunicationDevice(boolean on) {
+        AudioDeviceInfo speakerDevice = null;
+        for (AudioDeviceInfo info : mAudioManager.getAvailableCommunicationDevices()) {
+            if (info.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) {
+                speakerDevice = info;
+                break;
+            }
+        }
+        boolean speakerOn = false;
+        if (speakerDevice != null && on) {
+            boolean result = mAudioManager.setCommunicationDevice(speakerDevice);
+            if (result) {
+                speakerOn = true;
+            }
+        } else {
+            AudioDeviceInfo curDevice = mAudioManager.getCommunicationDevice();
+            if (curDevice != null
+                    && curDevice.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) {
+                mAudioManager.clearCommunicationDevice();
+            }
+        }
+        return speakerOn;
+    }
+
     private int calculateBaselineRouteMessage(boolean isExplicitUserRequest,
             boolean includeBluetooth) {
         boolean isSkipEarpiece = false;
@@ -1969,7 +2130,7 @@
         }
         if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0
                 && !mHasUserExplicitlyLeftBluetooth
-                && includeBluetooth) {
+                && includeBluetooth && !isWatchActiveOrOnlyWatchesAvailable()) {
             return isExplicitUserRequest ? USER_SWITCH_BLUETOOTH : SWITCH_BLUETOOTH;
         } else if ((mAvailableRoutes & ROUTE_EARPIECE) != 0 && !isSkipEarpiece) {
             return isExplicitUserRequest ? USER_SWITCH_EARPIECE : SWITCH_EARPIECE;
@@ -2025,4 +2186,8 @@
 
         return base;
     }
+
+    public Handler getAdapterHandler() {
+        return getHandler();
+    }
 }
diff --git a/src/com/android/server/telecom/CallDiagnosticServiceController.java b/src/com/android/server/telecom/CallDiagnosticServiceController.java
index 6c7ee38..1077f0d 100644
--- a/src/com/android/server/telecom/CallDiagnosticServiceController.java
+++ b/src/com/android/server/telecom/CallDiagnosticServiceController.java
@@ -522,7 +522,7 @@
                 callId, messageId, message);
         if (mPlayerFactory != null) {
             // Play that tone!
-            mPlayerFactory.createPlayer(InCallTonePlayer.TONE_IN_CALL_QUALITY_NOTIFICATION)
+            mPlayerFactory.createPlayer(call, InCallTonePlayer.TONE_IN_CALL_QUALITY_NOTIFICATION)
                     .startTone();
         }
         call.displayDiagnosticMessage(messageId, message);
diff --git a/src/com/android/server/telecom/CallEndpointController.java b/src/com/android/server/telecom/CallEndpointController.java
index 60827e2..4738cd4 100644
--- a/src/com/android/server/telecom/CallEndpointController.java
+++ b/src/com/android/server/telecom/CallEndpointController.java
@@ -25,7 +25,9 @@
 import android.telecom.CallEndpoint;
 import android.telecom.CallEndpointException;
 import android.telecom.Log;
+
 import com.android.internal.annotations.VisibleForTesting;
+
 import java.util.HashMap;
 import java.util.Map;
 import java.util.HashSet;
@@ -85,7 +87,7 @@
     }
 
     public void requestCallEndpointChange(CallEndpoint endpoint, ResultReceiver callback) {
-        Log.d(this, "requestCallEndpointChange %s", endpoint);
+        Log.i(this, "requestCallEndpointChange %s", endpoint);
         int route = mTypeToRouteMap.get(endpoint.getEndpointType());
         String bluetoothAddress = getBluetoothAddress(endpoint);
 
@@ -96,6 +98,11 @@
             return;
         }
 
+        if (isCurrentEndpointRequestedEndpoint(route, bluetoothAddress)) {
+            callback.send(CallEndpoint.ENDPOINT_OPERATION_SUCCESS, new Bundle());
+            return;
+        }
+
         if (mPendingChangeRequest != null && !mPendingChangeRequest.isDone()) {
             mPendingChangeRequest.complete(RESULT_ANOTHER_REQUEST);
             mPendingChangeRequest = null;
@@ -116,6 +123,33 @@
         mCallsManager.getCallAudioManager().setAudioRoute(route, bluetoothAddress);
     }
 
+    public boolean isCurrentEndpointRequestedEndpoint(int requestedRoute, String requestedAddress) {
+        if (mCallsManager.getCallAudioManager() == null
+                || mCallsManager.getCallAudioManager().getCallAudioState() == null) {
+            return false;
+        }
+        CallAudioState currentAudioState = mCallsManager.getCallAudioManager().getCallAudioState();
+        if (requestedRoute == currentAudioState.getRoute()) {
+            if (requestedRoute != CallAudioState.ROUTE_BLUETOOTH) {
+                // The audio route (earpiece, speaker, etc.) is already active
+                // and Telecom can ignore the spam request!
+                Log.i(this, "iCERE: user requested a non-BT route that is already active");
+                return true;
+            } else if (hasSameBluetoothAddress(currentAudioState, requestedAddress)) {
+                // if the requested (BT route, device) is active, ignore the request...
+                Log.i(this, "iCERE: user requested a BT endpoint that is already active");
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public boolean hasSameBluetoothAddress(CallAudioState audioState, String requestedAddress) {
+        boolean hasActiveBtDevice = audioState.getActiveBluetoothDevice() != null;
+        return hasActiveBtDevice && requestedAddress.equals(
+                audioState.getActiveBluetoothDevice().getAddress());
+    }
+
     private Bundle getErrorResult(int result) {
         String message;
         int resultCode;
@@ -165,8 +199,7 @@
         for (Call call : calls) {
             if (call != null && call.getConnectionService() != null) {
                 call.getConnectionService().onCallEndpointChanged(call, mActiveCallEndpoint);
-            }
-            else if (call != null && call.getTransactionServiceWrapper() != null) {
+            } else if (call != null && call.getTransactionServiceWrapper() != null) {
                 call.getTransactionServiceWrapper()
                         .onCallEndpointChanged(call, mActiveCallEndpoint);
             }
@@ -181,8 +214,7 @@
             if (call != null && call.getConnectionService() != null) {
                 call.getConnectionService().onAvailableCallEndpointsChanged(call,
                         mAvailableCallEndpoints);
-            }
-            else if (call != null && call.getTransactionServiceWrapper() != null) {
+            } else if (call != null && call.getTransactionServiceWrapper() != null) {
                 call.getTransactionServiceWrapper()
                         .onAvailableCallEndpointsChanged(call, mAvailableCallEndpoints);
             }
@@ -196,8 +228,7 @@
         for (Call call : calls) {
             if (call != null && call.getConnectionService() != null) {
                 call.getConnectionService().onMuteStateChanged(call, isMuted);
-            }
-            else if (call != null && call.getTransactionServiceWrapper() != null) {
+            } else if (call != null && call.getTransactionServiceWrapper() != null) {
                 call.getTransactionServiceWrapper().onMuteStateChanged(call, isMuted);
             }
         }
@@ -207,7 +238,7 @@
         Set<CallEndpoint> newAvailableEndpoints = new HashSet<>();
         Map<ParcelUuid, String> newBluetoothDevices = new HashMap<>();
 
-        mRouteToTypeMap.forEach((route, type)->{
+        mRouteToTypeMap.forEach((route, type) -> {
             if ((state.getSupportedRouteMask() & route) != 0) {
                 if (type == CallEndpoint.TYPE_STREAMING) {
                     if (state.getRoute() == CallAudioState.ROUTE_STREAMING) {
@@ -221,8 +252,9 @@
                     for (BluetoothDevice device : state.getSupportedBluetoothDevices()) {
                         CallEndpoint endpoint = findMatchingBluetoothEndpoint(device);
                         if (endpoint == null) {
+                            String deviceName = device.getName();
                             endpoint = new CallEndpoint(
-                                    device.getName() != null ? device.getName() : "",
+                                    deviceName != null ? deviceName : "",
                                     CallEndpoint.TYPE_BLUETOOTH);
                         }
                         newAvailableEndpoints.add(endpoint);
diff --git a/src/com/android/server/telecom/CallIntentProcessor.java b/src/com/android/server/telecom/CallIntentProcessor.java
index 7f864b8..062c872 100644
--- a/src/com/android/server/telecom/CallIntentProcessor.java
+++ b/src/com/android/server/telecom/CallIntentProcessor.java
@@ -1,6 +1,7 @@
 package com.android.server.telecom;
 
 import com.android.server.telecom.components.ErrorDialogActivity;
+import com.android.server.telecom.flags.FeatureFlags;
 
 import android.content.Context;
 import android.content.Intent;
@@ -32,7 +33,7 @@
 public class CallIntentProcessor {
     public interface Adapter {
         void processOutgoingCallIntent(Context context, CallsManager callsManager,
-                Intent intent, String callingPackage);
+                Intent intent, String callingPackage, FeatureFlags featureFlags);
         void processIncomingCallIntent(CallsManager callsManager, Intent intent);
         void processUnknownCallIntent(CallsManager callsManager, Intent intent);
     }
@@ -45,9 +46,9 @@
 
         @Override
         public void processOutgoingCallIntent(Context context, CallsManager callsManager,
-                Intent intent, String callingPackage) {
+                Intent intent, String callingPackage, FeatureFlags featureFlags) {
             CallIntentProcessor.processOutgoingCallIntent(context, callsManager, intent,
-                    callingPackage, mDefaultDialerCache);
+                    callingPackage, mDefaultDialerCache, featureFlags);
         }
 
         @Override
@@ -73,12 +74,14 @@
     private final Context mContext;
     private final CallsManager mCallsManager;
     private final DefaultDialerCache mDefaultDialerCache;
+    private final FeatureFlags mFeatureFlags;
 
     public CallIntentProcessor(Context context, CallsManager callsManager,
-            DefaultDialerCache defaultDialerCache) {
+            DefaultDialerCache defaultDialerCache, FeatureFlags featureFlags) {
         this.mContext = context;
         this.mCallsManager = callsManager;
         this.mDefaultDialerCache = defaultDialerCache;
+        this.mFeatureFlags = featureFlags;
     }
 
     public void processIntent(Intent intent, String callingPackage) {
@@ -90,7 +93,7 @@
             processUnknownCallIntent(mCallsManager, intent);
         } else {
             processOutgoingCallIntent(mContext, mCallsManager, intent, callingPackage,
-                    mDefaultDialerCache);
+                    mDefaultDialerCache, mFeatureFlags);
         }
         Trace.endSection();
     }
@@ -107,7 +110,8 @@
             CallsManager callsManager,
             Intent intent,
             String callingPackage,
-            DefaultDialerCache defaultDialerCache) {
+            DefaultDialerCache defaultDialerCache,
+            FeatureFlags featureFlags) {
 
         Uri handle = intent.getData();
         String scheme = handle.getScheme();
@@ -184,7 +188,7 @@
 
         NewOutgoingCallIntentBroadcaster broadcaster = new NewOutgoingCallIntentBroadcaster(
                 context, callsManager, intent, callsManager.getPhoneNumberUtilsAdapter(),
-                isPrivilegedDialer, defaultDialerCache);
+                isPrivilegedDialer, defaultDialerCache, new MmiUtils(), featureFlags);
 
         // If the broadcaster comes back with an immediate error, disconnect and show a dialog.
         NewOutgoingCallIntentBroadcaster.CallDisposition disposition = broadcaster.evaluateCall();
diff --git a/src/com/android/server/telecom/CallLogManager.java b/src/com/android/server/telecom/CallLogManager.java
old mode 100755
new mode 100644
index 0ec2362..fc4e05d
--- a/src/com/android/server/telecom/CallLogManager.java
+++ b/src/com/android/server/telecom/CallLogManager.java
@@ -19,9 +19,13 @@
 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.NonNull;
 import android.annotation.Nullable;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
 import android.location.Country;
 import android.location.CountryDetector;
 import android.location.Location;
@@ -30,6 +34,7 @@
 import android.os.Looper;
 import android.os.UserHandle;
 import android.os.PersistableBundle;
+import android.os.UserManager;
 import android.provider.CallLog;
 import android.provider.CallLog.Calls;
 import android.telecom.Connection;
@@ -42,13 +47,16 @@
 import android.telephony.CarrierConfigManager;
 import android.telephony.PhoneNumberUtils;
 import android.telephony.SubscriptionManager;
+import android.util.Pair;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.telecom.callfiltering.CallFilteringResult;
+import com.android.server.telecom.flags.FeatureFlags;
 
 import java.util.Arrays;
 import java.util.Locale;
 import java.util.Objects;
+import java.util.UUID;
 import java.util.stream.Stream;
 
 /**
@@ -68,16 +76,19 @@
      */
     private static class AddCallArgs {
         public AddCallArgs(Context context, CallLog.AddCallParams params,
-                @Nullable LogCallCompletedListener logCallCompletedListener) {
+                @Nullable LogCallCompletedListener logCallCompletedListener,
+                @NonNull Call call) {
             this.context = context;
             this.params = params;
             this.logCallCompletedListener = logCallCompletedListener;
+            this.call = call;
 
         }
         // Since the members are accessed directly, we don't use the
         // mXxxx notation.
         public final Context context;
         public final CallLog.AddCallParams params;
+        public final Call call;
         @Nullable
         public final LogCallCompletedListener logCallCompletedListener;
     }
@@ -88,29 +99,39 @@
     // TODO: come up with a better way to indicate in a android.telecom.DisconnectCause that
     // a conference was merged successfully
     private static final String REASON_IMS_MERGED_SUCCESSFULLY = "IMS_MERGED_SUCCESSFULLY";
+    private static final UUID LOG_CALL_FAILED_ANOMALY_ID =
+            UUID.fromString("d9b38771-ff36-417b-8723-2363a870c702");
+    private static final String LOG_CALL_FAILED_ANOMALY_DESC =
+            "Based on the current user, Telecom detected failure to record a call to the call log.";
 
     private final Context mContext;
     private final CarrierConfigManager mCarrierConfigManager;
     private final PhoneAccountRegistrar mPhoneAccountRegistrar;
     private final MissedCallNotifier mMissedCallNotifier;
+    private AnomalyReporterAdapter mAnomalyReporterAdapter;
     private static final String ACTION_CALLS_TABLE_ADD_ENTRY =
-                "com.android.server.telecom.intent.action.CALLS_ADD_ENTRY";
+            "com.android.server.telecom.intent.action.CALLS_ADD_ENTRY";
     private static final String PERMISSION_PROCESS_CALLLOG_INFO =
-                "android.permission.PROCESS_CALLLOG_INFO";
+            "android.permission.PROCESS_CALLLOG_INFO";
     private static final String CALL_TYPE = "callType";
     private static final String CALL_DURATION = "duration";
 
     private Object mLock;
     private String mCurrentCountryIso;
 
+    private final FeatureFlags mFeatureFlags;
+
     public CallLogManager(Context context, PhoneAccountRegistrar phoneAccountRegistrar,
-            MissedCallNotifier missedCallNotifier) {
+            MissedCallNotifier missedCallNotifier, AnomalyReporterAdapter anomalyReporterAdapter,
+            FeatureFlags featureFlags) {
         mContext = context;
         mCarrierConfigManager = (CarrierConfigManager) mContext
                 .getSystemService(Context.CARRIER_CONFIG_SERVICE);
         mPhoneAccountRegistrar = phoneAccountRegistrar;
         mMissedCallNotifier = missedCallNotifier;
+        mAnomalyReporterAdapter = anomalyReporterAdapter;
         mLock = new Object();
+        mFeatureFlags = featureFlags;
     }
 
     @Override
@@ -149,9 +170,10 @@
      * Call was NOT in the "choose account" phase when disconnected
      * Call is NOT a conference call which had children (unless it was remotely hosted).
      * Call is NOT a child call from a conference which was remotely hosted.
+     * Call has NOT indicated it should be skipped for logging in its extras
      * Call is NOT simulating a single party conference.
      * Call was NOT explicitly canceled, except for disconnecting from a conference.
-     * Call is NOT an external call
+     * Call is NOT an external call or an external call on watch.
      * Call is NOT disconnected because of merging into a conference.
      * Call is NOT a self-managed call OR call is a self-managed call which has indicated it
      * should be logged in its PhoneAccount
@@ -180,6 +202,11 @@
             return false;
         }
 
+        if (mFeatureFlags.telecomSkipLogBasedOnExtra() && call.getExtras() != null
+                && call.getExtras().containsKey(TelecomManager.EXTRA_DO_NOT_LOG_CALL)) {
+            return false;
+        }
+
         // A child call of a conference which was remotely hosted; these didn't originate on this
         // device and should not be logged.
         if (call.getParentCall() != null && call.hasProperty(Connection.PROPERTY_REMOTELY_HOSTED)) {
@@ -200,8 +227,10 @@
                     & Connection.CAPABILITY_DISCONNECT_FROM_CONFERENCE)
                     == Connection.CAPABILITY_DISCONNECT_FROM_CONFERENCE;
         }
-        // An external call
-        if (call.isExternalCall()) {
+        // An external and non-watch call
+        if (call.isExternalCall() && (!mContext.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_WATCH)
+                || !mFeatureFlags.telecomLogExternalWearableCalls())) {
             return false;
         }
 
@@ -240,8 +269,13 @@
             logCall(call, type, new LogCallCompletedListener() {
                 @Override
                 public void onLogCompleted(@Nullable Uri uri) {
-                    mMissedCallNotifier.showMissedCallNotification(
-                            new MissedCallNotifier.CallInfo(call));
+                    if (mFeatureFlags.addCallUriForMissedCalls()){
+                        mMissedCallNotifier.showMissedCallNotification(
+                                new MissedCallNotifier.CallInfo(call), uri);
+                    } else {
+                        mMissedCallNotifier.showMissedCallNotification(
+                                new MissedCallNotifier.CallInfo(call), /* uri= */ null);
+                    }
                 }
             }, result);
         } else {
@@ -263,7 +297,7 @@
      *     {@link android.provider.CallLog.Calls#BLOCKED_TYPE}.
      */
     void logCall(Call call, int callLogType,
-        @Nullable LogCallCompletedListener logCallCompletedListener, CallFilteringResult result) {
+            @Nullable LogCallCompletedListener logCallCompletedListener, CallFilteringResult result) {
 
         CallLog.AddCallParams.AddCallParametersBuilder paramBuilder =
                 new CallLog.AddCallParams.AddCallParametersBuilder();
@@ -324,7 +358,7 @@
         }
 
         PhoneAccount phoneAccount = mPhoneAccountRegistrar.getPhoneAccountUnchecked(accountHandle);
-        UserHandle initiatingUser = call.getInitiatingUser();
+        UserHandle initiatingUser = call.getAssociatedUser();
         if (phoneAccount != null &&
                 phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_MULTI_USER)) {
             if (initiatingUser != null &&
@@ -385,8 +419,13 @@
                 okayToLogCall(accountHandle, logNumber, call.isEmergencyCall());
         if (okayToLog) {
             AddCallArgs args = new AddCallArgs(mContext, paramBuilder.build(),
-                    logCallCompletedListener);
+                    logCallCompletedListener, call);
+            Log.addEvent(call, LogUtils.Events.LOG_CALL, "number=" + Log.piiHandle(logNumber)
+                    + ",postDial=" + Log.piiHandle(call.getPostDialDigits()) + ",pres="
+                    + call.getHandlePresentation());
             logCallAsync(args);
+        } else {
+            Log.addEvent(call, LogUtils.Events.SKIP_CALL_LOG);
         }
     }
 
@@ -511,8 +550,25 @@
                 AddCallArgs c = callList[i];
                 mListeners[i] = c.logCallCompletedListener;
                 try {
-                    // May block.
+                    Pair<Integer, Integer> startStats = getCallLogStats(c.call);
+                    Log.i(TAG, "LogCall; about to log callId=%s, "
+                                    + "startCount=%d, startMaxId=%d",
+                            c.call.getId(), startStats.first, startStats.second);
+
                     result[i] = Calls.addCall(c.context, c.params);
+                    Pair<Integer, Integer> endStats = getCallLogStats(c.call);
+                    Log.i(TAG, "LogCall; logged callId=%s, uri=%s, "
+                                    + "endCount=%d, endMaxId=%s",
+                            c.call.getId(), result, endStats.first, endStats.second);
+                    if ((endStats.second - startStats.second) <= 0) {
+                        // No call was added or even worse we lost a call in the log.  Trigger an
+                        // anomaly report.  Note: it technically possible that an app modified the
+                        // call log while we were writing to it here; that is pretty unlikely, and
+                        // the goal here is to try and identify potential anomalous conditions with
+                        // logging calls.
+                        mAnomalyReporterAdapter.reportAnomaly(LOG_CALL_FAILED_ANOMALY_ID,
+                                LOG_CALL_FAILED_ANOMALY_DESC);
+                    }
                 } 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
@@ -521,8 +577,10 @@
                     //
                     // We don't want to crash the whole process just because of that, so just log
                     // it instead.
-                    Log.e(TAG, e, "Exception raised during adding CallLog entry.");
+                    Log.e(TAG, e, "LogCall: Exception raised adding callId=%s", c.call.getId());
                     result[i] = null;
+                    mAnomalyReporterAdapter.reportAnomaly(LOG_CALL_FAILED_ANOMALY_ID,
+                            LOG_CALL_FAILED_ANOMALY_DESC);
                 }
             }
             return result;
@@ -597,4 +655,56 @@
             return mCurrentCountryIso;
         }
     }
+
+
+    /**
+     * Returns a pair containing the number of rows in the call log, as well as the maximum call log
+     * ID.  There is a limit of 500 entries in the call log for a phone account, so once we hit 500
+     * we can reasonably expect that number to not change before and after logging a call.
+     * We determine the maximum ID in the call log since this is a way we can objectively check if
+     * the provider did record a call log entry or not.  Ideally there should be more call log
+     * entries after logging than before, and certainly not less.
+     * @return pair with number of rows in the call log and max id.
+     */
+    private Pair<Integer, Integer> getCallLogStats(@NonNull Call call) {
+        try {
+            // Ensure we query the call log based on the current user.
+            final Context currentUserContext = mContext.createContextAsUser(
+                    call.getAssociatedUser(), /* flags= */ 0);
+            final ContentResolver currentUserResolver = currentUserContext.getContentResolver();
+            final UserManager userManager = currentUserContext.getSystemService(UserManager.class);
+            final int currentUserId = userManager.getProcessUserId();
+
+            // Use shadow provider based on current user unlock state.
+            Uri providerUri;
+            if (userManager.isUserUnlocked(currentUserId)) {
+                providerUri = Calls.CONTENT_URI;
+            } else {
+                providerUri = Calls.SHADOW_CONTENT_URI;
+            }
+            int maxCallId = -1;
+            int numFound;
+            try (Cursor countCursor = currentUserResolver.query(providerUri,
+                    new String[]{Calls._ID},
+                    null,
+                    null,
+                    Calls._ID + " DESC")) {
+                numFound = countCursor.getCount();
+                if (numFound > 0) {
+                    countCursor.moveToFirst();
+                    maxCallId = countCursor.getInt(0);
+                }
+            }
+            return new Pair<>(numFound, maxCallId);
+        } catch (Exception e) {
+            // Oh jeepers, we crashed getting the call count.
+            Log.e(TAG, e, "getCountOfCallLogRows: failed");
+            return new Pair<>(-1, -1);
+        }
+    }
+
+    @VisibleForTesting
+    public void setAnomalyReporterAdapter(AnomalyReporterAdapter anomalyReporterAdapter){
+        mAnomalyReporterAdapter = anomalyReporterAdapter;
+    }
 }
diff --git a/src/com/android/server/telecom/CallStreamingController.java b/src/com/android/server/telecom/CallStreamingController.java
index 54ea385..1323633 100644
--- a/src/com/android/server/telecom/CallStreamingController.java
+++ b/src/com/android/server/telecom/CallStreamingController.java
@@ -36,12 +36,15 @@
 import android.telecom.CallException;
 import android.telecom.CallStreamingService;
 import android.telecom.StreamingCall;
-import android.util.Log;
+import android.telecom.Log;
 
 import com.android.internal.telecom.ICallStreamingService;
+import com.android.server.telecom.voip.ParallelTransaction;
+import com.android.server.telecom.voip.SerialTransaction;
 import com.android.server.telecom.voip.VoipCallTransaction;
 import com.android.server.telecom.voip.VoipCallTransactionResult;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CompletionStage;
@@ -54,15 +57,20 @@
     private CallStreamingServiceConnection mConnection;
     private boolean mIsStreaming;
     private final Object mLock;
+    private TelecomSystem.SyncRoot mTelecomLock;
 
-    public CallStreamingController(Context context) {
+    public CallStreamingController(Context context, TelecomSystem.SyncRoot telecomLock) {
         mLock = new Object();
         mContext = context;
+        mTelecomLock = telecomLock;
     }
 
     private void onConnectedInternal(Call call, TransactionalServiceWrapper wrapper,
             IBinder service) throws RemoteException {
         synchronized (mLock) {
+            Log.i(this, "onConnectedInternal: callid=%s", call.getId());
+            Bundle extras = new Bundle();
+            extras.putString(StreamingCall.EXTRA_CALL_ID, call.getId());
             mStreamingCall = call;
             mTransactionalServiceWrapper = wrapper;
             mService = ICallStreamingService.Stub.asInterface(service);
@@ -72,7 +80,7 @@
             mService.onCallStreamingStarted(new StreamingCall(
                     mTransactionalServiceWrapper.getComponentName(),
                     mStreamingCall.getCallerDisplayName(),
-                    mStreamingCall.getContactUri(), new Bundle()));
+                    mStreamingCall.getHandle(), extras));
             mIsStreaming = true;
         }
     }
@@ -82,6 +90,14 @@
             mStreamingCall = null;
             mTransactionalServiceWrapper = null;
             if (mConnection != null) {
+                // Notify service streaming stopped and then unbind.
+                try {
+                    mService.onCallStreamingStopped();
+                } catch (RemoteException e) {
+                    // Could not notify stop streaming; we're about to just unbind so this is
+                    // unfortunate but not the end of the world.
+                    Log.e(this, e, "resetController: failed to notify stop streaming.");
+                }
                 mContext.unbindService(mConnection);
                 mConnection = null;
             }
@@ -97,16 +113,16 @@
     }
 
     public static class QueryCallStreamingTransaction extends VoipCallTransaction {
-        private static final String TAG = QueryCallStreamingTransaction.class.getSimpleName();
         private final CallsManager mCallsManager;
 
         public QueryCallStreamingTransaction(CallsManager callsManager) {
+            super(callsManager.getLock());
             mCallsManager = callsManager;
         }
 
         @Override
         public CompletableFuture<VoipCallTransactionResult> processTransaction(Void v) {
-            Log.d(TAG, "processTransaction");
+            Log.i(this, "processTransaction");
             CompletableFuture<VoipCallTransactionResult> future = new CompletableFuture<>();
 
             if (mCallsManager.getCallStreamingController().isStreaming()) {
@@ -123,19 +139,19 @@
     }
 
     public static class AudioInterceptionTransaction extends VoipCallTransaction {
-        private static final String TAG = AudioInterceptionTransaction.class.getSimpleName();
-
         private Call mCall;
         private boolean mEnterInterception;
 
-        public AudioInterceptionTransaction(Call call, boolean enterInterception) {
+        public AudioInterceptionTransaction(Call call, boolean enterInterception,
+                TelecomSystem.SyncRoot lock) {
+            super(lock);
             mCall = call;
             mEnterInterception = enterInterception;
         }
 
         @Override
         public CompletableFuture<VoipCallTransactionResult> processTransaction(Void v) {
-            Log.d(TAG, "processTransaction");
+            Log.i(this, "processTransaction");
             CompletableFuture<VoipCallTransactionResult> future = new CompletableFuture<>();
 
             if (mEnterInterception) {
@@ -155,7 +171,6 @@
     }
 
     public class StreamingServiceTransaction extends VoipCallTransaction {
-        private static final String TAG = "StreamingServiceTransaction";
         public static final String MESSAGE = "STREAMING_FAILED_NO_SENDER";
         private final TransactionalServiceWrapper mWrapper;
         private final Context mContext;
@@ -164,22 +179,22 @@
 
         public StreamingServiceTransaction(Context context, TransactionalServiceWrapper wrapper,
                 Call call) {
+            super(mTelecomLock);
             mWrapper = wrapper;
             mCall = call;
-            mUserHandle = mCall.getInitiatingUser();
+            mUserHandle = mCall.getAssociatedUser();
             mContext = context;
         }
 
         @SuppressLint("LongLogTag")
         @Override
         public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
-            Log.d(TAG, "processTransaction");
+            Log.i(this, "processTransaction");
             CompletableFuture<VoipCallTransactionResult> future = new CompletableFuture<>();
-
             RoleManager roleManager = mContext.getSystemService(RoleManager.class);
             PackageManager packageManager = mContext.getPackageManager();
             if (roleManager == null || packageManager == null) {
-                Log.e(TAG, "Can't find system service");
+                Log.w(this, "processTransaction: Can't find system service");
                 future.complete(new VoipCallTransactionResult(
                         VoipCallTransactionResult.RESULT_FAILED, MESSAGE));
                 return future;
@@ -188,18 +203,18 @@
             List<String> holders = roleManager.getRoleHoldersAsUser(
                     RoleManager.ROLE_SYSTEM_CALL_STREAMING, mUserHandle);
             if (holders.isEmpty()) {
-                Log.e(TAG, "Can't find streaming app");
+                Log.w(this, "processTransaction: Can't find streaming app");
                 future.complete(new VoipCallTransactionResult(
                         VoipCallTransactionResult.RESULT_FAILED, MESSAGE));
                 return future;
             }
-
+            Log.i(this, "processTransaction: servicePackage=%s", holders.get(0));
             Intent serviceIntent = new Intent(CallStreamingService.SERVICE_INTERFACE);
             serviceIntent.setPackage(holders.get(0));
             List<ResolveInfo> infos = packageManager.queryIntentServicesAsUser(serviceIntent,
                     PackageManager.GET_META_DATA, mUserHandle);
             if (infos.isEmpty()) {
-                Log.e(TAG, "Can't find streaming service");
+                Log.w(this, "processTransaction: Can't find streaming service");
                 future.complete(new VoipCallTransactionResult(
                         VoipCallTransactionResult.RESULT_FAILED, MESSAGE));
                 return future;
@@ -209,7 +224,7 @@
 
             if (serviceInfo.permission == null || !serviceInfo.permission.equals(
                     Manifest.permission.BIND_CALL_STREAMING_SERVICE)) {
-                android.telecom.Log.w(TAG, "Must require BIND_CALL_STREAMING_SERVICE: " +
+                Log.w(this, "Must require BIND_CALL_STREAMING_SERVICE: " +
                         serviceInfo.packageName);
                 future.complete(new VoipCallTransactionResult(
                         VoipCallTransactionResult.RESULT_FAILED, MESSAGE));
@@ -218,16 +233,15 @@
             Intent intent = new Intent(CallStreamingService.SERVICE_INTERFACE);
             intent.setComponent(serviceInfo.getComponentName());
 
-            mConnection =  new CallStreamingServiceConnection(mCall, mWrapper, future);
+            mConnection = new CallStreamingServiceConnection(mCall, mWrapper, future);
             if (!mContext.bindServiceAsUser(intent, mConnection, Context.BIND_AUTO_CREATE
                     | Context.BIND_FOREGROUND_SERVICE
                     | Context.BIND_SCHEDULE_LIKE_TOP_APP, mUserHandle)) {
-                Log.e(TAG, "Can't bind to streaming service");
+                Log.w(this, "Can't bind to streaming service");
                 future.complete(new VoipCallTransactionResult(
                         VoipCallTransactionResult.RESULT_FAILED,
                         "STREAMING_FAILED_SENDER_BINDING_ERROR"));
             }
-
             return future;
         }
     }
@@ -237,15 +251,14 @@
     }
 
     public class UnbindStreamingServiceTransaction extends VoipCallTransaction {
-        private static final String TAG = "UnbindStreamingServiceTransaction";
-
         public UnbindStreamingServiceTransaction() {
+            super(mTelecomLock);
         }
 
         @SuppressLint("LongLogTag")
         @Override
         public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
-            Log.d(TAG, "processTransaction");
+            Log.i(this, "processTransaction (unbindStreaming");
             CompletableFuture<VoipCallTransactionResult> future = new CompletableFuture<>();
 
             resetController();
@@ -255,6 +268,51 @@
         }
     }
 
+    public class StartStreamingTransaction extends SerialTransaction {
+        private Call mCall;
+
+        public StartStreamingTransaction(List<VoipCallTransaction> subTransactions, Call call,
+                TelecomSystem.SyncRoot lock) {
+            super(subTransactions, lock);
+            mCall = call;
+        }
+
+        @Override
+        public void handleTransactionFailure() {
+            mTransactionalServiceWrapper.stopCallStreaming(mCall);
+        }
+    }
+
+    public VoipCallTransaction getStartStreamingTransaction(CallsManager callsManager,
+            TransactionalServiceWrapper wrapper, Call call, TelecomSystem.SyncRoot lock) {
+        // start streaming transaction flow:
+        //     1. make sure there's no ongoing streaming call --> bind to EXO
+        //     2. change audio mode
+        //     3. bind to EXO
+        // If bind to EXO failed, add transaction for stop the streaming
+
+        // create list for multiple transactions
+        List<VoipCallTransaction> transactions = new ArrayList<>();
+        transactions.add(new QueryCallStreamingTransaction(callsManager));
+        transactions.add(new AudioInterceptionTransaction(call, true, lock));
+        transactions.add(getCallStreamingServiceTransaction(
+                callsManager.getContext(), wrapper, call));
+        return new StartStreamingTransaction(transactions, call, lock);
+    }
+
+    public VoipCallTransaction getStopStreamingTransaction(Call call, TelecomSystem.SyncRoot lock) {
+        // TODO: implement this
+        // Stop streaming transaction flow:
+        List<VoipCallTransaction> transactions = new ArrayList<>();
+
+        // 1. unbind to call streaming service
+        transactions.add(getUnbindStreamingServiceTransaction());
+        // 2. audio route operations
+        transactions.add(new CallStreamingController.AudioInterceptionTransaction(call,
+                false, lock));
+        return new ParallelTransaction(transactions, lock);
+    }
+
     @Override
     public void onCallRemoved(Call call) {
         if (mStreamingCall == call) {
@@ -276,10 +334,13 @@
                 case CallState.ON_HOLD:
                     transaction = new CallStreamingStateChangeTransaction(
                             StreamingCall.STATE_HOLDING);
+                    break;
                 case CallState.DISCONNECTING:
                 case CallState.DISCONNECTED:
+                    Log.addEvent(call, LogUtils.Events.STOP_STREAMING);
                     transaction = new CallStreamingStateChangeTransaction(
                             StreamingCall.STATE_DISCONNECTED);
+                    break;
                 default:
                     // ignore
             }
@@ -293,8 +354,8 @@
 
                             @Override
                             public void onError(CallException exception) {
-                                Log.e(String.valueOf(this), "Exception when set call "
-                                        + "streaming state to streaming app: " + exception);
+                                Log.e(this, exception, "Exception when set call "
+                                        + "streaming state to streaming app");
                             }
                         });
             }
@@ -305,6 +366,7 @@
         @StreamingCall.StreamingCallState int mState;
 
         public CallStreamingStateChangeTransaction(@StreamingCall.StreamingCallState int state) {
+            super(mTelecomLock);
             mState = state;
         }
 
@@ -340,6 +402,7 @@
         @Override
         public void onServiceConnected(ComponentName name, IBinder service) {
             try {
+                Log.i(this, "onServiceConnected: " + name);
                 onConnectedInternal(mCall, mWrapper, service);
                 mFuture.complete(new VoipCallTransactionResult(
                         VoipCallTransactionResult.RESULT_SUCCEED, null));
@@ -367,13 +430,6 @@
         }
 
         private void clearBinding() {
-            try {
-                if (mService != null) {
-                    mService.onCallStreamingStopped();
-                }
-            } catch (RemoteException e) {
-                Log.w(String.valueOf(this), "Exception when stop call streaming:" + e);
-            }
             resetController();
             if (!mFuture.isDone()) {
                 mFuture.complete(new VoipCallTransactionResult(
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
old mode 100644
new mode 100755
index f680084..e4ea6d5
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -24,6 +24,7 @@
 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 android.provider.CallLog.Calls.USER_MISSED_NEVER_RANG;
+import static android.provider.CallLog.Calls.USER_MISSED_NOT_RUNNING;
 import static android.provider.CallLog.Calls.USER_MISSED_NO_ANSWER;
 import static android.provider.CallLog.Calls.USER_MISSED_SHORT_RING;
 import static android.telecom.TelecomManager.ACTION_POST_CALL;
@@ -40,10 +41,12 @@
 
 import android.Manifest;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.AlertDialog;
 import android.app.KeyguardManager;
 import android.app.NotificationManager;
+import android.content.ActivityNotFoundException;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -52,6 +55,7 @@
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.ResolveInfoFlags;
+import android.content.pm.ResolveInfo;
 import android.content.pm.UserInfo;
 import android.graphics.Color;
 import android.graphics.drawable.ColorDrawable;
@@ -60,8 +64,6 @@
 import android.media.MediaPlayer;
 import android.media.ToneGenerator;
 import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -101,22 +103,25 @@
 import android.telecom.TelecomManager;
 import android.telecom.VideoProfile;
 import android.telephony.CarrierConfigManager;
+import android.telephony.CellIdentity;
 import android.telephony.PhoneNumberUtils;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.Pair;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.WindowManager;
-import android.view.accessibility.AccessibilityManager;
 import android.widget.Button;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.IntentForwarderActivity;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.telecom.bluetooth.BluetoothRouteManager;
 import com.android.server.telecom.bluetooth.BluetoothStateReceiver;
 import com.android.server.telecom.callfiltering.BlockCheckerAdapter;
 import com.android.server.telecom.callfiltering.BlockCheckerFilter;
+import com.android.server.telecom.callfiltering.BlockedNumbersAdapter;
 import com.android.server.telecom.callfiltering.CallFilterResultCallback;
 import com.android.server.telecom.callfiltering.CallFilteringResult;
 import com.android.server.telecom.callfiltering.CallFilteringResult.Builder;
@@ -124,17 +129,21 @@
 import com.android.server.telecom.callfiltering.DirectToVoicemailFilter;
 import com.android.server.telecom.callfiltering.DndCallFilter;
 import com.android.server.telecom.callfiltering.IncomingCallFilterGraph;
+import com.android.server.telecom.callfiltering.IncomingCallFilterGraphProvider;
 import com.android.server.telecom.callredirection.CallRedirectionProcessor;
 import com.android.server.telecom.components.ErrorDialogActivity;
 import com.android.server.telecom.components.TelecomBroadcastReceiver;
-import com.android.server.telecom.settings.BlockedNumbersUtil;
+import com.android.server.telecom.flags.FeatureFlags;
 import com.android.server.telecom.stats.CallFailureCause;
 import com.android.server.telecom.ui.AudioProcessingNotification;
 import com.android.server.telecom.ui.CallRedirectionTimeoutDialogActivity;
+import com.android.server.telecom.ui.CallStreamingNotification;
 import com.android.server.telecom.ui.ConfirmCallDialogActivity;
 import com.android.server.telecom.ui.DisconnectedCallNotifier;
 import com.android.server.telecom.ui.IncomingCallNotifier;
 import com.android.server.telecom.ui.ToastFactory;
+import com.android.server.telecom.voip.VoipCallMonitor;
+import com.android.server.telecom.voip.TransactionManager;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -175,13 +184,13 @@
     @VisibleForTesting
     public interface CallsManagerListener {
         /**
-         * Informs listeners when a {@link Call} is newly created but not yet added to
-         * {@link #mCalls}.  This is where the call has not yet been created by the underlying
-         * {@link ConnectionService}.
+         * Informs listeners when a {@link Call} is newly created, but not yet returned by a
+         * {@link android.telecom.ConnectionService} implementation.
          * @param call the call.
          */
-        default void onCallCreated(Call call) {}
+        default void onStartCreateConnection(Call call) {}
         void onCallAdded(Call call);
+        void onCreateConnectionFailed(Call call);
         void onCallRemoved(Call call);
         void onCallStateChanged(Call call, int oldState, int newState);
         void onConnectionServiceChanged(
@@ -203,7 +212,7 @@
         void onHoldToneRequested(Call call);
         void onExternalCallChanged(Call call, boolean isExternalCall);
         void onCallStreamingStateChanged(Call call, boolean isStreaming);
-        void onDisconnectedTonePlaying(boolean isTonePlaying);
+        void onDisconnectedTonePlaying(Call call, boolean isTonePlaying);
         void onConnectionTimeChanged(Call call);
         void onConferenceStateChanged(Call call, boolean isConference);
         void onCdmaConferenceSwap(Call call);
@@ -279,6 +288,14 @@
     public static final String EXCEPTION_RETRIEVING_PHONE_ACCOUNTS_EMERGENCY_ERROR_MSG =
             "Exception thrown while retrieving list of potential phone accounts when placing an "
                     + "emergency call.";
+    public static final UUID EMERGENCY_CALL_ABORTED_NO_PHONE_ACCOUNTS_ERROR_UUID =
+            UUID.fromString("2e994acb-1997-4345-8bf3-bad04303de26");
+    public static final String EMERGENCY_CALL_ABORTED_NO_PHONE_ACCOUNTS_ERROR_MSG =
+            "An emergency call was aborted since there were no available phone accounts.";
+    public static final UUID TELEPHONY_HAS_DEFAULT_BUT_TELECOM_DOES_NOT_UUID =
+            UUID.fromString("0a86157c-50ca-11ee-be56-0242ac120002");
+    public static final String TELEPHONY_HAS_DEFAULT_BUT_TELECOM_DOES_NOT_MSG =
+            "Telephony has a default MO acct but Telecom prompted user for MO";
 
     private static final int[] OUTGOING_CALL_STATES =
             {CallState.CONNECTING, CallState.SELECT_PHONE_ACCOUNT, CallState.DIALING,
@@ -337,6 +354,16 @@
             new ConcurrentHashMap<Call, Boolean>(8, 0.9f, 1));
 
     /**
+     * List of self-managed calls that have been initialized but not yet added to
+     * CallsManager#addCall(Call). There is a window of time when a Call has been added to Telecom
+     * (e.g. TelecomManager#addNewIncomingCall) to actually added in CallsManager#addCall(Call).
+     * This list is helpful for the NotificationManagerService to know that Telecom is currently
+     * setting up a call which is an important set in making notifications non-dismissible.
+     */
+    private final Set<Call> mSelfManagedCallsBeingSetup = Collections.newSetFromMap(
+            new ConcurrentHashMap<Call, Boolean>(8, 0.9f, 1));
+
+    /**
      * A pending call is one which requires user-intervention in order to be placed.
      * Used by {@link #startCallConfirmation}.
      */
@@ -427,9 +454,19 @@
     private final Handler mHandler = new Handler(Looper.getMainLooper());
     private final EmergencyCallHelper mEmergencyCallHelper;
     private final RoleManagerAdapter mRoleManagerAdapter;
+    private final VoipCallMonitor mVoipCallMonitor;
     private final CallEndpointController mCallEndpointController;
     private final CallAnomalyWatchdog mCallAnomalyWatchdog;
+
+    private final EmergencyCallDiagnosticLogger mEmergencyCallDiagnosticLogger;
     private final CallStreamingController mCallStreamingController;
+    private final BlockedNumbersAdapter mBlockedNumbersAdapter;
+    private final TransactionManager mTransactionManager;
+    private final UserManager mUserManager;
+    private final CallStreamingNotification mCallStreamingNotification;
+    private final FeatureFlags mFeatureFlags;
+
+    private final IncomingCallFilterGraphProvider mIncomingCallFilterGraphProvider;
 
     private final ConnectionServiceFocusManager.CallsManagerRequester mRequester =
             new ConnectionServiceFocusManager.CallsManagerRequester() {
@@ -461,6 +498,7 @@
 
     private AnomalyReporterAdapter mAnomalyReporter = new AnomalyReporterAdapterImpl();
 
+    private final MmiUtils mMmiUtils = new MmiUtils();
     /**
      * Listener to PhoneAccountRegistrar events.
      */
@@ -491,34 +529,14 @@
     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            Log.startSession("CM.CCCR");
             String action = intent.getAction();
             if (CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED.equals(action)
                     || SystemContract.ACTION_BLOCK_SUPPRESSION_STATE_CHANGED.equals(action)) {
-                new UpdateEmergencyCallNotificationTask().doInBackground(
-                        Pair.create(context, Log.createSubsession()));
+                updateEmergencyCallNotificationAsync(context);
             }
         }
     };
 
-    private static class UpdateEmergencyCallNotificationTask
-            extends AsyncTask<Pair<Context, Session>, Void, Void> {
-        @SafeVarargs
-        @Override
-        protected final Void doInBackground(Pair<Context, Session>... args) {
-            if (args == null || args.length != 1 || args[0] == null) {
-                Log.e(this, new IllegalArgumentException(), "Incorrect invocation");
-                return null;
-            }
-            Log.continueSession(args[0].second, "CM.UECNT");
-            Context context = args[0].first;
-            BlockedNumbersUtil.updateEmergencyCallNotification(context,
-                    SystemContract.shouldShowEmergencyCallNotification(context));
-            Log.endSession();
-            return null;
-        }
-    }
-
     /**
      * Initializes the required Telecom components.
      */
@@ -556,8 +574,17 @@
             ToastFactory toastFactory,
             CallEndpointControllerFactory callEndpointControllerFactory,
             CallAnomalyWatchdog callAnomalyWatchdog,
+            Ringer.AccessibilityManagerAdapter accessibilityManagerAdapter,
             Executor asyncTaskExecutor,
-            Ringer.AccessibilityManagerAdapter accessibilityManagerAdapter) {
+            Executor asyncCallAudioTaskExecutor,
+            BlockedNumbersAdapter blockedNumbersAdapter,
+            TransactionManager transactionManager,
+            EmergencyCallDiagnosticLogger emergencyCallDiagnosticLogger,
+            CallAudioCommunicationDeviceTracker communicationDeviceTracker,
+            CallStreamingNotification callStreamingNotification,
+            FeatureFlags featureFlags,
+            IncomingCallFilterGraphProvider incomingCallFilterGraphProvider) {
+
         mContext = context;
         mLock = lock;
         mPhoneNumberUtilsAdapter = phoneNumberUtilsAdapter;
@@ -574,27 +601,37 @@
         mTimeoutsAdapter = timeoutsAdapter;
         mEmergencyCallHelper = emergencyCallHelper;
         mCallerInfoLookupHelper = callerInfoLookupHelper;
+        mEmergencyCallDiagnosticLogger = emergencyCallDiagnosticLogger;
+        mIncomingCallFilterGraphProvider = incomingCallFilterGraphProvider;
 
         mDtmfLocalTonePlayer =
                 new DtmfLocalTonePlayer(new DtmfLocalTonePlayer.ToneGeneratorProxy());
-        CallAudioRouteStateMachine callAudioRouteStateMachine =
-                callAudioRouteStateMachineFactory.create(
-                        context,
-                        this,
-                        bluetoothManager,
-                        wiredHeadsetManager,
-                        statusBarNotifier,
-                        audioServiceFactory,
-                        CallAudioRouteStateMachine.EARPIECE_AUTO_DETECT
-                );
-        callAudioRouteStateMachine.initialize();
+        CallAudioRouteAdapter callAudioRouteAdapter;
+        if (!featureFlags.useRefactoredAudioRouteSwitching()) {
+            callAudioRouteAdapter = callAudioRouteStateMachineFactory.create(
+                    context,
+                    this,
+                    bluetoothManager,
+                    wiredHeadsetManager,
+                    statusBarNotifier,
+                    audioServiceFactory,
+                    CallAudioRouteStateMachine.EARPIECE_AUTO_DETECT,
+                    asyncCallAudioTaskExecutor,
+                    communicationDeviceTracker,
+                    featureFlags
+            );
+        } else {
+            callAudioRouteAdapter = new CallAudioRouteController();
+        }
+        callAudioRouteAdapter.initialize();
 
         CallAudioRoutePeripheralAdapter callAudioRoutePeripheralAdapter =
                 new CallAudioRoutePeripheralAdapter(
-                        callAudioRouteStateMachine,
+                        callAudioRouteAdapter,
                         bluetoothManager,
                         wiredHeadsetManager,
-                        mDockManager);
+                        mDockManager,
+                        asyncRingtonePlayer);
         AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
         InCallTonePlayer.MediaPlayerFactory mediaPlayerFactory =
                 (resourceId, attributes) ->
@@ -618,33 +655,45 @@
                 ringtoneFactory, systemVibrator,
                 new Ringer.VibrationEffectProxy(), mInCallController,
                 mContext.getSystemService(NotificationManager.class),
-                accessibilityManagerAdapter);
+                accessibilityManagerAdapter, featureFlags);
         mCallRecordingTonePlayer = new CallRecordingTonePlayer(mContext, audioManager,
                 mTimeoutsAdapter, mLock);
-        mCallAudioManager = new CallAudioManager(callAudioRouteStateMachine,
+        mCallAudioManager = new CallAudioManager(callAudioRouteAdapter,
                 this, callAudioModeStateMachineFactory.create(systemStateHelper,
-                (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE)),
+                (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE),
+                featureFlags, communicationDeviceTracker),
                 playerFactory, mRinger, new RingbackPlayer(playerFactory),
-                bluetoothStateReceiver, mDtmfLocalTonePlayer);
+                bluetoothStateReceiver, mDtmfLocalTonePlayer, featureFlags);
 
         mConnectionSvrFocusMgr = connectionServiceFocusManagerFactory.create(mRequester);
         mHeadsetMediaButton = headsetMediaButtonFactory.create(context, this, mLock);
         mTtyManager = new TtyManager(context, mWiredHeadsetManager);
         mProximitySensorManager = proximitySensorManagerFactory.create(context, this);
         mPhoneStateBroadcaster = new PhoneStateBroadcaster(this);
-        mCallLogManager = new CallLogManager(context, phoneAccountRegistrar, mMissedCallNotifier);
+        mCallLogManager = new CallLogManager(context, phoneAccountRegistrar, mMissedCallNotifier,
+                mAnomalyReporter, featureFlags);
         mConnectionServiceRepository =
                 new ConnectionServiceRepository(mPhoneAccountRegistrar, mContext, mLock, this);
         mInCallWakeLockController = inCallWakeLockControllerFactory.create(context, this);
         mClockProxy = clockProxy;
         mToastFactory = toastFactory;
         mRoleManagerAdapter = roleManagerAdapter;
-        mCallStreamingController = new CallStreamingController(mContext);
+        mVoipCallMonitor = new VoipCallMonitor(mContext, mLock);
+        mTransactionManager = transactionManager;
+        mBlockedNumbersAdapter = blockedNumbersAdapter;
+        mCallStreamingController = new CallStreamingController(mContext, mLock);
+        mCallStreamingNotification = callStreamingNotification;
+        mFeatureFlags = featureFlags;
 
+        if (mFeatureFlags.useImprovedListenerOrder()) {
+            mListeners.add(mInCallController);
+        }
         mListeners.add(mInCallWakeLockController);
         mListeners.add(statusBarNotifier);
         mListeners.add(mCallLogManager);
-        mListeners.add(mInCallController);
+        if (!mFeatureFlags.useImprovedListenerOrder()) {
+            mListeners.add(mInCallController);
+        }
         mListeners.add(mCallEndpointController);
         mListeners.add(mCallDiagnosticServiceController);
         mListeners.add(mCallAudioManager);
@@ -655,10 +704,15 @@
         mListeners.add(mProximitySensorManager);
         mListeners.add(audioProcessingNotification);
         mListeners.add(callAnomalyWatchdog);
+        mListeners.add(mEmergencyCallDiagnosticLogger);
         mListeners.add(mCallStreamingController);
 
         // this needs to be after the mCallAudioManager
         mListeners.add(mPhoneStateBroadcaster);
+        mListeners.add(mVoipCallMonitor);
+        mListeners.add(mCallStreamingNotification);
+
+        mVoipCallMonitor.startMonitor();
 
         // There is no USER_SWITCHED broadcast for user 0, handle it here explicitly.
         final UserManager userManager = UserManager.get(mContext);
@@ -669,12 +723,14 @@
         // Register BroadcastReceiver to handle enhanced call blocking feature related event.
         IntentFilter intentFilter = new IntentFilter(
                 CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
+        intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
         intentFilter.addAction(SystemContract.ACTION_BLOCK_SUPPRESSION_STATE_CHANGED);
         context.registerReceiver(mReceiver, intentFilter, Context.RECEIVER_EXPORTED);
         mGraphHandlerThreads = new LinkedList<>();
 
         mCallAnomalyWatchdog = callAnomalyWatchdog;
         mAsyncTaskExecutor = asyncTaskExecutor;
+        mUserManager = mContext.getSystemService(UserManager.class);
     }
 
     public void setIncomingCallNotifier(IncomingCallNotifier incomingCallNotifier) {
@@ -712,11 +768,14 @@
     @Override
     @VisibleForTesting
     public void onSuccessfulOutgoingCall(Call call, int callState) {
-        Log.v(this, "onSuccessfulOutgoingCall, %s", call);
+        Log.v(this, "onSuccessfulOutgoingCall, call=[%s], state=[%d]", call, callState);
         call.setPostCallPackageName(getRoleManagerAdapter().getDefaultCallScreeningApp(
-                call.getUserHandleFromTargetPhoneAccount()));
+                call.getAssociatedUser()));
 
-        setCallState(call, callState, "successful outgoing call");
+        if (!mFeatureFlags.fixAudioFlickerForOutgoingCalls()) {
+            setCallState(call, callState, "successful outgoing call");
+        }
+
         if (!mCalls.contains(call)) {
             // Call was not added previously in startOutgoingCall due to it being a potential MMI
             // code, so add it now.
@@ -728,7 +787,18 @@
             listener.onConnectionServiceChanged(call, null, call.getConnectionService());
         }
 
-        markCallAsDialing(call);
+        if (mFeatureFlags.fixAudioFlickerForOutgoingCalls()) {
+            // Allow the ConnectionService to start the call in the active state. This case is
+            // helpful for conference calls or meetings that can skip the dialing stage.
+            if (callState == CallState.ACTIVE) {
+                setCallState(call, callState, "skipping the dialing state and setting active");
+            } else {
+                markCallAsDialing(call);
+            }
+        }
+        else{
+            markCallAsDialing(call);
+        }
     }
 
     @Override
@@ -747,11 +817,12 @@
                 ? new Bundle()
                 : phoneAccount.getExtras();
         TelephonyManager telephonyManager = getTelephonyManager();
+        boolean performDndFilter = mFeatureFlags.skipFilterPhoneAccountPerformDndFilter();
         if (incomingCall.hasProperty(Connection.PROPERTY_EMERGENCY_CALLBACK_MODE) ||
                 incomingCall.hasProperty(Connection.PROPERTY_NETWORK_IDENTIFIED_EMERGENCY_CALL) ||
                 telephonyManager.isInEmergencySmsMode() ||
                 incomingCall.isSelfManaged() ||
-                extras.getBoolean(PhoneAccount.EXTRA_SKIP_CALL_FILTERING)) {
+                (!performDndFilter && extras.getBoolean(PhoneAccount.EXTRA_SKIP_CALL_FILTERING))) {
             Log.i(this, "Skipping call filtering for %s (ecm=%b, "
                             + "networkIdentifiedEmergencyCall = %b, emergencySmsMode = %b, "
                             + "selfMgd=%b, skipExtra=%b)",
@@ -769,16 +840,31 @@
                     .build(), false);
             incomingCall.setIsUsingCallFiltering(false);
             return;
+        } else if (performDndFilter && extras.getBoolean(PhoneAccount.EXTRA_SKIP_CALL_FILTERING)) {
+            IncomingCallFilterGraph graph = setupDndFilterOnlyGraph(incomingCall);
+            graph.performFiltering();
+            return;
         }
 
         IncomingCallFilterGraph graph = setUpCallFilterGraph(incomingCall);
         graph.performFiltering();
     }
 
+    private IncomingCallFilterGraph setupDndFilterOnlyGraph(Call incomingHfpCall) {
+        incomingHfpCall.setIsUsingCallFiltering(true);
+        DndCallFilter dndCallFilter = new DndCallFilter(incomingHfpCall, mRinger);
+        IncomingCallFilterGraph graph = mIncomingCallFilterGraphProvider.createGraph(
+                incomingHfpCall,
+                this::onCallFilteringComplete, mContext, mTimeoutsAdapter, mLock);
+        graph.addFilter(dndCallFilter);
+        mGraphHandlerThreads.add(graph.getHandlerThread());
+        return graph;
+    }
+
     private IncomingCallFilterGraph setUpCallFilterGraph(Call incomingCall) {
         incomingCall.setIsUsingCallFiltering(true);
         String carrierPackageName = getCarrierPackageName();
-        UserHandle userHandle = incomingCall.getUserHandleFromTargetPhoneAccount();
+        UserHandle userHandle = incomingCall.getAssociatedUser();
         String defaultDialerPackageName = TelecomManager.from(mContext).
                 getDefaultDialerPackage(userHandle);
         String userChosenPackageName = getRoleManagerAdapter().
@@ -787,7 +873,7 @@
                 mContext.getPackageManager(), packageName);
         ParcelableCallUtils.Converter converter = new ParcelableCallUtils.Converter();
 
-        IncomingCallFilterGraph graph = new IncomingCallFilterGraph(incomingCall,
+        IncomingCallFilterGraph graph = mIncomingCallFilterGraphProvider.createGraph(incomingCall,
                 this::onCallFilteringComplete, mContext, mTimeoutsAdapter, mLock);
         DirectToVoicemailFilter voicemailFilter = new DirectToVoicemailFilter(incomingCall,
                 mCallerInfoLookupHelper);
@@ -859,6 +945,9 @@
             return;
         }
 
+        // Store the shouldSuppress value in the call object which will be passed to InCallServices
+        incomingCall.setCallIsSuppressedByDoNotDisturb(result.shouldSuppressCallDueToDndStatus);
+
         // Inform our connection service that call filtering is done (if it was performed at all).
         if (incomingCall.isUsingCallFiltering()) {
             boolean isInContacts = incomingCall.getCallerInfo() != null
@@ -897,7 +986,7 @@
         if (result.shouldAllowCall) {
             incomingCall.setPostCallPackageName(
                     getRoleManagerAdapter().getDefaultCallScreeningApp(
-                            incomingCall.getUserHandleFromTargetPhoneAccount()
+                            incomingCall.getAssociatedUser()
                     ));
 
             Log.i(this, "onCallFilteringComplete: allow call.");
@@ -951,7 +1040,7 @@
             if (result.shouldShowNotification) {
                 Log.i(this, "onCallScreeningCompleted: blocked call, showing notification.");
                 mMissedCallNotifier.showMissedCallNotification(
-                        new MissedCallNotifier.CallInfo(incomingCall));
+                        new MissedCallNotifier.CallInfo(incomingCall), /* uri= */ null);
             }
         }
     }
@@ -1264,7 +1353,7 @@
         return mCallAudioManager;
     }
 
-    InCallController getInCallController() {
+    public InCallController getInCallController() {
         return mInCallController;
     }
 
@@ -1276,6 +1365,10 @@
         return mEmergencyCallHelper;
     }
 
+    EmergencyCallDiagnosticLogger getEmergencyCallDiagnosticLogger() {
+        return mEmergencyCallDiagnosticLogger;
+    }
+
     public DefaultDialerCache getDefaultDialerCache() {
         return mDefaultDialerCache;
     }
@@ -1340,8 +1433,11 @@
     }
 
     @VisibleForTesting
-    public void setAnomalyReporterAdapter(AnomalyReporterAdapter mAnomalyReporterAdapter){
-        mAnomalyReporter = mAnomalyReporterAdapter;
+    public void setAnomalyReporterAdapter(AnomalyReporterAdapter anomalyReporterAdapter){
+        mAnomalyReporter = anomalyReporterAdapter;
+        if (mCallLogManager != null) {
+            mCallLogManager.setAnomalyReporterAdapter(anomalyReporterAdapter);
+        }
     }
 
     void processIncomingConference(PhoneAccountHandle phoneAccountHandle, Bundle extras) {
@@ -1369,6 +1465,8 @@
             // Required for backwards compatibility
             handle = extras.getParcelable(TelephonyManager.EXTRA_INCOMING_NUMBER);
         }
+        PhoneAccount phoneAccount = mPhoneAccountRegistrar.getPhoneAccountUnchecked(
+                phoneAccountHandle);
         Call call = new Call(
                 generateNextCallId(extras),
                 mContext,
@@ -1384,27 +1482,42 @@
                 false /* forceAttachToExistingConnection */,
                 isConference, /* isConference */
                 mClockProxy,
-                mToastFactory);
-        notifyCallCreated(call);
-
-        // set properties for transactional call
-        if (extras.containsKey(TelecomManager.TRANSACTION_CALL_ID_KEY)) {
-            call.setIsTransactionalCall(true);
-            call.setConnectionCapabilities(
-                    extras.getInt(CallAttributes.CALL_CAPABILITIES_KEY,
-                            CallAttributes.SUPPORTS_SET_INACTIVE), true);
-            call.setTargetPhoneAccount(phoneAccountHandle);
-        }
-
+                mToastFactory,
+                mFeatureFlags);
         // Ensure new calls related to self-managed calls/connections are set as such. This will
         // be overridden when the actual connection is returned in startCreateConnection, however
         // doing this now ensures the logs and any other logic will treat this call as self-managed
         // from the moment it is created.
-        PhoneAccount phoneAccount = mPhoneAccountRegistrar.getPhoneAccountUnchecked(
-                phoneAccountHandle);
+        boolean isSelfManaged = phoneAccount != null && phoneAccount.isSelfManaged();
+        call.setIsSelfManaged(isSelfManaged);
+        // It's important to start tracking self-managed calls as soon as the Call object is
+        // initialized so NotificationManagerService is aware Telecom is setting up a call
+        if (isSelfManaged) mSelfManagedCallsBeingSetup.add(call);
+
+        // set properties for transactional call
+        if (extras.containsKey(TelecomManager.TRANSACTION_CALL_ID_KEY)) {
+            call.setIsTransactionalCall(true);
+            call.setCallingPackageIdentity(extras);
+            call.setConnectionCapabilities(
+                    extras.getInt(CallAttributes.CALL_CAPABILITIES_KEY,
+                            CallAttributes.SUPPORTS_SET_INACTIVE), true);
+            call.setTargetPhoneAccount(phoneAccountHandle);
+            if (extras.containsKey(CallAttributes.DISPLAY_NAME_KEY)) {
+                CharSequence displayName = extras.getCharSequence(CallAttributes.DISPLAY_NAME_KEY);
+                if (!TextUtils.isEmpty(displayName)) {
+                    call.setCallerDisplayName(displayName.toString(),
+                            TelecomManager.PRESENTATION_ALLOWED);
+                }
+            }
+            // Incoming address was set via EXTRA_INCOMING_CALL_ADDRESS above.
+            UserHandle associatedUser = UserUtil.getAssociatedUserForCall(
+                    mFeatureFlags.workProfileAssociatedUser(),
+                    getPhoneAccountRegistrar(), getCurrentUserHandle(), phoneAccountHandle);
+            call.setAssociatedUser(associatedUser);
+        }
+
         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);
@@ -1517,7 +1630,33 @@
 
         CallFailureCause startFailCause =
                 checkIncomingCallPermitted(call, call.getTargetPhoneAccount());
-        if (!isHandoverAllowed ||
+        // Check if the target phone account is possibly in ECBM.
+        call.setIsInECBM(getEmergencyCallHelper()
+                .isLastOutgoingEmergencyCallPAH(call.getTargetPhoneAccount()));
+
+        // Check if call is visible to the current user.
+        boolean isCallHiddenFromProfile = !isCallVisibleForUser(call, mCurrentUserHandle);
+        // For admins, we should check if the work profile is paused in order to reject
+        // the call.
+        if (mUserManager.isUserAdmin(mCurrentUserHandle.getIdentifier())) {
+            isCallHiddenFromProfile &= mUserManager.isQuietModeEnabled(
+                call.getAssociatedUser());
+        }
+
+        // We should always allow emergency calls and also allow non-emergency calls when ECBM
+        // is active for the phone account.
+        if (isCallHiddenFromProfile && !call.isEmergencyCall() && !call.isInECBM()) {
+            Log.d(TAG, "Rejecting non-emergency call because the owner %s is not running.",
+                    phoneAccountHandle.getUserHandle());
+            call.setMissedReason(USER_MISSED_NOT_RUNNING);
+            call.setStartFailCause(CallFailureCause.INVALID_USE);
+            if (isConference) {
+                notifyCreateConferenceFailed(phoneAccountHandle, call);
+            } else {
+                notifyCreateConnectionFailed(phoneAccountHandle, call);
+            }
+        }
+        else if (!isHandoverAllowed ||
                 (call.isSelfManaged() && !startFailCause.isSuccess())) {
             if (isConference) {
                 notifyCreateConferenceFailed(phoneAccountHandle, call);
@@ -1549,8 +1688,11 @@
             // transactional calls should skip Call#startCreateConnection below
             // as that is meant for Call objects with a ConnectionServiceWrapper
             call.setState(CallState.RINGING, "explicitly set new incoming to ringing");
+            // Transactional calls don't get created via a connection service; they are added now.
+            call.setIsCreateConnectionComplete(true);
             addCall(call);
         } else {
+            notifyStartCreateConnection(call);
             call.startCreateConnection(mPhoneAccountRegistrar);
         }
         return call;
@@ -1576,13 +1718,18 @@
                 true /* forceAttachToExistingConnection */,
                 false, /* isConference */
                 mClockProxy,
-                mToastFactory);
-        notifyCallCreated(call);
-
+                mToastFactory,
+                mFeatureFlags);
         call.initAnalytics();
 
+        // For unknown calls, base the associated user off of the target phone account handle.
+        UserHandle associatedUser = UserUtil.getAssociatedUserForCall(
+                mFeatureFlags.workProfileAssociatedUser(),
+                getPhoneAccountRegistrar(), getCurrentUserHandle(), phoneAccountHandle);
+        call.setAssociatedUser(associatedUser);
         setIntentExtrasAndStartTime(call, extras);
         call.addListener(this);
+        notifyStartCreateConnection(call);
         call.startCreateConnection(mPhoneAccountRegistrar);
     }
 
@@ -1661,7 +1808,6 @@
         boolean isReusedCall;
         Uri handle = isConference ? Uri.parse("tel:conf-factory") : participants.get(0);
         Call call = reuseOutgoingCall(handle);
-
         PhoneAccount account =
                 mPhoneAccountRegistrar.getPhoneAccount(requestedAccountHandle, initiatingUser);
         Bundle phoneAccountExtra = account != null ? account.getExtras() : null;
@@ -1689,24 +1835,48 @@
                     isConference ? participants : null,
                     null /* gatewayInfo */,
                     null /* connectionManagerPhoneAccount */,
-                    null /* requestedAccountHandle */,
+                    requestedAccountHandle /* targetPhoneAccountHandle */,
                     Call.CALL_DIRECTION_OUTGOING /* callDirection */,
                     false /* forceAttachToExistingConnection */,
                     isConference, /* isConference */
                     mClockProxy,
-                    mToastFactory);
+                    mToastFactory,
+                    mFeatureFlags);
 
             if (extras.containsKey(TelecomManager.TRANSACTION_CALL_ID_KEY)) {
                 call.setIsTransactionalCall(true);
+                call.setCallingPackageIdentity(extras);
                 call.setConnectionCapabilities(
                         extras.getInt(CallAttributes.CALL_CAPABILITIES_KEY,
                                 CallAttributes.SUPPORTS_SET_INACTIVE), true);
-                call.setTargetPhoneAccount(requestedAccountHandle);
+                if (extras.containsKey(CallAttributes.DISPLAY_NAME_KEY)) {
+                    CharSequence displayName = extras.getCharSequence(
+                            CallAttributes.DISPLAY_NAME_KEY);
+                    if (!TextUtils.isEmpty(displayName)) {
+                        call.setCallerDisplayName(displayName.toString(),
+                                TelecomManager.PRESENTATION_ALLOWED);
+                    }
+                }
             }
 
             call.initAnalytics(callingPackage, creationLogs.toString());
-            // Let listeners know that we just created a new call but haven't added it yet.
-            notifyCallCreated(call);
+
+            // Log info for emergency call
+            if (call.isEmergencyCall()) {
+                String simNumeric = "";
+                String networkNumeric = "";
+                int defaultVoiceSubId = SubscriptionManager.getDefaultVoiceSubscriptionId();
+                if (defaultVoiceSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+                    TelephonyManager tm = getTelephonyManager().createForSubscriptionId(
+                            defaultVoiceSubId);
+                    CellIdentity cellIdentity = tm.getLastKnownCellIdentity();
+                    simNumeric = tm.getSimOperatorNumeric();
+                    networkNumeric = (cellIdentity != null) ? cellIdentity.getPlmn() : "";
+                }
+                TelecomStatsLog.write(TelecomStatsLog.EMERGENCY_NUMBER_DIALED,
+                            handle.getSchemeSpecificPart(),
+                            callingPackage, simNumeric, networkNumeric);
+            }
 
             // Ensure new calls related to self-managed calls/connections are set as such.  This
             // will be overridden when the actual connection is returned in startCreateConnection,
@@ -1720,11 +1890,14 @@
                         || phoneAccountExtra.getBoolean(
                                 PhoneAccount.EXTRA_ADD_SELF_MANAGED_CALLS_TO_INCALLSERVICE, true));
             }
-            call.setInitiatingUser(initiatingUser);
+            call.setAssociatedUser(initiatingUser);
             isReusedCall = false;
         } else {
             isReusedCall = true;
         }
+        // It's important to start tracking self-managed calls as soon as the Call object is
+        // initialized so NotificationManagerService is aware Telecom is setting up a call
+        if (isSelfManaged) mSelfManagedCallsBeingSetup.add(call);
 
         int videoState = VideoProfile.STATE_AUDIO_ONLY;
         if (extras != null) {
@@ -1829,7 +2002,7 @@
         CompletableFuture<Call> makeRoomForCall = setAccountHandle.thenComposeAsync(
                 potentialPhoneAccounts -> {
                     Log.i(CallsManager.this, "make room for outgoing call stage");
-                    if (isPotentialInCallMMICode(handle) && !isSelfManaged) {
+                    if (mMmiUtils.isPotentialInCallMMICode(handle) && !isSelfManaged) {
                         return CompletableFuture.completedFuture(finalCall);
                     }
                     // If a call is being reused, then it has already passed the
@@ -1926,10 +2099,12 @@
                                     int managedProfileUserId = getManagedProfileUserId(mContext,
                                             initiatingUser.getIdentifier());
                                     if (managedProfileUserId != UserHandle.USER_NULL
-                                            && mPhoneAccountRegistrar.getCallCapablePhoneAccounts(
-                                            handle.getScheme(), false,
-                                            UserHandle.of(managedProfileUserId), false).size()
-                                            != 0) {
+                                            &&
+                                            mPhoneAccountRegistrar.getCallCapablePhoneAccounts(
+                                                    handle.getScheme(), false,
+                                                    UserHandle.of(managedProfileUserId),
+                                                    false).size()
+                                                    != 0) {
                                         boolean dialogShown = showSwitchToManagedProfileDialog(
                                                 callUri, initiatingUser, managedProfileUserId);
                                         if (dialogShown) {
@@ -1941,6 +2116,12 @@
                                 Log.i(CallsManager.this, "Aborting call since there are no"
                                         + " available accounts.");
                                 showErrorMessage(R.string.cant_call_due_to_no_supported_service);
+                                mListeners.forEach(l -> l.onCreateConnectionFailed(callToPlace));
+                                if (callToPlace.isEmergencyCall()) {
+                                    mAnomalyReporter.reportAnomaly(
+                                            EMERGENCY_CALL_ABORTED_NO_PHONE_ACCOUNTS_ERROR_UUID,
+                                            EMERGENCY_CALL_ABORTED_NO_PHONE_ACCOUNTS_ERROR_MSG);
+                                }
                                 return CompletableFuture.completedFuture(null);
                             }
                             boolean needsAccountSelection = accountSuggestions.size() > 1
@@ -1949,6 +2130,21 @@
                                 return CompletableFuture.completedFuture(Pair.create(callToPlace,
                                         accountSuggestions.get(0).getPhoneAccountHandle()));
                             }
+
+                            // At this point Telecom is requesting the user to select a phone
+                            // account. However, Telephony is reporting that the user has a default
+                            // outgoing account (which is denoted by a non-negative subId number).
+                            // At some point, Telecom and Telephony are out of sync with the default
+                            // outgoing calling account.
+                            if(mFeatureFlags.telephonyHasDefaultButTelecomDoesNot()) {
+                                if (SubscriptionManager.getDefaultVoiceSubscriptionId() !=
+                                        SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+                                    mAnomalyReporter.reportAnomaly(
+                                            TELEPHONY_HAS_DEFAULT_BUT_TELECOM_DOES_NOT_UUID,
+                                            TELEPHONY_HAS_DEFAULT_BUT_TELECOM_DOES_NOT_MSG);
+                                }
+                            }
+
                             // This is the state where the user is expected to select an account
                             callToPlace.setState(CallState.SELECT_PHONE_ACCOUNT,
                                     "needs account selection");
@@ -1988,7 +2184,7 @@
                     (callPhoneAccountHandlePair, uriCallerInfoPair) -> {
                         Call theCall = callPhoneAccountHandlePair.first;
                         UserHandle userHandleForCallScreening = theCall.
-                                getUserHandleFromTargetPhoneAccount();
+                                getAssociatedUser();
                         boolean isInContacts = uriCallerInfoPair.second != null
                                 && uriCallerInfoPair.second.contactExists;
                         Log.d(CallsManager.this, "outgoingCallIdStage: isInContacts=%s",
@@ -2062,7 +2258,7 @@
                     setIntentExtrasAndStartTime(callToUse, extras);
                     setCallSourceToAnalytics(callToUse, originalIntent);
 
-                    if (isPotentialMMICode(handle) && !isSelfManaged) {
+                    if (mMmiUtils.isPotentialMMICode(handle) && !isSelfManaged) {
                         // Do not add the call if it is a potential MMI code.
                         callToUse.addListener(this);
                     } else if (!mCalls.contains(callToUse)) {
@@ -2092,23 +2288,95 @@
 
     private boolean showSwitchToManagedProfileDialog(Uri callUri, UserHandle initiatingUser,
             int managedProfileUserId) {
-        try {
-            Intent showErrorIntent = new Intent(
-                    TelecomManager.ACTION_SHOW_SWITCH_TO_WORK_PROFILE_FOR_CALL_DIALOG, callUri);
-            showErrorIntent.addCategory(Intent.CATEGORY_DEFAULT);
-            showErrorIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-            showErrorIntent.putExtra(TelecomManager.EXTRA_MANAGED_PROFILE_USER_ID,
-                    managedProfileUserId);
-
-            if (mContext.getPackageManager().queryIntentActivitiesAsUser(showErrorIntent,
-                    ResolveInfoFlags.of(0), initiatingUser).size() != 0) {
-                mContext.startActivityAsUser(showErrorIntent, initiatingUser);
-                return true;
-            }
-        } catch (Exception e) {
-            Log.w(this, "Failed to launch switch to managed profile dialog");
+        // Note that the ACTION_CALL intent will resolve to Telecomm's UserCallActivity
+        // even if there is no dialer. Hence we explicitly check for whether a default dialer
+        // exists instead of relying on ActivityNotFound when sending the call intent.
+        if (TextUtils.isEmpty(
+                mDefaultDialerCache.getDefaultDialerApplication(managedProfileUserId))) {
+            Log.i(
+                    this,
+                    "Work profile telephony: default dialer app missing, showing error dialog.");
+            return maybeShowErrorDialog(callUri, managedProfileUserId, initiatingUser);
         }
-        return false;
+
+        UserManager userManager = mContext.getSystemService(UserManager.class);
+        if (userManager.isQuietModeEnabled(UserHandle.of(managedProfileUserId))) {
+            Log.i(
+                    this,
+                    "Work profile telephony: quiet mode enabled, showing error dialog");
+            return maybeShowErrorDialog(callUri, managedProfileUserId, initiatingUser);
+        }
+        Log.i(
+                this,
+                "Work profile telephony: show forwarding call to managed profile dialog");
+        return maybeRedirectToIntentForwarder(callUri, initiatingUser);
+    }
+
+    private boolean maybeRedirectToIntentForwarder(
+            Uri callUri,
+            UserHandle initiatingUser) {
+        // Note: This intent is selected to match the CALL_MANAGED_PROFILE filter in
+        // DefaultCrossProfileIntentFiltersUtils. This ensures that it is redirected to
+        // IntentForwarderActivity.
+        Intent forwardCallIntent = new Intent(Intent.ACTION_CALL, callUri);
+        forwardCallIntent.addCategory(Intent.CATEGORY_DEFAULT);
+        ResolveInfo resolveInfos =
+                mContext.getPackageManager()
+                        .resolveActivityAsUser(
+                                forwardCallIntent,
+                                ResolveInfoFlags.of(0),
+                                initiatingUser.getIdentifier());
+        // Check that the intent will actually open the resolver rather than looping to the personal
+        // profile. This should not happen due to the cross profile intent filters.
+        if (resolveInfos == null
+                || !resolveInfos
+                    .getComponentInfo()
+                    .getComponentName()
+                    .getShortClassName()
+                    .equals(IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE)) {
+            Log.w(
+                    this,
+                    "Work profile telephony: Intent would not resolve to forwarder activity.");
+            return false;
+        }
+
+        try {
+            mContext.startActivityAsUser(forwardCallIntent, initiatingUser);
+            return true;
+        } catch (ActivityNotFoundException e) {
+            Log.e(this, e, "Unable to start call intent for work telephony");
+            return false;
+        }
+    }
+
+    private boolean maybeShowErrorDialog(
+            Uri callUri,
+            int managedProfileUserId,
+            UserHandle initiatingUser) {
+        Intent showErrorIntent =
+                    new Intent(
+                            TelecomManager.ACTION_SHOW_SWITCH_TO_WORK_PROFILE_FOR_CALL_DIALOG,
+                            callUri);
+        showErrorIntent.addCategory(Intent.CATEGORY_DEFAULT);
+        showErrorIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        showErrorIntent.putExtra(
+                TelecomManager.EXTRA_MANAGED_PROFILE_USER_ID, managedProfileUserId);
+        if (mContext.getPackageManager()
+                .queryIntentActivitiesAsUser(
+                        showErrorIntent,
+                        ResolveInfoFlags.of(0),
+                        initiatingUser)
+                .isEmpty()) {
+            return false;
+        }
+        try {
+            mContext.startActivityAsUser(showErrorIntent, initiatingUser);
+            return true;
+        } catch (ActivityNotFoundException e) {
+            Log.e(
+                    this, e,"Work profile telephony: Unable to show error dialog");
+            return false;
+        }
     }
 
     public void startConference(List<Uri> participants, Bundle clientExtras, String callingPackage,
@@ -2120,6 +2388,15 @@
 
          PhoneAccountHandle phoneAccountHandle = clientExtras.getParcelable(
                  TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
+         PhoneAccount account =
+                mPhoneAccountRegistrar.getPhoneAccount(phoneAccountHandle, initiatingUser);
+         boolean isSelfManaged = account != null && account.isSelfManaged();
+         // Enforce outgoing call restriction for conference calls. This is handled via
+         // UserCallIntentProcessor for normal MO calls.
+         if (UserUtil.hasOutgoingCallsUserRestriction(mContext, initiatingUser,
+                 null, isSelfManaged, CallsManager.class.getCanonicalName())) {
+             return;
+         }
          CompletableFuture<Call> callFuture = startOutgoingCall(participants, phoneAccountHandle,
                  clientExtras, initiatingUser, null/* originalIntent */, callingPackage,
                  true/* isconference*/);
@@ -2151,7 +2428,7 @@
         // Find the user chosen call screening app.
         String callScreeningApp =
                 mRoleManagerAdapter.getDefaultCallScreeningApp(
-                        theCall.getUserHandleFromTargetPhoneAccount());
+                        theCall.getAssociatedUser());
 
         CompletableFuture future =
                 new CallScreeningServiceHelper(mContext,
@@ -2315,7 +2592,7 @@
                 && !phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_MULTI_USER)) {
             // Note that mCurrentUserHandle may not actually be the current user, i.e.
             // in the case of work profiles
-            UserHandle currentUserHandle = call.getUserHandleFromTargetPhoneAccount();
+            UserHandle currentUserHandle = call.getAssociatedUser();
             // Check if the phoneAccountHandle belongs to the current user
             if (phoneAccountHandle != null &&
                     !phoneAccountHandle.getUserHandle().equals(currentUserHandle)) {
@@ -2598,7 +2875,7 @@
         // Auto-enable speakerphone if the originating intent specified to do so, if the call
         // is a video call, of if using speaker when docked
         PhoneAccount account = mPhoneAccountRegistrar.getPhoneAccount(
-                call.getTargetPhoneAccount(), call.getInitiatingUser());
+                call.getTargetPhoneAccount(), call.getAssociatedUser());
         boolean allowVideo = false;
         if (account != null) {
             allowVideo = account.hasCapabilities(PhoneAccount.CAPABILITY_VIDEO_CALLING);
@@ -2642,6 +2919,7 @@
                     disconnectSelfManagedCalls("place emerg call" /* reason */);
                 }
                 try {
+                    notifyStartCreateConnection(call);
                     call.startCreateConnection(mPhoneAccountRegistrar);
                 } catch (Exception exception) {
                     // If an exceptions is thrown while creating the connection, prompt the user to
@@ -2659,7 +2937,7 @@
             }
         } else if (mPhoneAccountRegistrar.getCallCapablePhoneAccounts(
                 requireCallCapableAccountByHandle ? callHandleScheme : null, false,
-                call.getInitiatingUser(), false).isEmpty()) {
+                call.getAssociatedUser(), false).isEmpty()) {
             // If there are no call capable accounts, disconnect the call.
             markCallAsDisconnected(call, new DisconnectCause(DisconnectCause.CANCELED,
                     "No registered PhoneAccounts"));
@@ -2695,9 +2973,15 @@
             // from the client via a transaction before answering.
             call.answer(videoState);
         } else {
+            if (!mFeatureFlags.genAnomReportOnFocusTimeout()) {
+                Call activeCall = (Call) mConnectionSvrFocusMgr.getCurrentFocusCall();
+                Log.d(this, "answerCall: Incoming call = %s Ongoing call %s", call, activeCall);
+            }
             // Hold or disconnect the active call and request call focus for the incoming call.
-            Call activeCall = (Call) mConnectionSvrFocusMgr.getCurrentFocusCall();
-            Log.d(this, "answerCall: Incoming call = %s Ongoing call %s", call, activeCall);
+            Bundle bundle = new Bundle();
+            bundle.putLong(TelecomManager.EXTRA_CALL_ANSWERED_TIME_MILLIS,
+                     mClockProxy.currentTimeMillis());
+            call.putConnectionServiceExtras(bundle);
             holdActiveCallForNewCall(call);
             mConnectionSvrFocusMgr.requestFocus(
                     call,
@@ -3145,7 +3429,10 @@
                         isEmergency);
         // Only one SIM PhoneAccount can be active at one time for DSDS. Only that SIM PhoneAccount
         // should be available if a call is already active on the SIM account.
-        if (!isDsdaCallingPossible()) {
+        // Similarly, the emergency call should be attempted over the same PhoneAccount as the
+        // ongoing call. However, if the ongoing call is over cross-SIM registration, then the
+        // emergency call will be attempted over a different Phone object at a later stage.
+        if (isEmergency || !isDsdaCallingPossible()) {
             List<PhoneAccountHandle> simAccounts =
                     mPhoneAccountRegistrar.getSimPhoneAccountsOfCurrentUser();
             PhoneAccountHandle ongoingCallAccount = null;
@@ -3277,7 +3564,7 @@
         } else {
             if (setDefault) {
                 mPhoneAccountRegistrar
-                        .setUserSelectedOutgoingPhoneAccount(account, call.getInitiatingUser());
+                        .setUserSelectedOutgoingPhoneAccount(account, call.getAssociatedUser());
             }
 
             if (mPendingAccountSelection != null) {
@@ -3325,14 +3612,15 @@
      * Called when disconnect tone is started or stopped, including any InCallTone
      * after disconnected call.
      *
+     * @param call
      * @param isTonePlaying true if the disconnected tone is started, otherwise the disconnected
-     * tone is stopped.
+     *                      tone is stopped.
      */
     @VisibleForTesting
-    public void onDisconnectedTonePlaying(boolean isTonePlaying) {
+    public void onDisconnectedTonePlaying(Call call, boolean isTonePlaying) {
         Log.v(this, "onDisconnectedTonePlaying, %s", isTonePlaying ? "started" : "stopped");
         for (CallsManagerListener listener : mListeners) {
-            listener.onDisconnectedTonePlaying(isTonePlaying);
+            listener.onDisconnectedTonePlaying(call, isTonePlaying);
         }
     }
 
@@ -3358,10 +3646,11 @@
      */
     boolean holdActiveCallForNewCall(Call call) {
         Call activeCall = (Call) mConnectionSvrFocusMgr.getCurrentFocusCall();
-        Log.i(this, "holdActiveCallForNewCall, newCall: %s, activeCall: %s", call, activeCall);
+        Log.i(this, "holdActiveCallForNewCall, newCall: %s, activeCall: %s", call.getId(),
+                (activeCall == null ? "<none>" : activeCall.getId()));
         if (activeCall != null && activeCall != call) {
             if (canHold(activeCall)) {
-                activeCall.hold();
+                activeCall.hold("swap to " + call.getId());
                 return true;
             } else if (supportsHold(activeCall)
                     && areFromSameSource(activeCall, call)) {
@@ -3497,6 +3786,8 @@
      * @param disconnectCause The disconnect cause, see {@link android.telecom.DisconnectCause}.
      */
     public void markCallAsDisconnected(Call call, DisconnectCause disconnectCause) {
+        Log.i(this, "markCallAsDisconnected: call=%s; disconnectCause=%s",
+                call.toString(), disconnectCause.toString());
         int oldState = call.getState();
         if (call.getState() == CallState.SIMULATED_RINGING
                 && disconnectCause.getCode() == DisconnectCause.REMOTE) {
@@ -3521,6 +3812,12 @@
             }
         }
 
+        // Notify listeners that the call was disconnected before being added to CallsManager.
+        // Listeners will not receive onAdded or onRemoved callbacks.
+        if (!mCalls.contains(call)) {
+            mListeners.forEach(l -> l.onCreateConnectionFailed(call));
+        }
+
         // 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.
@@ -3590,40 +3887,57 @@
      * @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, "performRemoval: isDisconnectingChildCall = "
-                        + isDisconnectingChildCall + "call -> %s", call);
-                mLocallyDisconnectingCalls.remove(call);
-                // Auto-unhold the foreground call due to a locally disconnected call, except if the
-                // call which was disconnected is a member of a conference (don't want to auto
-                // un-hold the conference if we remove a member of the conference).
-                if (!isDisconnectingChildCall && foregroundCall != null
-                        && foregroundCall.getState() == CallState.ON_HOLD) {
-                    foregroundCall.unhold();
-                }
-            } else if (foregroundCall != null &&
-                    !foregroundCall.can(Connection.CAPABILITY_SUPPORT_HOLD) &&
-                    foregroundCall.getState() == CallState.ON_HOLD) {
+        if (mInCallController.getBindingFuture() != null) {
+            mInCallController.getBindingFuture().thenRunAsync(() -> {
+                        doRemoval(call);
+                    }, new LoggedHandlerExecutor(mHandler, "CM.pR", mLock))
+                    .exceptionally((throwable) -> {
+                        Log.e(TAG, throwable, "Error while executing call removal");
+                        mAnomalyReporter.reportAnomaly(CALL_REMOVAL_EXECUTION_ERROR_UUID,
+                                CALL_REMOVAL_EXECUTION_ERROR_MSG);
+                        return null;
+                    });
+        } else {
+            doRemoval(call);
+        }
+    }
 
-                // 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, "performRemoval: Auto-unholding held foreground call (call doesn't "
-                        + "support hold)");
+    /**
+     * Code to perform removal of a call.  Called above from {@link #performRemoval(Call)} either
+     * async (in live code) or sync (in testing).
+     * @param call the call to remove.
+     */
+    private void doRemoval(Call call) {
+        call.maybeCleanupHandover();
+        removeCall(call);
+        Call foregroundCall = mCallAudioManager.getPossiblyHeldForegroundCall();
+        if (mLocallyDisconnectingCalls.contains(call)) {
+            boolean isDisconnectingChildCall = call.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
+            // call which was disconnected is a member of a conference (don't want to auto
+            // un-hold the conference if we remove a member of the conference).
+            // Also, ensure that the call we're removing is from the same ConnectionService as
+            // the one we're removing.  We don't want to auto-unhold between ConnectionService
+            // implementations, especially if one is managed and the other is a VoIP CS.
+            if (!isDisconnectingChildCall && foregroundCall != null
+                    && foregroundCall.getState() == CallState.ON_HOLD
+                    && areFromSameSource(foregroundCall, call)) {
                 foregroundCall.unhold();
             }
-        }, new LoggedHandlerExecutor(mHandler, "CM.pR", mLock))
-                .exceptionally((throwable) -> {
-                    Log.e(TAG, throwable, "Error while executing call removal");
-                    mAnomalyReporter.reportAnomaly(CALL_REMOVAL_EXECUTION_ERROR_UUID,
-                            CALL_REMOVAL_EXECUTION_ERROR_MSG);
-                    return null;
-                });
+        } else if (foregroundCall != null &&
+                !foregroundCall.can(Connection.CAPABILITY_SUPPORT_HOLD) &&
+                foregroundCall.getState() == CallState.ON_HOLD) {
+
+            // 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, "performRemoval: Auto-unholding held foreground call (call doesn't "
+                    + "support hold)");
+            foregroundCall.unhold();
+        }
     }
 
     /**
@@ -3935,8 +4249,13 @@
                 connectTime,
                 connectElapsedTime,
                 mClockProxy,
-                mToastFactory);
-        notifyCallCreated(call);
+                mToastFactory,
+                mFeatureFlags);
+
+        // Unlike connections, conferences are not created first and then notified as create
+        // connection complete from the CS.  They originate from the CS and are reported directly to
+        // telecom where they're added (see below).
+        call.setIsCreateConnectionComplete(true);
 
         setCallState(call, Call.getStateFromConnectionState(parcelableConference.getState()),
                 "new conference call");
@@ -3948,6 +4267,11 @@
         call.setVideoProvider(parcelableConference.getVideoProvider());
         call.setStatusHints(parcelableConference.getStatusHints());
         call.putConnectionServiceExtras(parcelableConference.getExtras());
+        // For conference calls, set the associated user from the target phone account user handle.
+        UserHandle associatedUser = UserUtil.getAssociatedUserForCall(
+                mFeatureFlags.workProfileAssociatedUser(), getPhoneAccountRegistrar(),
+                getCurrentUserHandle(), phoneAccount);
+        call.setAssociatedUser(associatedUser);
         // In case this Conference was added via a ConnectionManager, keep track of the original
         // Connection ID as created by the originating ConnectionService.
         Bundle extras = parcelableConference.getExtras();
@@ -4040,6 +4364,7 @@
         Log.i(this, "addCall(%s)", call);
         call.addListener(this);
         mCalls.add(call);
+        mSelfManagedCallsBeingSetup.remove(call);
 
         // Specifies the time telecom finished routing the call. This is used by the dialer for
         // analytics.
@@ -4083,6 +4408,7 @@
             mCalls.remove(call);
             shouldNotify = true;
         }
+        mSelfManagedCallsBeingSetup.remove(call);
 
         call.destroy();
         updateExternalCallCanPullSupport();
@@ -4333,37 +4659,6 @@
         }
     }
 
-    private boolean isPotentialMMICode(Uri handle) {
-        return (handle != null && handle.getSchemeSpecificPart() != null
-                && handle.getSchemeSpecificPart().contains("#"));
-    }
-
-    /**
-     * Determines if a dialed number is potentially an In-Call MMI code.  In-Call MMI codes are
-     * MMI codes which can be dialed when one or more calls are in progress.
-     * <P>
-     * Checks for numbers formatted similar to the MMI codes defined in:
-     * {@link com.android.internal.telephony.Phone#handleInCallMmiCommands(String)}
-     *
-     * @param handle The URI to call.
-     * @return {@code True} if the URI represents a number which could be an in-call MMI code.
-     */
-    private boolean isPotentialInCallMMICode(Uri handle) {
-        if (handle != null && handle.getSchemeSpecificPart() != null &&
-                handle.getScheme() != null &&
-                handle.getScheme().equals(PhoneAccount.SCHEME_TEL)) {
-
-            String dialedNumber = handle.getSchemeSpecificPart();
-            return (dialedNumber.equals("0") ||
-                    (dialedNumber.startsWith("1") && dialedNumber.length() <= 2) ||
-                    (dialedNumber.startsWith("2") && dialedNumber.length() <= 2) ||
-                    dialedNumber.equals("3") ||
-                    dialedNumber.equals("4") ||
-                    dialedNumber.equals("5"));
-        }
-        return false;
-    }
-
     /**
      * Determines if there are any ongoing self managed calls for the given package/user.
      * @param packageName The package name to check.
@@ -4371,8 +4666,10 @@
      * @return {@code true} if the app has ongoing calls, or {@code false} otherwise.
      */
     public boolean isInSelfManagedCall(String packageName, UserHandle userHandle) {
-        return mCalls.stream().anyMatch(
-                c -> c.isSelfManaged()
+        return mSelfManagedCallsBeingSetup.stream().anyMatch(c -> c.isSelfManaged()
+                && c.getTargetPhoneAccount().getComponentName().getPackageName().equals(packageName)
+                && c.getTargetPhoneAccount().getUserHandle().equals(userHandle)) ||
+                mCalls.stream().anyMatch(c -> c.isSelfManaged()
                 && c.getTargetPhoneAccount().getComponentName().getPackageName().equals(packageName)
                 && c.getTargetPhoneAccount().getUserHandle().equals(userHandle));
     }
@@ -4540,15 +4837,21 @@
 
     /**
      * Determines the number of unholdable calls present in a connection service other than the one
-     * the passed phone account belonds to.
+     * the passed phone account belongs to. If a ConnectionService has not been associated with an
+     * outgoing call yet (for example, it is in the SELECT_PHONE_ACCOUNT state), then we do not
+     * count that call because it is not tracked as an active call yet.
      * @param phoneAccountHandle The handle of the PhoneAccount.
      * @return Number of unholdable calls owned by other connection service.
      */
     public int getNumUnholdableCallsForOtherConnectionService(
             PhoneAccountHandle phoneAccountHandle) {
         return (int) mCalls.stream().filter(call ->
-                !phoneAccountHandle.getComponentName().equals(
-                        call.getTargetPhoneAccount().getComponentName())
+                // If this convention needs to be changed, answerCall will need to be modified to
+                // change what an "active call" is so that the call in SELECT_PHONE_ACCOUNT state
+                // will be properly cancelled.
+                call.getTargetPhoneAccount() != null
+                        && !phoneAccountHandle.getComponentName().equals(
+                                call.getTargetPhoneAccount().getComponentName())
                         && call.getParentCall() == null
                         && !call.isExternalCall()
                         && !canHold(call)).count();
@@ -4564,11 +4867,14 @@
     }
 
     /**
-     * Determines if there are any self-managed calls.
+     * Note: isInSelfManagedCall(packageName, UserHandle) should always be used in favor or this
+     * method. This method determines if there are any self-managed calls globally.
      * @return {@code true} if there are self-managed calls, {@code false} otherwise.
      */
+    @VisibleForTesting
     public boolean hasSelfManagedCalls() {
-        return mCalls.stream().filter(call -> call.isSelfManaged()).count() > 0;
+        return mSelfManagedCallsBeingSetup.size() > 0 ||
+                mCalls.stream().filter(call -> call.isSelfManaged()).count() > 0;
     }
 
     /**
@@ -4794,9 +5100,18 @@
             return true;
         }
 
-        // If the live call is stuck in a connecting state, then we should disconnect it in favor
-        // of the new outgoing call and prompt the user to generate a bugreport.
-        if (liveCall.getState() == CallState.CONNECTING) {
+        // If the live call is stuck in a connecting state for longer than the transitory timeout,
+        // then we should disconnect it in favor of the new outgoing call and prompt the user to
+        // generate a bugreport.
+        // TODO: In the future we should let the CallAnomalyWatchDog do this disconnection of the
+        // live call stuck in the connecting state.  Unfortunately that code will get tripped up by
+        // calls that have a longer than expected new outgoing call broadcast response time.  This
+        // mitigation is intended to catch calls stuck in a CONNECTING state for a long time that
+        // block outgoing calls.  However, if the user dials two calls in quick succession it will
+        // result in both calls getting disconnected, which is not optimal.
+        if (liveCall.getState() == CallState.CONNECTING
+                && ((mClockProxy.elapsedRealtime() - liveCall.getCreationElapsedRealtimeMillis())
+                > mTimeoutsAdapter.getNonVoipCallTransitoryStateTimeoutMillis())) {
             mAnomalyReporter.reportAnomaly(LIVE_CALL_STUCK_CONNECTING_ERROR_UUID,
                     LIVE_CALL_STUCK_CONNECTING_ERROR_MSG);
             liveCall.disconnect("Force disconnect CONNECTING call.");
@@ -4834,12 +5149,25 @@
                     liveCallPhoneAccount);
         }
 
-        // First thing, if we are trying to make a call with the same phone account as the live
-        // call, then allow it so that the connection service can make its own decision about
-        // how to handle the new call relative to the current one.
+        // First thing, for managed calls, if we are trying to make a call with the same phone
+        // account as the live call, then allow it so that the connection service can make its own
+        // decision about how to handle the new call relative to the current one.
+        // Note: This behavior is primarily in place because Telephony historically manages the
+        // state of the calls it tracks by itself, holding and unholding as needed.  Self-managed
+        // calls, even though from the same package are normally held/unheld automatically by
+        // Telecom.  Calls within a single ConnectionService get held/unheld automatically during
+        // "swap" operations by CallsManager#holdActiveCallForNewCall.  There is, however, a quirk
+        // in that if an app declares TWO different ConnectionServices, holdActiveCallForNewCall
+        // would not work correctly because focus switches between ConnectionServices, yet we
+        // tended to assume that if the calls are from the same package that the hold/unhold should
+        // be done by the app.  That was a bad assumption as it meant that we could have two active
+        // calls.
+        // TODO(b/280826075): We need to come back and revisit all this logic in a holistic manner.
         if (PhoneAccountHandle.areFromSamePackage(liveCallPhoneAccount,
-                call.getTargetPhoneAccount())) {
-            Log.i(this, "makeRoomForOutgoingCall: phoneAccount matches.");
+                call.getTargetPhoneAccount())
+                && !call.isSelfManaged()
+                && !liveCall.isSelfManaged()) {
+            Log.i(this, "makeRoomForOutgoingCall: managed phoneAccount matches");
             call.getAnalytics().setCallIsAdditional(true);
             liveCall.getAnalytics().setCallIsInterrupted(true);
             return true;
@@ -4932,6 +5260,25 @@
     }
 
     /**
+     * Asynchronously updates the emergency call notification.
+     * @param context the context for the update.
+     */
+    private void updateEmergencyCallNotificationAsync(Context context) {
+        mAsyncTaskExecutor.execute(() -> {
+            Log.startSession("CM.UEMCNA");
+            try {
+                boolean shouldShow = mBlockedNumbersAdapter.shouldShowEmergencyCallNotification(
+                        context);
+                Log.i(CallsManager.this, "updateEmergencyCallNotificationAsync; show=%b",
+                        shouldShow);
+                mBlockedNumbersAdapter.updateEmergencyCallNotification(context, shouldShow);
+            } finally {
+                Log.endSession();
+            }
+        });
+    }
+
+    /**
      * Creates a new call for an existing connection.
      *
      * @param callId The id of the new call.
@@ -4962,7 +5309,8 @@
                 connection.getConnectTimeMillis() /* connectTimeMillis */,
                 connection.getConnectElapsedTimeMillis(), /* connectElapsedTimeMillis */
                 mClockProxy,
-                mToastFactory);
+                mToastFactory,
+                mFeatureFlags);
 
         call.initAnalytics();
         call.getAnalytics().setCreatedFromExistingConnection(true);
@@ -4975,6 +5323,12 @@
         call.setHandle(connection.getHandle(), connection.getHandlePresentation());
         call.setCallerDisplayName(connection.getCallerDisplayName(),
                 connection.getCallerDisplayNamePresentation());
+        // For existing connections, use the phone account user handle to determine the user
+        // association with the call.
+        UserHandle associatedUser = UserUtil.getAssociatedUserForCall(
+                mFeatureFlags.workProfileAssociatedUser(), getPhoneAccountRegistrar(),
+                getCurrentUserHandle(), connection.getPhoneAccount());
+        call.setAssociatedUser(associatedUser);
         call.addListener(this);
         call.putConnectionServiceExtras(connection.getExtras());
 
@@ -4995,6 +5349,9 @@
                 call.setParentCall(parentCall);
             }
         }
+        // Existing connections originate from a connection service, so they are completed creation
+        // by the ConnectionService implicitly.
+        call.setIsCreateConnectionComplete(true);
         addCall(call);
         if (parentCall != null) {
             // Now, set the call as a child of the parent since it has been added to Telecom.  This
@@ -5196,7 +5553,7 @@
         mCallAudioManager.getCallAudioModeStateMachine().getHandler().post(() -> {
             mainHandlerLatch.countDown();
         });
-        mCallAudioManager.getCallAudioRouteStateMachine().getHandler().post(() -> {
+        mCallAudioManager.getCallAudioRouteAdapter().getAdapterHandler().post(() -> {
             mainHandlerLatch.countDown();
         });
 
@@ -5222,9 +5579,10 @@
             // We are going to place the new outgoing call, so disconnect any ongoing self-managed
             // calls which are ongoing at this time.
             disconnectSelfManagedCalls("outgoing call " + callId);
-
-            mPendingCallConfirm.complete(mPendingCall);
-            mPendingCallConfirm = null;
+            if (mPendingCallConfirm != null) {
+                mPendingCallConfirm.complete(mPendingCall);
+                mPendingCallConfirm = null;
+            }
             mPendingCall = null;
         }
     }
@@ -5243,8 +5601,10 @@
             markCallAsDisconnected(mPendingCall, new DisconnectCause(DisconnectCause.CANCELED));
             markCallAsRemoved(mPendingCall);
             mPendingCall = null;
-            mPendingCallConfirm.complete(null);
-            mPendingCallConfirm = null;
+            if (mPendingCallConfirm != null) {
+                mPendingCallConfirm.complete(null);
+                mPendingCallConfirm = null;
+            }
         }
     }
 
@@ -5306,7 +5666,7 @@
      *
      * @param pw The {@code IndentingPrintWriter} to write the state to.
      */
-    public void dump(IndentingPrintWriter pw) {
+    public void dump(IndentingPrintWriter pw, String[] args) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
         if (mCalls != null) {
             pw.println("mCalls: ");
@@ -5368,6 +5728,14 @@
             mCallAnomalyWatchdog.dump(pw);
             pw.decreaseIndent();
         }
+
+        if (mEmergencyCallDiagnosticLogger != null) {
+            pw.println("mEmergencyCallDiagnosticLogger:");
+            pw.increaseIndent();
+            mEmergencyCallDiagnosticLogger.dump(pw, args);
+            pw.decreaseIndent();
+        }
+
         if (mDefaultDialerCache != null) {
             pw.println("mDefaultDialerCache:");
             pw.increaseIndent();
@@ -5389,6 +5757,13 @@
             impl.dump(pw);
             pw.decreaseIndent();
         }
+
+        if (mConnectionSvrFocusMgr != null) {
+            pw.println("mConnectionSvrFocusMgr:");
+            pw.increaseIndent();
+            mConnectionSvrFocusMgr.dump(pw);
+            pw.decreaseIndent();
+        }
     }
 
     /**
@@ -5397,8 +5772,10 @@
     * @param call The call.
     */
     private void maybeShowErrorDialogOnDisconnect(Call call) {
-        if (call.getState() == CallState.DISCONNECTED && (isPotentialMMICode(call.getHandle())
-                || isPotentialInCallMMICode(call.getHandle())) && !mCalls.contains(call)) {
+        if (call.getState() == CallState.DISCONNECTED && (mMmiUtils.isPotentialMMICode(
+                call.getHandle())
+                || mMmiUtils.isPotentialInCallMMICode(call.getHandle())) && !mCalls.contains(
+                call)) {
             DisconnectCause disconnectCause = call.getDisconnectCause();
             if (!TextUtils.isEmpty(disconnectCause.getDescription()) && ((disconnectCause.getCode()
                     == DisconnectCause.ERROR) || (disconnectCause.getCode()
@@ -5467,13 +5844,17 @@
             return;
         }
         ConnectionServiceWrapper service = mConnectionServiceRepository.getService(
-                phoneAccountHandle.getComponentName(), phoneAccountHandle.getUserHandle());
+                phoneAccountHandle.getComponentName(), phoneAccountHandle.getUserHandle(),
+                mFeatureFlags);
         if (service == null) {
             Log.i(this, "Found no connection service.");
             return;
         } else {
             call.setConnectionService(service);
             service.createConnectionFailed(call);
+            if (!mCalls.contains(call)){
+                mListeners.forEach(l -> l.onCreateConnectionFailed(call));
+            }
         }
     }
 
@@ -5489,23 +5870,27 @@
             return;
         }
         ConnectionServiceWrapper service = mConnectionServiceRepository.getService(
-                phoneAccountHandle.getComponentName(), phoneAccountHandle.getUserHandle());
+                phoneAccountHandle.getComponentName(), phoneAccountHandle.getUserHandle(),
+                mFeatureFlags);
         if (service == null) {
             Log.i(this, "Found no connection service.");
             return;
         } else {
             call.setConnectionService(service);
             service.createConferenceFailed(call);
+            if (!mCalls.contains(call)){
+                mListeners.forEach(l -> l.onCreateConnectionFailed(call));
+            }
         }
     }
 
     /**
-     * Notify interested parties that a new call has been created, but not yet added to
-     * CallsManager.
+     * Notify interested parties that a new call is about to be handed off to a ConnectionService to
+     * be created.
      * @param theCall the new call.
      */
-    private void notifyCallCreated(final Call theCall) {
-        mListeners.forEach(l -> l.onCallCreated(theCall));
+    private void notifyStartCreateConnection(final Call theCall) {
+        mListeners.forEach(l -> l.onStartCreateConnection(theCall));
     }
 
     /**
@@ -5590,7 +5975,7 @@
                 handoverFromCall.getHandle(), null,
                 null, null,
                 Call.CALL_DIRECTION_OUTGOING, false,
-                false, mClockProxy, mToastFactory);
+                false, mClockProxy, mToastFactory, mFeatureFlags);
         call.initAnalytics();
 
         // Set self-managed and voipAudioMode if destination is self-managed CS
@@ -5598,7 +5983,9 @@
         if (isSelfManaged) {
             call.setIsVoipAudioMode(true);
         }
-        call.setInitiatingUser(getCurrentUserHandle());
+        // Set associated user based on the existing call as it doesn't make sense to handover calls
+        // across user profiles.
+        call.setAssociatedUser(handoverFromCall.getAssociatedUser());
 
         // Ensure we don't try to place an outgoing call with video if video is not
         // supported.
@@ -5670,7 +6057,7 @@
                 // Disconnect all self-managed calls to make priority for emergency call.
                 disconnectSelfManagedCalls("emergency call");
             }
-
+            notifyStartCreateConnection(call);
             call.startCreateConnection(mPhoneAccountRegistrar);
         }
 
@@ -5795,7 +6182,8 @@
                 false /* forceAttachToExistingConnection */,
                 false, /* isConference */
                 mClockProxy,
-                mToastFactory);
+                mToastFactory,
+                mFeatureFlags);
 
         if (fromCall == null || isHandoverInProgress() ||
                 !isHandoverFromPhoneAccountSupported(fromCall.getTargetPhoneAccount()) ||
@@ -5832,6 +6220,9 @@
         fromCall.setHandoverDestinationCall(call);
         call.setHandoverSourceCall(fromCall);
         call.setHandoverState(HandoverState.HANDOVER_TO_STARTED);
+        // Set associated user based on the existing call as it doesn't make sense to handover calls
+        // across user profiles.
+        call.setAssociatedUser(fromCall.getAssociatedUser());
         fromCall.setHandoverState(HandoverState.HANDOVER_FROM_STARTED);
 
         if (isSpeakerEnabledForVideoCalls() && VideoProfile.isVideo(videoState)) {
@@ -5847,7 +6238,7 @@
         extras.putBoolean(TelecomManager.EXTRA_IS_HANDOVER_CONNECTION, true);
         extras.putParcelable(TelecomManager.EXTRA_HANDOVER_FROM_PHONE_ACCOUNT,
                 fromCall.getTargetPhoneAccount());
-
+        notifyStartCreateConnection(call);
         call.startCreateConnection(mPhoneAccountRegistrar);
     }
 
@@ -5967,34 +6358,53 @@
     }
 
     /**
-     * Intended for ongoing or new calls that would like to go active/answered and need to
-     * update the mConnectionSvrFocusMgr before setting the state
+     * This helper mainly requests mConnectionSvrFocusMgr to update the call focus via a
+     * {@link TransactionalFocusRequestCallback}.  However, in the case of a held call, the
+     * state must be set first and then a request must be made.
+     *
+     * @param newCallFocus          to set active/answered
+     * @param resultCallback        that back propagates the focusManager result
+     *
+     * Note: This method should only be called if there are no active calls.
      */
-    public void transactionRequestNewFocusCall(Call call, int newCallState,
-            OutcomeReceiver<Boolean, CallException> callback) {
-        Log.d(this, "transactionRequestNewFocusCall");
-        PendingAction pendingAction = new ActionSetCallState(call, newCallState,
-                "transactional ActionSetCallState");
+    public void requestNewCallFocusAndVerify(Call newCallFocus,
+            OutcomeReceiver<Boolean, CallException> resultCallback) {
+        int currentCallState = newCallFocus.getState();
+        PendingAction pendingAction = null;
+
+        // if the current call is in a state that can become the new call focus, we can set the
+        // state afterwards...
+        if (ConnectionServiceFocusManager.PRIORITY_FOCUS_CALL_STATE.contains(currentCallState)) {
+            pendingAction = new ActionSetCallState(newCallFocus, CallState.ACTIVE,
+                    "vCFC: pending action set state");
+        } else {
+            // However, HELD calls need to be set to ACTIVE before requesting call focus.
+            setCallState(newCallFocus, CallState.ACTIVE, "vCFC: immediately set active");
+        }
+
         mConnectionSvrFocusMgr
-                .requestFocus(call,
-                        new TransactionalFocusRequestCallback(pendingAction, call, callback));
+                .requestFocus(newCallFocus,
+                        new TransactionalFocusRequestCallback(pendingAction, currentCallState,
+                                newCallFocus, resultCallback));
     }
 
     /**
      * Request a new call focus and ensure the request was successful via an OutcomeReceiver. Also,
-     * include a PendingAction that will execute if the call focus change is successful.
+     * conditionally include a PendingAction that will execute if and only if the call focus change
+     * is successful.
      */
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     public class TransactionalFocusRequestCallback implements
             ConnectionServiceFocusManager.RequestFocusCallback {
         private PendingAction mPendingAction;
-        @NonNull
-        private Call mTargetCallFocus;
+        private int mPreviousCallState;
+        @NonNull private Call mTargetCallFocus;
         private OutcomeReceiver<Boolean, CallException> mCallback;
 
-        TransactionalFocusRequestCallback(PendingAction pendingAction, @NonNull Call call,
-                OutcomeReceiver<Boolean, CallException> callback) {
+        TransactionalFocusRequestCallback(PendingAction pendingAction, int previousState,
+                @NonNull Call call, OutcomeReceiver<Boolean, CallException> callback) {
             mPendingAction = pendingAction;
+            mPreviousCallState = previousState;
             mTargetCallFocus = call;
             mCallback = callback;
         }
@@ -6007,12 +6417,18 @@
                     mTargetCallFocus, currentCallFocus);
             if (currentCallFocus == null ||
                     !currentCallFocus.getId().equals(mTargetCallFocus.getId())) {
+                // possibly reset the call state
+                if (mTargetCallFocus.getState() != mPreviousCallState) {
+                    mTargetCallFocus.setState(mPreviousCallState, "resetting call state");
+                }
                 mCallback.onError(new CallException("failed to switch focus to requested call",
                         CallException.CODE_CALL_CANNOT_BE_SET_TO_ACTIVE));
                 return;
             }
             // at this point, we know the FocusManager is able to update successfully
-            mPendingAction.performAction(); // set the call state
+            if (mPendingAction != null) {
+                mPendingAction.performAction(); // set the call state
+            }
             mCallback.onResult(true); // complete the transaction
         }
     }
@@ -6093,7 +6509,7 @@
      * @return {@code true} if call is visible to the calling user
      */
     boolean isCallVisibleForUser(Call call, UserHandle userHandle) {
-        return call.getUserHandleFromTargetPhoneAccount().equals(userHandle)
+        return call.getAssociatedUser().equals(userHandle)
                 || call.getPhoneAccountFromHandle()
                 .hasCapabilities(PhoneAccount.CAPABILITY_MULTI_USER);
     }
@@ -6180,6 +6596,10 @@
         return mRinger;
     }
 
+    @VisibleForTesting
+    public VoipCallMonitor getVoipCallMonitor() {
+        return mVoipCallMonitor;
+    }
 
     /**
      * This method should only be used for testing.
@@ -6193,4 +6613,40 @@
     public CallStreamingController getCallStreamingController() {
         return mCallStreamingController;
     }
+
+    /**
+     * Given a call identified by call id, get the instance from the list of calls.
+     * @param callId the call id.
+     * @return the call, or null if not found.
+     */
+    public @Nullable Call getCall(@NonNull String callId) {
+        Optional<Call> foundCall = mCalls.stream().filter(
+                c -> c.getId().equals(callId)).findFirst();
+        if (foundCall.isPresent()) {
+            return foundCall.get();
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Triggers stopping of call streaming for a call by launching a stop streaming transaction.
+     * @param call the call.
+     */
+    public void stopCallStreaming(@NonNull Call call) {
+        if (call.getTransactionServiceWrapper() == null) {
+            return;
+        }
+        call.getTransactionServiceWrapper().stopCallStreaming(call);
+    }
+
+    @VisibleForTesting
+    public Set<Call> getSelfManagedCallsBeingSetup() {
+        return mSelfManagedCallsBeingSetup;
+    }
+
+    @VisibleForTesting
+    public void addCallBeingSetup(Call call) {
+        mSelfManagedCallsBeingSetup.add(call);
+    }
 }
diff --git a/src/com/android/server/telecom/CallsManagerListenerBase.java b/src/com/android/server/telecom/CallsManagerListenerBase.java
index 6b8e2fe..0c54be3 100644
--- a/src/com/android/server/telecom/CallsManagerListenerBase.java
+++ b/src/com/android/server/telecom/CallsManagerListenerBase.java
@@ -16,10 +16,10 @@
 
 package com.android.server.telecom;
 
-import android.telecom.AudioState;
 import android.telecom.CallAudioState;
 import android.telecom.CallEndpoint;
 import android.telecom.VideoProfile;
+
 import java.util.Set;
 
 /**
@@ -35,6 +35,10 @@
     }
 
     @Override
+    public void onCreateConnectionFailed(Call call) {
+    }
+
+    @Override
     public void onCallStateChanged(Call call, int oldState, int newState) {
     }
 
@@ -108,7 +112,7 @@
     }
 
     @Override
-    public void onDisconnectedTonePlaying(boolean isTonePlaying) {
+    public void onDisconnectedTonePlaying(Call call, boolean isTonePlaying) {
     }
 
     @Override
diff --git a/src/com/android/server/telecom/ConnectionServiceFocusManager.java b/src/com/android/server/telecom/ConnectionServiceFocusManager.java
index 8db98e9..35be0f8 100644
--- a/src/com/android/server/telecom/ConnectionServiceFocusManager.java
+++ b/src/com/android/server/telecom/ConnectionServiceFocusManager.java
@@ -25,13 +25,20 @@
 import android.telecom.Log;
 import android.telecom.Logging.Session;
 import android.text.TextUtils;
+import android.util.LocalLog;
+import android.util.LogPrinter;
+import android.util.Printer;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.telecom.flags.Flags;
+import com.android.internal.util.IndentingPrintWriter;
 
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
+import java.util.Set;
+import java.util.UUID;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
@@ -40,6 +47,12 @@
 public class ConnectionServiceFocusManager {
     private static final String TAG = "ConnectionSvrFocusMgr";
     private static final int GET_CURRENT_FOCUS_TIMEOUT_MILLIS = 1000;
+    private final LocalLog mLocalLog = new LocalLog(20);
+    private final AnomalyReporterAdapter mAnomalyReporter = new AnomalyReporterAdapterImpl();
+    public static final UUID WATCHDOG_GET_CALL_FOCUS_TIMEOUT_UUID =
+            UUID.fromString("edd7334a-ef87-432b-a1d0-a2f23959c73e");
+    public static final String WATCHDOG_GET_CALL_FOCUS_TIMEOUT_MSG =
+            "Telecom CallAnomalyWatchdog detected a timeout while getting the call focus.";
 
     /** Factory interface used to create the {@link ConnectionServiceFocusManager} instance. */
     public interface ConnectionServiceFocusManagerFactory {
@@ -123,6 +136,11 @@
          * @return {@code True} if this call can receive focus, {@code false} otherwise.
          */
         boolean isFocusable();
+
+        /**
+         * @return the ID of the focusable for debug purposes.
+         */
+        String getId();
     }
 
     /** Interface define a call back for focus request event. */
@@ -153,10 +171,9 @@
         void setCallsManagerListener(CallsManager.CallsManagerListener listener);
     }
 
-    private static final int[] PRIORITY_FOCUS_CALL_STATE = new int[] {
-            CallState.ACTIVE, CallState.CONNECTING, CallState.DIALING, CallState.AUDIO_PROCESSING,
-            CallState.RINGING
-    };
+    public static final Set<Integer> PRIORITY_FOCUS_CALL_STATE
+            = Set.of(CallState.ACTIVE, CallState.CONNECTING, CallState.DIALING,
+            CallState.AUDIO_PROCESSING, CallState.RINGING);
 
     private static final int MSG_REQUEST_FOCUS = 1;
     private static final int MSG_RELEASE_CONNECTION_FOCUS = 2;
@@ -324,8 +341,23 @@
             if (syncCallFocus != null) {
                 return syncCallFocus.orElse(null);
             } else {
-                Log.w(TAG, "Timed out waiting for synchronous current focus. Returning possibly"
-                        + " inaccurate result");
+                if (Flags.genAnomReportOnFocusTimeout()) {
+                    Log.w(TAG, "Timed out waiting for synchronous current focus. Returning possibly"
+                                    + " inaccurate result. returning currentFocusCall=[%s]",
+                            mCurrentFocusCall);
+
+                    // dump the state of the handler to better understand the timeout
+                    mEventHandler.dump(
+                            new LogPrinter(android.util.Log.INFO, TAG), "CsFocusMgr_timeout");
+
+                    // report the timeout
+                    mAnomalyReporter.reportAnomaly(
+                            WATCHDOG_GET_CALL_FOCUS_TIMEOUT_UUID,
+                            WATCHDOG_GET_CALL_FOCUS_TIMEOUT_MSG);
+                } else {
+                    Log.w(TAG, "Timed out waiting for synchronous current focus. Returning possibly"
+                            + " inaccurate result");
+                }
                 return mCurrentFocusCall;
             }
         } catch (InterruptedException e) {
@@ -361,10 +393,11 @@
     }
 
     private void updateCurrentFocusCall() {
+        CallFocus previousFocus = mCurrentFocusCall;
         mCurrentFocusCall = null;
 
         if (mCurrentFocus == null) {
-            Log.d(this, "updateCurrentFocusCall: mCurrentFocus is null");
+            Log.i(this, "updateCurrentFocusCall: mCurrentFocus is null");
             return;
         }
 
@@ -374,17 +407,20 @@
                         && call.isFocusable())
                 .collect(Collectors.toList());
 
-        for (int i = 0; i < PRIORITY_FOCUS_CALL_STATE.length; i++) {
-            for (CallFocus call : calls) {
-                if (call.getState() == PRIORITY_FOCUS_CALL_STATE[i]) {
-                    mCurrentFocusCall = call;
-                    Log.d(this, "updateCurrentFocusCall %s", mCurrentFocusCall);
-                    return;
+        for (CallFocus call : calls) {
+            if (PRIORITY_FOCUS_CALL_STATE.contains(call.getState())) {
+                mCurrentFocusCall = call;
+                if (previousFocus != call) {
+                    mLocalLog.log(call.getId());
                 }
+                Log.i(this, "updateCurrentFocusCall %s", mCurrentFocusCall);
+                return;
             }
         }
-
-        Log.d(this, "updateCurrentFocusCall = null");
+        if (previousFocus != null) {
+            mLocalLog.log("<none>");
+        }
+        Log.i(this, "updateCurrentFocusCall = null");
     }
 
     private void onRequestFocusDone(FocusRequest focusRequest) {
@@ -479,6 +515,11 @@
         }
     }
 
+    public void dump(IndentingPrintWriter pw) {
+        pw.println("Call Focus History:");
+        mLocalLog.dump(pw);
+    }
+
     private final class FocusManagerHandler extends Handler {
         FocusManagerHandler(Looper looper) {
             super(looper);
diff --git a/src/com/android/server/telecom/ConnectionServiceRepository.java b/src/com/android/server/telecom/ConnectionServiceRepository.java
index 3991ed5..d6a78d0 100644
--- a/src/com/android/server/telecom/ConnectionServiceRepository.java
+++ b/src/com/android/server/telecom/ConnectionServiceRepository.java
@@ -23,6 +23,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.telecom.flags.FeatureFlags;
 
 import java.util.HashMap;
 
@@ -61,7 +62,10 @@
     }
 
     @VisibleForTesting
-    public ConnectionServiceWrapper getService(ComponentName componentName, UserHandle userHandle) {
+    public ConnectionServiceWrapper getService(
+            ComponentName componentName,
+            UserHandle userHandle,
+            FeatureFlags featureFlags) {
         Pair<ComponentName, UserHandle> cacheKey = Pair.create(componentName, userHandle);
         ConnectionServiceWrapper service = mServiceCache.get(cacheKey);
         if (service == null) {
@@ -72,7 +76,8 @@
                     mCallsManager,
                     mContext,
                     mLock,
-                    userHandle);
+                    userHandle,
+                    featureFlags);
             service.addListener(mUnbindListener);
             mServiceCache.put(cacheKey, service);
         }
diff --git a/src/com/android/server/telecom/ConnectionServiceWrapper.java b/src/com/android/server/telecom/ConnectionServiceWrapper.java
index 59a84f9..5936730 100644
--- a/src/com/android/server/telecom/ConnectionServiceWrapper.java
+++ b/src/com/android/server/telecom/ConnectionServiceWrapper.java
@@ -23,6 +23,7 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.graphics.drawable.Icon;
 import android.location.Location;
 import android.location.LocationManager;
 import android.location.LocationRequest;
@@ -32,7 +33,6 @@
 import android.os.CancellationSignal;
 import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
-import android.os.Process;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.UserHandle;
@@ -64,19 +64,23 @@
 import com.android.internal.telecom.IVideoProvider;
 import com.android.internal.telecom.RemoteServiceCallback;
 import com.android.internal.util.Preconditions;
+import com.android.server.telecom.flags.FeatureFlags;
+import com.android.server.telecom.flags.Flags;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 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.ConcurrentHashMap;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
-import java.util.Objects;
 
 /**
  * Wrapper for {@link IConnectionService}s, handles binding to {@link IConnectionService} and keeps
@@ -89,10 +93,12 @@
         ConnectionServiceFocusManager.ConnectionServiceFocus {
 
     private static final String TELECOM_ABBREVIATION = "cast";
-
+    private static final long SERVICE_BINDING_TIMEOUT = 15000L;
     private CompletableFuture<Pair<Integer, Location>> mQueryLocationFuture = null;
     private @Nullable CancellationSignal mOngoingQueryLocationRequest = null;
     private final ExecutorService mQueryLocationExecutor = Executors.newSingleThreadExecutor();
+    private ScheduledExecutorService mScheduledExecutor =
+            Executors.newSingleThreadScheduledExecutor();
 
     private final class Adapter extends IConnectionServiceAdapter.Stub {
 
@@ -101,10 +107,17 @@
                 ParcelableConnection connection, Session.Info sessionInfo) {
             Log.startSession(sessionInfo, LogUtils.Sessions.CSW_HANDLE_CREATE_CONNECTION_COMPLETE,
                     mPackageAbbreviation);
+            UserHandle callingUserHandle = Binder.getCallingUserHandle();
             long token = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
                     logIncoming("handleCreateConnectionComplete %s", callId);
+                    // Check status hints image for cross user access
+                    if (connection.getStatusHints() != null) {
+                        Icon icon = connection.getStatusHints().getIcon();
+                        connection.getStatusHints().setIcon(StatusHints.
+                                validateAccountIconUserBoundary(icon, callingUserHandle));
+                    }
                     ConnectionServiceWrapper.this
                             .handleCreateConnectionComplete(callId, request, connection);
 
@@ -505,6 +518,14 @@
             Log.startSession(sessionInfo, LogUtils.Sessions.CSW_ADD_CONFERENCE_CALL,
                     mPackageAbbreviation);
 
+            UserHandle callingUserHandle = Binder.getCallingUserHandle();
+            // Check status hints image for cross user access
+            if (parcelableConference.getStatusHints() != null) {
+                Icon icon = parcelableConference.getStatusHints().getIcon();
+                parcelableConference.getStatusHints().setIcon(StatusHints
+                        .validateAccountIconUserBoundary(icon, callingUserHandle));
+            }
+
             if (parcelableConference.getConnectElapsedTimeMillis() != 0
                     && mContext.checkCallingOrSelfPermission(MODIFY_PHONE_STATE)
                             != PackageManager.PERMISSION_GRANTED) {
@@ -770,10 +791,17 @@
         public void setStatusHints(String callId, StatusHints statusHints,
                 Session.Info sessionInfo) {
             Log.startSession(sessionInfo, "CSW.sSH", mPackageAbbreviation);
+            UserHandle callingUserHandle = Binder.getCallingUserHandle();
             long token = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
                     logIncoming("setStatusHints %s %s", callId, statusHints);
+                    // Check status hints image for cross user access
+                    if (statusHints != null) {
+                        Icon icon = statusHints.getIcon();
+                        statusHints.setIcon(StatusHints.validateAccountIconUserBoundary(
+                                icon, callingUserHandle));
+                    }
                     Call call = mCallIdMapper.getCall(callId);
                     if (call != null) {
                         call.setStatusHints(statusHints);
@@ -920,6 +948,9 @@
                         callingPhoneAccountHandle.getComponentName().getPackageName());
             }
 
+            boolean hasCrossUserAccess = mContext.checkCallingOrSelfPermission(
+                    android.Manifest.permission.INTERACT_ACROSS_USERS)
+                    == PackageManager.PERMISSION_GRANTED;
             long token = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
@@ -931,7 +962,7 @@
                     // an emergency call.
                             mPhoneAccountRegistrar.getCallCapablePhoneAccounts(null /*uriScheme*/,
                             false /*includeDisabledAccounts*/, userHandle, 0 /*capabilities*/,
-                            0 /*excludedCapabilities*/, false);
+                            0 /*excludedCapabilities*/, hasCrossUserAccess);
                     PhoneAccountHandle phoneAccountHandle = null;
                     for (PhoneAccountHandle accountHandle : accountHandles) {
                         if(accountHandle.equals(callingPhoneAccountHandle)) {
@@ -967,6 +998,12 @@
                             connectIdToCheck = callId;
                         }
 
+                        // Check status hints image for cross user access
+                        if (connection.getStatusHints() != null) {
+                            Icon icon = connection.getStatusHints().getIcon();
+                            connection.getStatusHints().setIcon(StatusHints.
+                                    validateAccountIconUserBoundary(icon, userHandle));
+                        }
                         // 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
@@ -1317,6 +1354,7 @@
     private final CallsManager mCallsManager;
     private final AppOpsManager mAppOpsManager;
     private final Context mContext;
+    private final FeatureFlags mFlags;
 
     private ConnectionServiceFocusManager.ConnectionServiceFocusListener mConnSvrFocusListener;
 
@@ -1338,8 +1376,10 @@
             CallsManager callsManager,
             Context context,
             TelecomSystem.SyncRoot lock,
-            UserHandle userHandle) {
-        super(ConnectionService.SERVICE_INTERFACE, componentName, context, lock, userHandle);
+            UserHandle userHandle,
+            FeatureFlags featureFlags) {
+        super(ConnectionService.SERVICE_INTERFACE, componentName, context, lock, userHandle,
+                featureFlags);
         mConnectionServiceRepository = connectionServiceRepository;
         phoneAccountRegistrar.addListener(new PhoneAccountRegistrar.Listener() {
             // TODO -- Upon changes to PhoneAccountRegistrar, need to re-wire connections
@@ -1349,6 +1389,7 @@
         mCallsManager = callsManager;
         mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
         mContext = context;
+        mFlags = featureFlags;
     }
 
     /** See {@link IConnectionService#addConnectionServiceAdapter}. */
@@ -1432,7 +1473,11 @@
                 callback.send(0,
                         getQueryLocationErrorResult(QueryLocationException.ERROR_UNSPECIFIED));
             }
+            //make sure we don't pass mock locations diretly, always reset() mock locations
             if (result.second != null) {
+                if(result.second.isMock()) {
+                    result.second.reset();
+                }
                 callback.send(1, getQueryLocationResult(result.second));
             } else {
                 callback.send(0, getQueryLocationErrorResult(result.first));
@@ -1558,7 +1603,22 @@
                         .setParticipants(call.getParticipants())
                         .setIsAdhocConferenceCall(call.isAdhocConferenceCall())
                         .build();
-
+                if (Flags.unbindTimeoutConnections()) {
+                    android.telecom.Logging.Runnable r =
+                            new android.telecom.Logging.Runnable("CSW.cC", mLock) {
+                        @Override
+                        public void loggedRun() {
+                            if (!call.isCreateConnectionComplete()) {
+                                Log.e(this, new Exception(), "Conference %s creation timeout",
+                                        getComponentName());
+                                response.handleCreateConferenceFailure(
+                                        new DisconnectCause(DisconnectCause.ERROR));
+                            }
+                        }
+                    };
+                    mScheduledExecutor.schedule(r.getRunnableToCancel(), SERVICE_BINDING_TIMEOUT,
+                            TimeUnit.MILLISECONDS);
+                }
                 try {
                     mServiceInterface.createConference(
                             call.getConnectionManagerPhoneAccount(),
@@ -1567,7 +1627,6 @@
                             call.shouldAttachToExistingConnection(),
                             call.isUnknown(),
                             Log.getExternalSession(TELECOM_ABBREVIATION));
-
                 } catch (RemoteException e) {
                     Log.e(this, e, "Failure to createConference -- %s", getComponentName());
                     mPendingResponses.remove(callId).handleCreateConferenceFailure(
@@ -1600,6 +1659,7 @@
                     Log.i(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;
@@ -1661,6 +1721,23 @@
                         .setRttPipeToInCall(call.getCsToInCallRttPipeForCs())
                         .build();
 
+                if (Flags.unbindTimeoutConnections()) {
+                    android.telecom.Logging.Runnable r =
+                            new android.telecom.Logging.Runnable("CSW.cC", mLock) {
+                                @Override
+                                public void loggedRun() {
+                                    if (!call.isCreateConnectionComplete()) {
+                                        Log.e(this, new Exception(),
+                                                "Connection %s creation timeout",
+                                                getComponentName());
+                                        response.handleCreateConnectionFailure(
+                                                new DisconnectCause(DisconnectCause.ERROR));
+                                    }
+                                }
+                            };
+                    mScheduledExecutor.schedule(r.getRunnableToCancel(), SERVICE_BINDING_TIMEOUT,
+                            TimeUnit.MILLISECONDS);
+                }
                 try {
                     mServiceInterface.createConnection(
                             call.getConnectionManagerPhoneAccount(),
@@ -1669,7 +1746,6 @@
                             call.shouldAttachToExistingConnection(),
                             call.isUnknown(),
                             Log.getExternalSession(TELECOM_ABBREVIATION));
-
                 } catch (RemoteException e) {
                     Log.e(this, e, "Failure to createConnection -- %s", getComponentName());
                     mPendingResponses.remove(callId).handleCreateConnectionFailure(
@@ -2118,7 +2194,8 @@
         }
     }
 
-    void addCall(Call call) {
+    @VisibleForTesting
+    public void addCall(Call call) {
         if (mCallIdMapper.getCallId(call) == null) {
             mCallIdMapper.addCall(call);
         }
@@ -2354,6 +2431,7 @@
         BindCallback callback = new BindCallback() {
             @Override
             public void onSuccess() {
+                if (!isServiceValid("connectionServiceFocusLost")) return;
                 try {
                     mServiceInterface.connectionServiceFocusLost(
                             Log.getExternalSession(TELECOM_ABBREVIATION));
@@ -2373,6 +2451,7 @@
         BindCallback callback = new BindCallback() {
             @Override
             public void onSuccess() {
+                if (!isServiceValid("connectionServiceFocusGained")) return;
                 try {
                     mServiceInterface.connectionServiceFocusGained(
                             Log.getExternalSession(TELECOM_ABBREVIATION));
@@ -2451,12 +2530,11 @@
      */
     private void handleConnectionServiceDeath() {
         if (!mPendingResponses.isEmpty()) {
-            CreateConnectionResponse[] responses = mPendingResponses.values().toArray(
-                    new CreateConnectionResponse[mPendingResponses.values().size()]);
+            Collection<CreateConnectionResponse> responses = mPendingResponses.values();
             mPendingResponses.clear();
-            for (int i = 0; i < responses.length; i++) {
-                responses[i].handleCreateConnectionFailure(
-                        new DisconnectCause(DisconnectCause.ERROR, "CS_DEATH"));
+            for (CreateConnectionResponse response : responses) {
+                response.handleCreateConnectionFailure(new DisconnectCause(DisconnectCause.ERROR,
+                        "CS_DEATH"));
             }
         }
         mCallIdMapper.clear();
@@ -2500,7 +2578,7 @@
                 isCallerConnectionManager = true;
             }
             ConnectionServiceWrapper service = mConnectionServiceRepository.getService(
-                    handle.getComponentName(), handle.getUserHandle());
+                    handle.getComponentName(), handle.getUserHandle(), mFlags);
             if (service != null && service != this) {
                 simServices.add(service);
             } else {
@@ -2585,4 +2663,9 @@
         sb.append("]");
         return sb.toString();
     }
+
+    @VisibleForTesting
+    public void setScheduledExecutorService(ScheduledExecutorService service) {
+        mScheduledExecutor = service;
+    }
 }
diff --git a/src/com/android/server/telecom/CreateConnectionProcessor.java b/src/com/android/server/telecom/CreateConnectionProcessor.java
index 331c32b..f5b257d 100644
--- a/src/com/android/server/telecom/CreateConnectionProcessor.java
+++ b/src/com/android/server/telecom/CreateConnectionProcessor.java
@@ -32,6 +32,8 @@
 // TODO: Needed for move to system service: import com.android.internal.R;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.telecom.flags.Flags;
+import com.android.server.telecom.flags.FeatureFlags;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -125,6 +127,7 @@
     private DisconnectCause mLastErrorDisconnectCause;
     private final PhoneAccountRegistrar mPhoneAccountRegistrar;
     private final Context mContext;
+    private final FeatureFlags mFlags;
     private CreateConnectionTimeout mTimeout;
     private ConnectionServiceWrapper mService;
     private int mConnectionAttempt;
@@ -132,7 +135,8 @@
     @VisibleForTesting
     public CreateConnectionProcessor(
             Call call, ConnectionServiceRepository repository, CreateConnectionResponse response,
-            PhoneAccountRegistrar phoneAccountRegistrar, Context context) {
+            PhoneAccountRegistrar phoneAccountRegistrar, Context context,
+            FeatureFlags featureFlags) {
         Log.v(this, "CreateConnectionProcessor created for Call = %s", call);
         mCall = call;
         mRepository = repository;
@@ -140,6 +144,7 @@
         mPhoneAccountRegistrar = phoneAccountRegistrar;
         mContext = context;
         mConnectionAttempt = 0;
+        mFlags = featureFlags;
     }
 
     boolean isProcessingComplete() {
@@ -239,7 +244,7 @@
             Log.i(this, "Trying attempt %s", attempt);
             PhoneAccountHandle phoneAccount = attempt.connectionManagerPhoneAccount;
             mService = mRepository.getService(phoneAccount.getComponentName(),
-                    phoneAccount.getUserHandle());
+                    phoneAccount.getUserHandle(), mFlags);
             if (mService == null) {
                 Log.i(this, "Found no connection service for attempt %s", attempt);
                 attemptNextPhoneAccount();
@@ -247,7 +252,25 @@
                 mConnectionAttempt++;
                 mCall.setConnectionManagerPhoneAccount(attempt.connectionManagerPhoneAccount);
                 mCall.setTargetPhoneAccount(attempt.targetPhoneAccount);
-                mCall.setConnectionService(mService);
+                if (mFlags.updatedRcsCallCountTracking()) {
+                    if (Objects.equals(attempt.connectionManagerPhoneAccount,
+                            attempt.targetPhoneAccount)) {
+                        mCall.setConnectionService(mService);
+                    } else {
+                        PhoneAccountHandle remotePhoneAccount = attempt.targetPhoneAccount;
+                        ConnectionServiceWrapper mRemoteService =
+                                mRepository.getService(remotePhoneAccount.getComponentName(),
+                                remotePhoneAccount.getUserHandle(), mFlags);
+                        if (mRemoteService == null) {
+                            mCall.setConnectionService(mService);
+                        } else {
+                            Log.v(this, "attemptNextPhoneAccount Setting RCS = %s", mRemoteService);
+                            mCall.setConnectionService(mService, mRemoteService);
+                        }
+                    }
+                } else {
+                    mCall.setConnectionService(mService);
+                }
                 setTimeoutIfNeeded(mService, attempt);
                 if (mCall.isIncoming()) {
                     if (mCall.isAdhocConferenceCall()) {
@@ -391,7 +414,7 @@
             // current user.
             // ONLY include phone accounts which are NOT self-managed; we will never consider a self
             // managed phone account for placing an emergency call.
-            UserHandle userFromCall = mCall.getUserHandleFromTargetPhoneAccount();
+            UserHandle userFromCall = mCall.getAssociatedUser();
             List<PhoneAccount> allAccounts = mPhoneAccountRegistrar
                     .getAllPhoneAccounts(userFromCall, false)
                     .stream()
@@ -429,19 +452,28 @@
             // Get user preferred PA if it exists.
             PhoneAccount preferredPA = mPhoneAccountRegistrar.getPhoneAccountUnchecked(
                     preferredPAH);
-            // Next, add all SIM phone accounts which can place emergency calls.
-            sortSimPhoneAccountsForEmergency(allAccounts, preferredPA);
-            // 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)) {
-                    PhoneAccountHandle phoneAccountHandle = phoneAccount.getAccountHandle();
-                    Log.i(this, "Will try PSTN account %s for emergency", phoneAccountHandle);
-                    mAttemptRecords.add(new CallAttemptRecord(phoneAccountHandle,
-                            phoneAccountHandle));
-                    // Add only one emergency SIM PhoneAccount to the attempt list, telephony will
-                    // perform retries if the call fails.
-                    break;
+            if (mCall.isIncoming() && preferredPA != null) {
+                // The phone account for the incoming call should be used.
+                mAttemptRecords.add(new CallAttemptRecord(preferredPA.getAccountHandle(),
+                        preferredPA.getAccountHandle()));
+            } else {
+                // Next, add all SIM phone accounts which can place emergency calls.
+                sortSimPhoneAccountsForEmergency(allAccounts, preferredPA);
+                Log.i(this, "The preferred PA is: %s", preferredPA);
+                // 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)) {
+                        PhoneAccountHandle phoneAccountHandle = phoneAccount.getAccountHandle();
+                        Log.i(this, "Will try PSTN account %s for emergency",
+                                phoneAccountHandle);
+                        mAttemptRecords.add(new CallAttemptRecord(phoneAccountHandle,
+                                phoneAccountHandle));
+                        // Add only one emergency SIM PhoneAccount to the attempt list, telephony
+                        // will perform retries if the call fails.
+                        break;
+                    }
                 }
             }
 
diff --git a/src/com/android/server/telecom/DefaultDialerCache.java b/src/com/android/server/telecom/DefaultDialerCache.java
index a4a0242..dc79715 100644
--- a/src/com/android/server/telecom/DefaultDialerCache.java
+++ b/src/com/android/server/telecom/DefaultDialerCache.java
@@ -160,6 +160,7 @@
         packageIntentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
         packageIntentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
         packageIntentFilter.addDataScheme("package");
+        packageIntentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
         context.registerReceiverAsUser(mReceiver, UserHandle.ALL, packageIntentFilter, null, null);
 
         IntentFilter bootIntentFilter = new IntentFilter(Intent.ACTION_BOOT_COMPLETED);
@@ -265,7 +266,7 @@
             if (packageName == null ||
                     Objects.equals(packageName, mCurrentDefaultDialerPerUser.get(userId))) {
                 String newDefaultDialer = refreshCacheForUser(userId);
-                Log.i(LOG_TAG, "Refreshing default dialer for user %d: now %s",
+                Log.v(LOG_TAG, "Refreshing default dialer for user %d: now %s",
                         userId, newDefaultDialer);
             }
         }
diff --git a/src/com/android/server/telecom/DockManager.java b/src/com/android/server/telecom/DockManager.java
index dda5711..114672e 100644
--- a/src/com/android/server/telecom/DockManager.java
+++ b/src/com/android/server/telecom/DockManager.java
@@ -76,6 +76,7 @@
 
         // Register for misc other intent broadcasts.
         IntentFilter intentFilter = new IntentFilter(Intent.ACTION_DOCK_EVENT);
+        intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
         context.registerReceiver(mReceiver, intentFilter);
     }
 
diff --git a/src/com/android/server/telecom/EmergencyCallDiagnosticLogger.java b/src/com/android/server/telecom/EmergencyCallDiagnosticLogger.java
new file mode 100644
index 0000000..af79da3
--- /dev/null
+++ b/src/com/android/server/telecom/EmergencyCallDiagnosticLogger.java
@@ -0,0 +1,438 @@
+/*
+ * Copyright (C) 2022 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 static android.telephony.TelephonyManager.EmergencyCallDiagnosticParams;
+
+import android.os.BugreportManager;
+import android.os.DropBoxManager;
+import android.provider.DeviceConfig;
+import android.telecom.DisconnectCause;
+import android.telecom.Log;
+import android.telephony.TelephonyManager;
+import android.util.LocalLog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executor;
+
+/**
+ * The EmergencyCallDiagnosticsLogger monitors information required to diagnose potential outgoing
+ * ecall failures on the device. When a potential failure is detected, it calls a Telephony API to
+ * persist relevant information (dumpsys, logcat etc.) to the dropbox. This acts as a central place
+ * to determine when and what to collect.
+ *
+ * <p>When a bugreport is triggered, this module will read the dropbox entries and add them to the
+ * telecom dump.
+ */
+public class EmergencyCallDiagnosticLogger extends CallsManagerListenerBase
+        implements Call.Listener {
+
+    public static final int REPORT_REASON_RANGE_START = -1; //!!DO NOT CHANGE
+    public static final int REPORT_REASON_RANGE_END = 5; //increment this and add new reason above
+    public static final int COLLECTION_TYPE_BUGREPORT = 10;
+    public static final int COLLECTION_TYPE_TELECOM_STATE = 11;
+    public static final int COLLECTION_TYPE_TELEPHONY_STATE = 12;
+    public static final int COLLECTION_TYPE_LOGCAT_BUFFERS = 13;
+    private static final int REPORT_REASON_STUCK_CALL_DETECTED = 0;
+    private static final int REPORT_REASON_INACTIVE_CALL_TERMINATED_BY_USER_AFTER_DELAY = 1;
+    private static final int REPORT_REASON_CALL_FAILED = 2;
+    private static final int REPORT_REASON_CALL_CREATED_BUT_NEVER_ADDED = 3;
+    private static final int REPORT_REASON_SHORT_DURATION_AFTER_GOING_ACTIVE = 4;
+    private static final String DROPBOX_TAG = "ecall_diagnostic_data";
+    private static final String ENABLE_BUGREPORT_COLLECTION_FOR_EMERGENCY_CALL_DIAGNOSTICS =
+            "enable_bugreport_collection_for_emergency_call_diagnostics";
+    private static final String ENABLE_TELECOM_DUMP_COLLECTION_FOR_EMERGENCY_CALL_DIAGNOSTICS =
+            "enable_telecom_dump_collection_for_emergency_call_diagnostics";
+
+    private static final String ENABLE_LOGCAT_COLLECTION_FOR_EMERGENCY_CALL_DIAGNOSTICS =
+            "enable_logcat_collection_for_emergency_call_diagnostics";
+    private static final String ENABLE_TELEPHONY_DUMP_COLLECTION_FOR_EMERGENCY_CALL_DIAGNOSTICS =
+            "enable_telephony_dump_collection_for_emergency_call_diagnostics";
+
+    private static final String DUMPSYS_ARG_FOR_DIAGNOSTICS = "EmergencyDiagnostics";
+
+    // max text size to read from dropbox entry
+    private static final int DEFAULT_MAX_READ_BYTES_PER_DROP_BOX_ENTRY = 500000;
+    private static final String MAX_BYTES_PER_DROP_BOX_ENTRY = "max_bytes_per_dropbox_entry";
+    private static final int MAX_DROPBOX_ENTRIES_TO_DUMP = 6;
+
+    private final Timeouts.Adapter mTimeoutAdapter;
+    // This map holds all calls, but keeps pruning non-emergency calls when we can determine it
+    private final Map<Call, CallEventTimestamps> mEmergencyCallsMap = new ConcurrentHashMap<>(2);
+    private final DropBoxManager mDropBoxManager;
+    private final LocalLog mLocalLog = new LocalLog(10);
+    private final TelephonyManager mTelephonyManager;
+    private final BugreportManager mBugreportManager;
+    private final Executor mAsyncTaskExecutor;
+    private final ClockProxy mClockProxy;
+
+    public EmergencyCallDiagnosticLogger(
+            TelephonyManager tm,
+            BugreportManager brm,
+            Timeouts.Adapter timeoutAdapter, DropBoxManager dropBoxManager,
+            Executor asyncTaskExecutor, ClockProxy clockProxy) {
+        mTimeoutAdapter = timeoutAdapter;
+        mDropBoxManager = dropBoxManager;
+        mTelephonyManager = tm;
+        mBugreportManager = brm;
+        mAsyncTaskExecutor = asyncTaskExecutor;
+        mClockProxy = clockProxy;
+    }
+
+    // this calculates time from ACTIVE --> removed
+    private static long getCallTimeInActiveStateSec(CallEventTimestamps ts) {
+        if (ts.getCallActiveTime() == 0 || ts.getCallRemovedTime() == 0) {
+            return 0;
+        } else {
+            return (ts.getCallRemovedTime() - ts.getCallActiveTime()) / 1000;
+        }
+    }
+
+    // this calculates time from call created --> removed
+    private static long getTotalCallTimeSec(CallEventTimestamps ts) {
+        if (ts.getCallRemovedTime() == 0 || ts.getCallCreatedTime() == 0) {
+            return 0;
+        } else {
+            return (ts.getCallRemovedTime() - ts.getCallCreatedTime()) / 1000;
+        }
+    }
+
+    //determines what to collect based on fail reason
+    //if COLLECTION_TYPE_BUGREPORT is present in the returned list, then that
+    //should be the only collection type in the list
+    @VisibleForTesting
+    public static List<Integer> getDataCollectionTypes(int reason) {
+        switch (reason) {
+            case REPORT_REASON_SHORT_DURATION_AFTER_GOING_ACTIVE:
+                return Arrays.asList(COLLECTION_TYPE_TELECOM_STATE);
+            case REPORT_REASON_CALL_CREATED_BUT_NEVER_ADDED:
+                return Arrays.asList(
+                        COLLECTION_TYPE_TELECOM_STATE, COLLECTION_TYPE_TELEPHONY_STATE);
+            case REPORT_REASON_CALL_FAILED:
+            case REPORT_REASON_INACTIVE_CALL_TERMINATED_BY_USER_AFTER_DELAY:
+            case REPORT_REASON_STUCK_CALL_DETECTED:
+                return Arrays.asList(
+                        COLLECTION_TYPE_TELECOM_STATE,
+                        COLLECTION_TYPE_TELEPHONY_STATE,
+                        COLLECTION_TYPE_LOGCAT_BUFFERS);
+            default:
+        }
+        return new ArrayList<>();
+    }
+
+    private int getMaxBytesPerDropboxEntry() {
+        return DeviceConfig.getInt(DeviceConfig.NAMESPACE_TELEPHONY,
+                MAX_BYTES_PER_DROP_BOX_ENTRY, DEFAULT_MAX_READ_BYTES_PER_DROP_BOX_ENTRY);
+    }
+
+    @VisibleForTesting
+    public Map<Call, CallEventTimestamps> getEmergencyCallsMap() {
+        return mEmergencyCallsMap;
+    }
+
+    private void triggerDiagnosticsCollection(Call call, int reason) {
+        Log.i(this, "Triggering diagnostics for call %s reason: %d", call.getId(), reason);
+        List<Integer> dataCollectionTypes = getDataCollectionTypes(reason);
+        boolean invokeTelephonyPersistApi = false;
+        CallEventTimestamps ts = mEmergencyCallsMap.get(call);
+        EmergencyCallDiagnosticParams dp =
+                new EmergencyCallDiagnosticParams();
+        for (Integer dataCollectionType : dataCollectionTypes) {
+            switch (dataCollectionType) {
+                case COLLECTION_TYPE_TELECOM_STATE:
+                    if (isTelecomDumpCollectionEnabled()) {
+                        dp.setTelecomDumpSysCollection(true);
+                        invokeTelephonyPersistApi = true;
+                    }
+                    break;
+                case COLLECTION_TYPE_TELEPHONY_STATE:
+                    if (isTelephonyDumpCollectionEnabled()) {
+                        dp.setTelephonyDumpSysCollection(true);
+                        invokeTelephonyPersistApi = true;
+                    }
+                    break;
+                case COLLECTION_TYPE_LOGCAT_BUFFERS:
+                    if (isLogcatCollectionEnabled()) {
+                        dp.setLogcatCollection(true, ts.getCallCreatedTime());
+                        invokeTelephonyPersistApi = true;
+                    }
+                    break;
+                case COLLECTION_TYPE_BUGREPORT:
+                    if (isBugreportCollectionEnabled()) {
+                        mAsyncTaskExecutor.execute(new Runnable() {
+                            @Override
+                            public void run() {
+                                persistBugreport();
+                            }
+                        });
+                    }
+                    break;
+                default:
+            }
+        }
+        if (invokeTelephonyPersistApi) {
+            mAsyncTaskExecutor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    Log.i(this, "Requesting Telephony to persist data %s", dp.toString());
+                    try {
+                        mTelephonyManager.persistEmergencyCallDiagnosticData(DROPBOX_TAG, dp);
+                    } catch (Exception e) {
+                        Log.w(this,
+                                "Exception while invoking "
+                                        + "Telephony#persistEmergencyCallDiagnosticData  %s",
+                                e.toString());
+                    }
+                }
+            });
+        }
+    }
+
+    private boolean isBugreportCollectionEnabled() {
+        return DeviceConfig.getBoolean(
+                DeviceConfig.NAMESPACE_TELEPHONY,
+                ENABLE_BUGREPORT_COLLECTION_FOR_EMERGENCY_CALL_DIAGNOSTICS,
+                false);
+    }
+
+    private boolean isTelecomDumpCollectionEnabled() {
+        return DeviceConfig.getBoolean(
+                DeviceConfig.NAMESPACE_TELEPHONY,
+                ENABLE_TELECOM_DUMP_COLLECTION_FOR_EMERGENCY_CALL_DIAGNOSTICS,
+                true);
+    }
+
+    private boolean isLogcatCollectionEnabled() {
+        return DeviceConfig.getBoolean(
+                DeviceConfig.NAMESPACE_TELEPHONY,
+                ENABLE_LOGCAT_COLLECTION_FOR_EMERGENCY_CALL_DIAGNOSTICS,
+                true);
+    }
+
+    private boolean isTelephonyDumpCollectionEnabled() {
+        return DeviceConfig.getBoolean(
+                DeviceConfig.NAMESPACE_TELEPHONY,
+                ENABLE_TELEPHONY_DUMP_COLLECTION_FOR_EMERGENCY_CALL_DIAGNOSTICS,
+                true);
+    }
+
+    private void persistBugreport() {
+        if (isBugreportCollectionEnabled()) {
+            // TODO:
+        }
+    }
+
+    private boolean shouldTrackCall(Call call) {
+        return (call != null && call.isEmergencyCall() && call.isOutgoing());
+    }
+
+    public void reportStuckCall(Call call) {
+        if (shouldTrackCall(call)) {
+            Log.i(this, "Triggering diagnostics for stuck call %s", call.getId());
+            triggerDiagnosticsCollection(call, REPORT_REASON_STUCK_CALL_DETECTED);
+            call.removeListener(this);
+            mEmergencyCallsMap.remove(call);
+        }
+    }
+
+    @Override
+    public void onStartCreateConnection(Call call) {
+        if (shouldTrackCall(call)) {
+            long currentTime = mClockProxy.currentTimeMillis();
+            call.addListener(this);
+            Log.i(this, "Tracking call %s timestamp: %d", call.getId(), currentTime);
+            mEmergencyCallsMap.put(call, new CallEventTimestamps(currentTime));
+        }
+    }
+
+    @Override
+    public void onCreateConnectionFailed(Call call) {
+        if (shouldTrackCall(call)) {
+            Log.i(this, "Triggering diagnostics for  call %s that was never added", call.getId());
+            triggerDiagnosticsCollection(call, REPORT_REASON_CALL_CREATED_BUT_NEVER_ADDED);
+            call.removeListener(this);
+            mEmergencyCallsMap.remove(call);
+        }
+    }
+
+    /**
+     * Override of {@link CallsManagerListenerBase} to track when calls are removed
+     *
+     * @param call the call
+     */
+    @Override
+    public void onCallRemoved(Call call) {
+        if (call != null && (mEmergencyCallsMap.get(call) != null)) {
+            call.removeListener(this);
+
+            CallEventTimestamps ts = mEmergencyCallsMap.get(call);
+            long currentTime = mClockProxy.currentTimeMillis();
+            ts.setCallRemovedTime(currentTime);
+
+            maybeTriggerDiagnosticsCollection(call, ts);
+            mEmergencyCallsMap.remove(call);
+        }
+    }
+
+    // !NOTE!: this method should only be called after we get onCallRemoved
+    private void maybeTriggerDiagnosticsCollection(Call removedCall, CallEventTimestamps ts) {
+        Log.i(this, "Evaluating emergency call for diagnostic logging: %s", removedCall.getId());
+        boolean wentActive = (ts.getCallActiveTime() != 0);
+        long callActiveTimeSec = (wentActive ? getCallTimeInActiveStateSec(ts) : 0);
+        long timeSinceCallCreatedSec = getTotalCallTimeSec(ts);
+        int dc = removedCall.getDisconnectCause().getCode();
+
+        if (wentActive) {
+            if (callActiveTimeSec
+                    < mTimeoutAdapter.getEmergencyCallActiveTimeThresholdMillis() / 1000) {
+                // call connected but did not go on for long
+                triggerDiagnosticsCollection(
+                        removedCall, REPORT_REASON_SHORT_DURATION_AFTER_GOING_ACTIVE);
+            }
+        } else {
+
+            if (dc == DisconnectCause.LOCAL
+                    && timeSinceCallCreatedSec
+                    > mTimeoutAdapter.getEmergencyCallTimeBeforeUserDisconnectThresholdMillis()
+                    / 1000) {
+                // call was disconnected by the user (but not immediately)
+                triggerDiagnosticsCollection(
+                        removedCall, REPORT_REASON_INACTIVE_CALL_TERMINATED_BY_USER_AFTER_DELAY);
+            } else if (dc != DisconnectCause.LOCAL) {
+                // this can be a case for a full bugreport
+                triggerDiagnosticsCollection(removedCall, REPORT_REASON_CALL_FAILED);
+            }
+        }
+    }
+
+    /**
+     * Override of {@link com.android.server.telecom.CallsManager.CallsManagerListener} to track
+     * call state changes.
+     *
+     * @param call     the call
+     * @param oldState its old state
+     * @param newState the new state
+     */
+    @Override
+    public void onCallStateChanged(Call call, int oldState, int newState) {
+
+        if (call != null && mEmergencyCallsMap.get(call) != null && newState == CallState.ACTIVE) {
+            CallEventTimestamps ts = mEmergencyCallsMap.get(call);
+            if (ts != null) {
+                long currentTime = mClockProxy.currentTimeMillis();
+                ts.setCallActiveTime(currentTime);
+            }
+        }
+    }
+
+    private void dumpDiagnosticDataFromDropbox(IndentingPrintWriter pw) {
+        pw.increaseIndent();
+        pw.println("PERSISTED DIAGNOSTIC DATA FROM DROP BOX");
+        int totalEntriesDumped = 0;
+        long currentTime = mClockProxy.currentTimeMillis();
+        long entriesAfterTime =
+                currentTime - (mTimeoutAdapter.getDaysBackToSearchEmergencyDiagnosticEntries() * 24
+                        * 60L * 60L * 1000L);
+        Log.i(this, "current time: %d entriesafter: %d", currentTime, entriesAfterTime);
+        DropBoxManager.Entry entry;
+        entry = mDropBoxManager.getNextEntry(DROPBOX_TAG, entriesAfterTime);
+        while (entry != null) {
+            Log.i(this, "found entry with ts: %d", entry.getTimeMillis());
+            String content[] = entry.getText(getMaxBytesPerDropboxEntry()).split(
+                    System.lineSeparator());
+            long entryTime = entry.getTimeMillis();
+            if (content != null) {
+                pw.increaseIndent();
+                pw.println("------------BEGIN ENTRY (" + entryTime + ")--------");
+                for (String line : content) {
+                    pw.println(line);
+                }
+                pw.println("--------END ENTRY--------");
+                pw.decreaseIndent();
+                totalEntriesDumped++;
+            }
+            entry = mDropBoxManager.getNextEntry(DROPBOX_TAG, entryTime);
+            if (totalEntriesDumped > MAX_DROPBOX_ENTRIES_TO_DUMP) {
+                /*
+                Since Emergency calls are a rare/once in a lifetime time occurrence for most users,
+                we should not be seeing too many entries. This code just guards against edge case
+                like load testing, b2b failures etc. We may accumulate a lot of dropbox entries in
+                such cases, but we limit to dumping only MAX_DROPBOX_ENTRIES_TO_DUMP in the
+                bugreport
+
+                The Dropbox API in its current state does not allow to query Entries in reverse
+                chronological order efficiently.
+                 */
+
+                Log.i(this, "Skipping dump for remaining entries. dumped :%d", totalEntriesDumped);
+                break;
+            }
+        }
+        pw.println("END OF PERSISTED DIAGNOSTIC DATA FROM DROP BOX");
+        pw.decreaseIndent();
+    }
+
+    public void dump(IndentingPrintWriter pw, String[] args) {
+        pw.increaseIndent();
+        mLocalLog.dump(pw);
+        pw.decreaseIndent();
+        if (args != null && args.length > 0 && args[0].equals(DUMPSYS_ARG_FOR_DIAGNOSTICS)) {
+            //dont read dropbox entries since this dump is triggered by telephony for diagnostics
+            Log.i(this, "skipped dumping diagnostic data");
+            return;
+        }
+        dumpDiagnosticDataFromDropbox(pw);
+    }
+
+    private static class CallEventTimestamps {
+
+        private final long mCallCreatedTime;
+        private long mCallActiveTime;
+        private long mCallRemovedTime;
+
+        public CallEventTimestamps(long createdTime) {
+            mCallCreatedTime = createdTime;
+        }
+
+        public long getCallActiveTime() {
+            return mCallActiveTime;
+        }
+
+        public void setCallActiveTime(long callActiveTime) {
+            this.mCallActiveTime = callActiveTime;
+        }
+
+        public long getCallCreatedTime() {
+            return mCallCreatedTime;
+        }
+
+        public long getCallRemovedTime() {
+            return mCallRemovedTime;
+        }
+
+        public void setCallRemovedTime(long callRemovedTime) {
+            this.mCallRemovedTime = callRemovedTime;
+        }
+    }
+}
diff --git a/src/com/android/server/telecom/EmergencyCallHelper.java b/src/com/android/server/telecom/EmergencyCallHelper.java
index a213e26..5ab0e99 100644
--- a/src/com/android/server/telecom/EmergencyCallHelper.java
+++ b/src/com/android/server/telecom/EmergencyCallHelper.java
@@ -21,6 +21,8 @@
 import android.content.pm.PackageManager;
 import android.os.UserHandle;
 import android.telecom.Log;
+import android.telecom.PhoneAccountHandle;
+
 import com.android.internal.annotations.VisibleForTesting;
 
 /**
@@ -34,6 +36,7 @@
     private final DefaultDialerCache mDefaultDialerCache;
     private final Timeouts.Adapter mTimeoutsAdapter;
     private UserHandle mLocationPermissionGrantedToUser;
+    private PhoneAccountHandle mLastOutgoingEmergencyCallPAH;
 
     //stores the original state of permissions that dialer had
     private boolean mHadFineLocation = false;
@@ -46,6 +49,7 @@
     private boolean mBackgroundLocationGranted = false;
 
     private long mLastEmergencyCallTimestampMillis;
+    private long mLastOutgoingEmergencyCallTimestampMillis;
 
     @VisibleForTesting
     public EmergencyCallHelper(
@@ -63,7 +67,7 @@
             grantLocationPermission(userHandle);
         }
         if (call != null && call.isEmergencyCall()) {
-            recordEmergencyCallTime();
+            recordEmergencyCall(call);
         }
     }
 
@@ -78,15 +82,44 @@
         return mLastEmergencyCallTimestampMillis;
     }
 
-    private void recordEmergencyCallTime() {
-        mLastEmergencyCallTimestampMillis = System.currentTimeMillis();
+    @VisibleForTesting
+    public void setLastOutgoingEmergencyCallTimestampMillis(long timestampMillis) {
+        mLastOutgoingEmergencyCallTimestampMillis = timestampMillis;
     }
 
-    private boolean isInEmergencyCallbackWindow() {
-        return System.currentTimeMillis() - getLastEmergencyCallTimeMillis()
+    @VisibleForTesting
+    public void setLastOutgoingEmergencyCallPAH(PhoneAccountHandle accountHandle) {
+        mLastOutgoingEmergencyCallPAH = accountHandle;
+    }
+
+    @VisibleForTesting
+    public boolean isLastOutgoingEmergencyCallPAH(PhoneAccountHandle currentCallHandle) {
+        boolean ecbmActive = mLastOutgoingEmergencyCallPAH != null
+                && isInEmergencyCallbackWindow(mLastOutgoingEmergencyCallTimestampMillis)
+                && currentCallHandle != null
+                && currentCallHandle.equals(mLastOutgoingEmergencyCallPAH);
+        if (ecbmActive) {
+            Log.i(this, "ECBM is enabled for %s. The last recorded call timestamp was at %s",
+                    currentCallHandle, mLastOutgoingEmergencyCallTimestampMillis);
+        }
+
+        return ecbmActive;
+    }
+
+    boolean isInEmergencyCallbackWindow(long lastEmergencyCallTimestampMillis) {
+        return System.currentTimeMillis() - lastEmergencyCallTimestampMillis
                 < mTimeoutsAdapter.getEmergencyCallbackWindowMillis(mContext.getContentResolver());
     }
 
+    private void recordEmergencyCall(Call call) {
+        mLastEmergencyCallTimestampMillis = System.currentTimeMillis();
+        if (!call.isIncoming()) {
+            // ECBM is applicable to MO emergency calls
+            mLastOutgoingEmergencyCallTimestampMillis = mLastEmergencyCallTimestampMillis;
+            mLastOutgoingEmergencyCallPAH = call.getTargetPhoneAccount();
+        }
+    }
+
     private boolean shouldGrantTemporaryLocationPermission(Call call) {
         if (!mContext.getResources().getBoolean(R.bool.grant_location_permission_enabled)) {
             Log.i(this, "ShouldGrantTemporaryLocationPermission, disabled by config");
@@ -96,7 +129,8 @@
             Log.i(this, "ShouldGrantTemporaryLocationPermission, no call");
             return false;
         }
-        if (!call.isEmergencyCall() && !isInEmergencyCallbackWindow()) {
+        if (!call.isEmergencyCall() && !isInEmergencyCallbackWindow(
+                getLastEmergencyCallTimeMillis())) {
             Log.i(this, "ShouldGrantTemporaryLocationPermission, not emergency");
             return false;
         }
diff --git a/src/com/android/server/telecom/InCallAdapter.java b/src/com/android/server/telecom/InCallAdapter.java
index f5214ed..9ce10bd 100755
--- a/src/com/android/server/telecom/InCallAdapter.java
+++ b/src/com/android/server/telecom/InCallAdapter.java
@@ -695,7 +695,7 @@
     @Override
     public void sendRttRequest(String callId) {
         try {
-            Log.startSession("ICA.sRR");
+            Log.startSession("ICA.sRR", mOwnerPackageAbbreviation);
             long token = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
@@ -717,7 +717,7 @@
     @Override
     public void respondToRttRequest(String callId, int id, boolean accept) {
         try {
-            Log.startSession("ICA.rTRR");
+            Log.startSession("ICA.rTRR", mOwnerPackageAbbreviation);
             long token = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
@@ -739,7 +739,7 @@
     @Override
     public void stopRtt(String callId) {
         try {
-            Log.startSession("ICA.sRTT");
+            Log.startSession("ICA.sRTT", mOwnerPackageAbbreviation);
             long token = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
@@ -761,11 +761,16 @@
     @Override
     public void setRttMode(String callId, int mode) {
         try {
-            Log.startSession("ICA.sRM");
+            Log.startSession("ICA.sRM", mOwnerPackageAbbreviation);
             long token = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
-                    // TODO
+                    Call call = mCallIdMapper.getCall(callId);
+                    if (call != null) {
+                        call.setRttMode(mode);
+                    } else {
+                        Log.w(this, "setRttMode(): call %s not found", callId);
+                    }
                 }
             } finally {
                 Binder.restoreCallingIdentity(token);
diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java
index 33ddf0a..1aee25c 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -22,7 +22,9 @@
 import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.ActivityManager;
 import android.app.AppOpsManager;
+import android.app.KeyguardManager;
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.content.AttributionSource;
@@ -65,6 +67,7 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.telecom.SystemStateHelper.SystemStateListener;
+import com.android.server.telecom.flags.FeatureFlags;
 import com.android.server.telecom.ui.NotificationChannelManager;
 
 import java.util.ArrayList;
@@ -79,6 +82,7 @@
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 /**
  * Binds to {@link IInCallService} and provides the service to {@link CallsManager} through which it
@@ -98,14 +102,6 @@
             UUID.fromString("0c2adf96-353a-433c-afe9-1e5564f304f9");
     public static final String SET_IN_CALL_ADAPTER_ERROR_MSG =
             "Exception thrown while setting the in-call adapter.";
-    public static final UUID BIND_TO_IN_CALL_ERROR_UUID =
-            UUID.fromString("1261231d-b16a-4e0c-a322-623f8bb8e599");
-    public static final String BIND_TO_IN_CALL_ERROR_MSG =
-            "Failed to connect when attempting to bind to InCall.";
-    public static final UUID BIND_TO_IN_CALL_EMERGENCY_ERROR_UUID =
-            UUID.fromString("9ec8f1f0-3f0b-4079-9e9f-325f1262a8c7");
-    public static final String BIND_TO_IN_CALL_EMERGENCY_ERROR_MSG =
-            "Outgoing emergency call failed to connect when attempting to bind to InCall.";
 
     @VisibleForTesting
     public void setAnomalyReporterAdapter(AnomalyReporterAdapter mAnomalyReporterAdapter){
@@ -154,16 +150,20 @@
         private long mBindingStartTime;
         private long mDisconnectTime;
 
+        private boolean mHasCrossUserOrProfilePerm;
+
         public InCallServiceInfo(ComponentName componentName,
                 boolean isExternalCallsSupported,
                 boolean isSelfManageCallsSupported,
-                int type) {
+                int type, boolean hasCrossUserOrProfilePerm) {
             mComponentName = componentName;
             mIsExternalCallsSupported = isExternalCallsSupported;
             mIsSelfManagedCallsSupported = isSelfManageCallsSupported;
             mType = type;
+            mHasCrossUserOrProfilePerm = hasCrossUserOrProfilePerm;
         }
 
+        public boolean hasCrossUserOrProfilePermission() { return mHasCrossUserOrProfilePerm; }
         public ComponentName getComponentName() {
             return mComponentName;
         }
@@ -300,8 +300,19 @@
         private boolean mIsNullBinding = false;
         private NotificationManager mNotificationManager;
 
+        //this is really used for cases where the userhandle for a call
+        //does not match what we want to use for bindAsUser
+        private final UserHandle mUserHandleToUseForBinding;
+
         public InCallServiceBindingConnection(InCallServiceInfo info) {
             mInCallServiceInfo = info;
+            mUserHandleToUseForBinding = null;
+        }
+
+        public InCallServiceBindingConnection(InCallServiceInfo info,
+                UserHandle userHandleToUseForBinding) {
+            mInCallServiceInfo = info;
+            mUserHandleToUseForBinding = userHandleToUseForBinding;
         }
 
         @Override
@@ -343,19 +354,36 @@
             Log.i(this, "Attempting to bind to InCall %s, with %s", mInCallServiceInfo, intent);
             mIsConnected = true;
             mInCallServiceInfo.setBindingStartTime(mClockProxy.elapsedRealtime());
-            UserHandle userToBind = getUserFromCall(call);
+            boolean isManagedProfile = UserUtil.isManagedProfile(mContext, userFromCall);
+            // Note that UserHandle.CURRENT fails to capture the work profile, so we need to handle
+            // it separately to ensure that the ICS is bound to the appropriate user. If ECBM is
+            // active, we know that a work sim was previously used to place a MO emergency call. We
+            // need to ensure that we bind to the CURRENT_USER in this case, as the work user would
+            // not be running (handled in getUserFromCall).
+            UserHandle userToBind = isManagedProfile ? userFromCall : UserHandle.CURRENT;
+            if ((mInCallServiceInfo.mType == IN_CALL_SERVICE_TYPE_NON_UI
+                    || mInCallServiceInfo.mType == IN_CALL_SERVICE_TYPE_CAR_MODE_UI) && (
+                    mUserHandleToUseForBinding != null)) {
+                //guarding change for non-UI/carmode-UI services which may not be present for
+                // work profile.
+                //In this case, we use the parent user handle. (This also seems to be more
+                // accurate that USER_CURRENT since we queried/discovered the packages using the
+                // parent handle)
+                if (mInCallServiceInfo.hasCrossUserOrProfilePermission()) {
+                    userToBind = mUserHandleToUseForBinding;
+                } else {
+                    Log.i(this,
+                            "service does not have INTERACT_ACROSS_PROFILES or "
+                                    + "INTERACT_ACROSS_USERS permission");
+                }
+            }
+            Log.i(this, "using user id: %s for binding. User from Call is: %s", userToBind,
+                    userFromCall);
             if (!mContext.bindServiceAsUser(intent, mServiceConnection,
                     Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
                         | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS
                         | Context.BIND_SCHEDULE_LIKE_TOP_APP, userToBind)) {
                 Log.w(this, "Failed to connect.");
-                if (call != null && call.isEmergencyCall()) {
-                    mAnomalyReporter.reportAnomaly(BIND_TO_IN_CALL_EMERGENCY_ERROR_UUID,
-                            BIND_TO_IN_CALL_EMERGENCY_ERROR_MSG);
-                } else {
-                    mAnomalyReporter.reportAnomaly(BIND_TO_IN_CALL_ERROR_UUID,
-                            BIND_TO_IN_CALL_ERROR_MSG);
-                }
                 mIsConnected = false;
             }
 
@@ -676,7 +704,7 @@
                 }
 
                 mCarModeConnection = mCurrentConnection =
-                        new InCallServiceBindingConnection(carModeConnectionInfo);
+                        new InCallServiceBindingConnection(carModeConnectionInfo, userHandle);
                 mIsCarMode = true;
 
                 int result = mCurrentConnection.connect(null);
@@ -816,6 +844,9 @@
             }
             Call callToConnectWith = mCallIdMapper.getCalls().iterator().next();
             for (InCallServiceBindingConnection newConnection : newConnections) {
+                // Ensure we track the new sub-connection so that when we later disconnect we will
+                // be able to disconnect it.
+                mSubConnections.add(newConnection);
                 newConnection.connect(callToConnectWith);
             }
         }
@@ -965,34 +996,137 @@
         }
     };
 
+    private UserHandle findChildManagedProfileUser(UserHandle parent, UserManager um) {
+        UserHandle childManagedProfileUser = null;
+
+        //find child managed profile user (if any)
+        List<UserHandle> allUsers = um.getAllProfiles();
+        for (UserHandle u : allUsers) {
+            if ((um.getProfileParent(u) != null) && (um.getProfileParent(u).equals(parent))
+                    && um.isManagedProfile(u.getIdentifier())) {
+                //found managed profile child
+                Log.i(this,
+                        "Child managed profile user found: " + u.getIdentifier());
+                childManagedProfileUser = u;
+                break;
+            }
+        }
+        return childManagedProfileUser;
+    }
     private BroadcastReceiver mPackageChangedReceiver = new BroadcastReceiver() {
+        private List<InCallController.InCallServiceInfo> getNonUiInCallServiceInfoList(
+                Intent intent, UserHandle userHandle) {
+            String changedPackage = intent.getData().getSchemeSpecificPart();
+            List<InCallController.InCallServiceInfo> inCallServiceInfoList =
+                    Arrays.stream(intent.getStringArrayExtra(
+                                    Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST))
+                            .map((className) ->
+                                    ComponentName.createRelative(changedPackage,
+                                            className))
+                            .filter(mKnownNonUiInCallServices::contains)
+                            .flatMap(componentName -> getInCallServiceComponents(
+                                    userHandle, componentName,
+                                    IN_CALL_SERVICE_TYPE_NON_UI).stream())
+                            .collect(Collectors.toList());
+            return ((inCallServiceInfoList != null) ? inCallServiceInfoList : new ArrayList<>());
+        }
+
+        //Here we query components using the userHandle. We then also query components using the
+        //parent userHandle (if any) while removing duplicates. For non-dup components found using
+        //parent userHandle, we use the overloaded InCallServiceBindingConnection constructor.
+        @SuppressWarnings("ReturnValueIgnored")
+        private List<InCallServiceBindingConnection> getNonUiInCallServiceBindingConnectionList(
+                Intent intent, @NonNull UserHandle userHandle, UserHandle parentUserHandle) {
+            List<InCallServiceBindingConnection> result = new ArrayList<>();
+            List<InCallController.InCallServiceInfo> serviceInfoListForParent = new ArrayList<>();
+
+            //query and add components for the child
+            List<InCallController.InCallServiceInfo> serviceInfoListForUser =
+                    getNonUiInCallServiceInfoList(intent, userHandle);
+
+            //if user has a parent, get components for parents
+            if (parentUserHandle != null) {
+                serviceInfoListForParent = getNonUiInCallServiceInfoList(intent, parentUserHandle);
+            }
+
+            serviceInfoListForUser
+                    .stream()
+                    .map(InCallServiceBindingConnection::new)
+                    .collect(Collectors.toCollection(() -> result));
+
+            serviceInfoListForParent
+                    .stream()
+                    .filter((e) -> !(serviceInfoListForUser.contains(e)))
+                    .map((serviceinfo) -> new InCallServiceBindingConnection(serviceinfo,
+                            parentUserHandle))
+                    .collect(Collectors.toCollection(() -> result));
+
+            return result;
+        }
+
         @Override
         public void onReceive(Context context, Intent intent) {
             Log.startSession("ICC.pCR");
+            UserManager um = mContext.getSystemService(UserManager.class);
             try {
                 if (Intent.ACTION_PACKAGE_CHANGED.equals(intent.getAction())) {
                     synchronized (mLock) {
-                        String changedPackage = intent.getData().getSchemeSpecificPart();
                         int uid = intent.getIntExtra(Intent.EXTRA_UID, 0);
                         UserHandle userHandle = UserHandle.getUserHandleForUid(uid);
-                        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(
-                                                userHandle, componentName,
-                                                IN_CALL_SERVICE_TYPE_NON_UI).stream())
-                                        .map(InCallServiceBindingConnection::new)
-                                        .collect(Collectors.toList());
+                        boolean isManagedProfile = um.isManagedProfile(userHandle.getIdentifier());
 
-                        if (mNonUIInCallServiceConnections.containsKey(userHandle)) {
-                            mNonUIInCallServiceConnections.get(userHandle).
-                                    addConnections(componentsToBind);
+                        /*
+                        There are two possibilities here:
+                         1) We get a work-profile/managed userHandle. In this case we need to check
+                         if there are any ongoing calls for that user. If yes, then process further
+                         by querying component using this user handle (also bindAsUser using this
+                          handle). Else safely ignore it.
+                                OR
+                         2) We get the primary/non-managed userHandle. In this case, we have two
+                          sub-cases to handle:
+                                   a) If there are ongoing calls for this user, query components
+                                   using this user and addConnections
+                                   b) If there are ongoing calls for the child of this user, we
+                                   also addConnections to that child (but invoke bindAsUser later
+                                    with the parent handle).
+
+                         */
+
+                        UserHandle childManagedProfileUser = findChildManagedProfileUser(
+                                userHandle, um);
+                        boolean isUserKeyPresent = mNonUIInCallServiceConnections.containsKey(
+                                userHandle);
+                        boolean isChildUserKeyPresent = (childManagedProfileUser == null) ? false
+                                : mNonUIInCallServiceConnections.containsKey(
+                                        childManagedProfileUser);
+                        List<InCallServiceBindingConnection> componentsToBindForUser = null;
+                        List<InCallServiceBindingConnection> componentsToBindForChild = null;
+
+                        if(isUserKeyPresent) {
+                            componentsToBindForUser =
+                                    getNonUiInCallServiceBindingConnectionList(intent,
+                                            userHandle, null);
+                        }
+                        if (isChildUserKeyPresent) {
+                            componentsToBindForChild =
+                                    getNonUiInCallServiceBindingConnectionList(intent,
+                                            childManagedProfileUser, userHandle);
                         }
 
+                        Log.i(this,
+                                "isUserKeyPresent:%b isChildKeyPresent:%b isManagedProfile:%b "
+                                        + "user:%d",
+                                isUserKeyPresent, isChildUserKeyPresent, isManagedProfile,
+                                userHandle.getIdentifier());
+
+                        if (isUserKeyPresent && componentsToBindForUser != null) {
+                            mNonUIInCallServiceConnections.get(userHandle).
+                                    addConnections(componentsToBindForUser);
+                        }
+                        if (isChildUserKeyPresent && componentsToBindForChild != null) {
+                            mNonUIInCallServiceConnections.get(childManagedProfileUser).
+                                    addConnections(componentsToBindForChild);
+                        }
                         // If the current car mode app become enabled from disabled, update
                         // the connection to binding
                         updateCarModeForConnections();
@@ -1106,11 +1240,12 @@
 
     private ArraySet<String> mAllCarrierPrivilegedApps = new ArraySet<>();
     private ArraySet<String> mActiveCarrierPrivilegedApps = new ArraySet<>();
+    private FeatureFlags mFeatureFlags;
 
     public InCallController(Context context, TelecomSystem.SyncRoot lock, CallsManager callsManager,
             SystemStateHelper systemStateHelper, DefaultDialerCache defaultDialerCache,
             Timeouts.Adapter timeoutsAdapter, EmergencyCallHelper emergencyCallHelper,
-            CarModeTracker carModeTracker, ClockProxy clockProxy) {
+            CarModeTracker carModeTracker, ClockProxy clockProxy, FeatureFlags featureFlags) {
         mContext = context;
         mAppOpsManager = context.getSystemService(AppOpsManager.class);
         mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
@@ -1124,7 +1259,10 @@
         mSystemStateHelper.addListener(mSystemStateListener);
         mClockProxy = clockProxy;
         restrictPhoneCallOps();
-        mContext.registerReceiver(mUserAddedReceiver, new IntentFilter(Intent.ACTION_USER_ADDED));
+        IntentFilter userAddedFilter = new IntentFilter(Intent.ACTION_USER_ADDED);
+        userAddedFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
+        mContext.registerReceiver(mUserAddedReceiver, userAddedFilter);
+        mFeatureFlags = featureFlags;
     }
 
     private void restrictPhoneCallOps() {
@@ -1199,6 +1337,11 @@
     @Override
     public void onCallAdded(Call call) {
         UserHandle userFromCall = getUserFromCall(call);
+
+        Log.i(this, "onCallAdded: %s", call);
+        // Track the call if we don't already know about it.
+        addCall(call);
+
         if (!isBoundAndConnectedToServices(userFromCall)) {
             Log.i(this, "onCallAdded: %s; not bound or connected.", call);
             // We are not bound, or we're not connected.
@@ -1214,10 +1357,6 @@
             mEmergencyCallHelper.maybeGrantTemporaryLocationPermission(call,
                     userFromCall);
 
-            Log.i(this, "onCallAdded: %s", call);
-            // Track the call if we don't already know about it.
-            addCall(call);
-
             if (inCallServiceConnection != null) {
                 Log.i(this, "mInCallServiceConnection isConnected=%b",
                         inCallServiceConnection.isConnected());
@@ -1268,17 +1407,31 @@
     @Override
     public void onCallRemoved(Call call) {
         Log.i(this, "onCallRemoved: %s", call);
-        if (mCallsManager.getCalls().isEmpty()) {
+        // Instead of checking if there are no active calls, we should check if there any calls with
+        // the same associated user returned from getUserFromCall. For instance, it's possible to
+        // have calls coexist on the personal profile and work profile, in which case, we would only
+        // remove the ICS connection for the user associated with the call to be disconnected.
+        UserHandle userFromCall = getUserFromCall(call);
+        Stream<Call> callsAssociatedWithUserFromCall = mCallsManager.getCalls().stream()
+                .filter((c) -> getUserFromCall(c).equals(userFromCall));
+        boolean isCallCountZero = mFeatureFlags.workProfileAssociatedUser()
+                ? callsAssociatedWithUserFromCall.count() == 0
+                : mCallsManager.getCalls().isEmpty();
+        if (isCallCountZero) {
             /** Let's add a 2 second delay before we send unbind to the services to hopefully
              *  give them enough time to process all the pending messages.
              */
             mHandler.postDelayed(new Runnable("ICC.oCR", mLock) {
                 @Override
                 public void loggedRun() {
-                    // Check again to make sure there are no active calls.
-                    if (mCallsManager.getCalls().isEmpty()) {
-                        unbindFromServices(getUserFromCall(call));
-
+                    // Check again to make sure there are no active calls for the associated user.
+                    Stream<Call> callsAssociatedWithUserFromCall = mCallsManager.getCalls().stream()
+                            .filter((c) -> getUserFromCall(c).equals(userFromCall));
+                    boolean isCallCountZero = mFeatureFlags.workProfileAssociatedUser()
+                            ? callsAssociatedWithUserFromCall.count() == 0
+                            : mCallsManager.getCalls().isEmpty();
+                    if (isCallCountZero) {
+                        unbindFromServices(userFromCall);
                         mEmergencyCallHelper.maybeRevokeTemporaryLocationPermission();
                     }
                 }
@@ -1556,6 +1709,29 @@
 
     @VisibleForTesting
     public void bringToForeground(boolean showDialpad, UserHandle callingUser) {
+        KeyguardManager keyguardManager = mContext.getSystemService(KeyguardManager.class);
+        boolean isLockscreenRestricted = keyguardManager != null
+                && keyguardManager.isKeyguardLocked();
+        UserHandle currentUser = mCallsManager.getCurrentUserHandle();
+        // Handle cases when calls are placed from the keyguard UI screen, which operates under
+        // the admin user. This needs to account for emergency calls placed from secondary/guest
+        // users as well as the work profile. Once the screen is locked, the user should be able to
+        // return to the call (from the keyguard UI).
+        if (mFeatureFlags.eccKeyguard() && mCallsManager.isInEmergencyCall()
+                && isLockscreenRestricted && !mInCallServices.containsKey(callingUser)) {
+            // If screen is locked and the current user is the system, query calls for the work
+            // profile user, if available. Otherwise, the user is in the secondary/guest profile,
+            // so we can default to the system user.
+            if (currentUser.isSystem()) {
+                UserManager um = mContext.getSystemService(UserManager.class);
+                UserHandle workProfileUser = findChildManagedProfileUser(currentUser, um);
+                boolean hasWorkCalls = mCallsManager.getCalls().stream()
+                        .filter((c) -> getUserFromCall(c).equals(workProfileUser)).count() > 0;
+                callingUser = hasWorkCalls ? workProfileUser : currentUser;
+            } else {
+                callingUser = currentUser;
+            }
+        }
         if (mInCallServices.containsKey(callingUser)) {
             for (IInCallService inCallService : mInCallServices.get(callingUser).values()) {
                 try {
@@ -1573,6 +1749,11 @@
         return mInCallServices;
     }
 
+    @VisibleForTesting
+    public Map<UserHandle, CarSwappingInCallServiceConnection> getInCallServiceConnections() {
+        return mInCallServiceConnections;
+    }
+
     void silenceRinger(Set<UserHandle> userHandles) {
         userHandles.forEach(userHandle -> {
             if (mInCallServices.containsKey(userHandle)) {
@@ -1666,6 +1847,7 @@
      * Unbinds an existing bound connection to the in-call app.
      */
     public void unbindFromServices(UserHandle userHandle) {
+        Log.i(this, "Unbinding from services for user %s", userHandle);
         try {
             mContext.unregisterReceiver(mPackageChangedReceiver);
         } catch (IllegalArgumentException e) {
@@ -1693,6 +1875,14 @@
     @VisibleForTesting
     public void bindToServices(Call call) {
         UserHandle userFromCall = getUserFromCall(call);
+        UserHandle parentUser = null;
+        UserManager um = mContext.getSystemService(UserManager.class);
+
+        if (um.isManagedProfile(userFromCall.getIdentifier())) {
+            parentUser = um.getProfileParent(userFromCall);
+            Log.i(this, "child:%s  parent:%s", userFromCall, parentUser);
+        }
+
         if (!mInCallServiceConnections.containsKey(userFromCall)) {
             InCallServiceConnection dialerInCall = null;
             InCallServiceInfo defaultDialerComponentInfo = getDefaultDialerComponent(userFromCall);
@@ -1712,10 +1902,24 @@
 
             InCallServiceConnection carModeInCall = null;
             InCallServiceInfo carModeComponentInfo = getCurrentCarModeComponent(userFromCall);
+            InCallServiceInfo carModeComponentInfoForParentUser = null;
+            if(parentUser != null) {
+                //query using parent user too
+                carModeComponentInfoForParentUser = getCurrentCarModeComponent(
+                        parentUser);
+            }
+
             if (carModeComponentInfo != null &&
                     !carModeComponentInfo.getComponentName().equals(
                             mDefaultDialerCache.getSystemDialerComponent())) {
                 carModeInCall = new InCallServiceBindingConnection(carModeComponentInfo);
+            } else if (carModeComponentInfo == null &&
+                    carModeComponentInfoForParentUser != null &&
+                    !carModeComponentInfoForParentUser.getComponentName().equals(
+                            mDefaultDialerCache.getSystemDialerComponent())) {
+                carModeInCall = new InCallServiceBindingConnection(
+                        carModeComponentInfoForParentUser, parentUser);
+                Log.i(this, "Using car mode component queried using parent handle");
             }
 
             mInCallServiceConnections.put(userFromCall,
@@ -1743,17 +1947,49 @@
 
         IntentFilter packageChangedFilter = new IntentFilter(Intent.ACTION_PACKAGE_CHANGED);
         packageChangedFilter.addDataScheme("package");
-        mContext.registerReceiver(mPackageChangedReceiver, packageChangedFilter);
+        mContext.registerReceiverAsUser(mPackageChangedReceiver, UserHandle.ALL,
+                packageChangedFilter, null, null);
     }
 
     private void updateNonUiInCallServices(Call call) {
         UserHandle userFromCall = getUserFromCall(call);
+        UserHandle parentUser = null;
+
+        UserManager um = mContext.getSystemService(UserManager.class);
+        if(um.isManagedProfile(userFromCall.getIdentifier()))
+        {
+            parentUser = um.getProfileParent(userFromCall);
+        }
+
         List<InCallServiceInfo> nonUIInCallComponents =
                 getInCallServiceComponents(userFromCall, IN_CALL_SERVICE_TYPE_NON_UI);
+        List<InCallServiceInfo> nonUIInCallComponentsForParent = new ArrayList<>();
+        if(parentUser != null)
+        {
+            //also get Non-UI services using parent handle.
+            nonUIInCallComponentsForParent =
+                    getInCallServiceComponents(parentUser, IN_CALL_SERVICE_TYPE_NON_UI);
+
+        }
         List<InCallServiceBindingConnection> nonUIInCalls = new LinkedList<>();
         for (InCallServiceInfo serviceInfo : nonUIInCallComponents) {
             nonUIInCalls.add(new InCallServiceBindingConnection(serviceInfo));
         }
+
+        //add nonUI InCall services queried using parent user (if any)
+        for (InCallServiceInfo serviceInfo : nonUIInCallComponentsForParent) {
+            if (nonUIInCallComponents.contains(serviceInfo)) {
+                //skip dups
+                Log.i(this, "skipped duplicate component found using parent user: "
+                        + serviceInfo.getComponentName());
+            } else {
+                nonUIInCalls.add(new InCallServiceBindingConnection(serviceInfo, parentUser));
+                Log.i(this,
+                        "added component queried using parent user: "
+                                + serviceInfo.getComponentName());
+            }
+        }
+
         List<String> callCompanionApps = mCallsManager
                 .getRoleManagerAdapter().getCallCompanionApps();
         if (callCompanionApps != null && !callCompanionApps.isEmpty()) {
@@ -1820,7 +2056,7 @@
             // 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.");
-            return new InCallServiceInfo(componentName, false, false, type);
+            return new InCallServiceInfo(componentName, false, false, type, false);
         }
     }
 
@@ -1855,6 +2091,34 @@
         return getInCallServiceComponents(userHandle, packageName,
                 componentName, requestedType, true /* ignoreDisabled */);
     }
+    private boolean canInteractAcrossUsersOrProfiles(ServiceInfo serviceInfo,
+            PackageManager packageManager) {
+        String op = AppOpsManager.permissionToOp("android.permission.INTERACT_ACROSS_PROFILES");
+        String[] uidPackages = packageManager.getPackagesForUid(serviceInfo.applicationInfo.uid);
+
+        boolean hasInteractAcrossProfiles = Arrays.stream(uidPackages).anyMatch(
+                p -> ((packageManager.checkPermission(
+                        Manifest.permission.INTERACT_ACROSS_PROFILES,
+                        p) == PackageManager.PERMISSION_GRANTED)
+                ));
+        boolean hasInteractAcrossUsers = Arrays.stream(uidPackages).anyMatch(
+                p -> ((packageManager.checkPermission(
+                        Manifest.permission.INTERACT_ACROSS_USERS,
+                        p) == PackageManager.PERMISSION_GRANTED)
+                ));
+        boolean hasInteractAcrossProfilesAppOp = Arrays.stream(uidPackages).anyMatch(
+                p -> (AppOpsManager.MODE_ALLOWED == mAppOpsManager.checkOpNoThrow(
+                        op, serviceInfo.applicationInfo.uid, p))
+        );
+        Log.i(this,
+                "packageName:%s INTERACT_ACROSS_USERS:%b INTERACT_ACROSS_PROFILES:%b "
+                        + "INTERACT_ACROSS_PROFILES_APPOP:%b",
+                uidPackages[0], hasInteractAcrossUsers, hasInteractAcrossProfiles,
+                hasInteractAcrossProfilesAppOp);
+
+        return (hasInteractAcrossUsers || hasInteractAcrossProfiles
+                || hasInteractAcrossProfilesAppOp);
+    }
 
     private List<InCallServiceInfo> getInCallServiceComponents(UserHandle userHandle,
             String packageName, ComponentName componentName,
@@ -1868,11 +2132,16 @@
         if (componentName != null) {
             serviceIntent.setComponent(componentName);
         }
+        Log.i(this,
+                "getComponents, pkgname: " + packageName + " comp: " + componentName + " userid: "
+                        + userHandle.getIdentifier() + " requestedType: " + requestedType);
         PackageManager packageManager = mContext.getPackageManager();
         Context userContext = mContext.createContextAsUser(userHandle,
                 0 /* flags */);
         PackageManager userPackageManager = userContext != null ?
                 userContext.getPackageManager() : packageManager;
+
+
         for (ResolveInfo entry : packageManager.queryIntentServicesAsUser(
                 serviceIntent,
                 PackageManager.GET_META_DATA | PackageManager.MATCH_DISABLED_COMPONENTS,
@@ -1889,9 +2158,13 @@
 
                 int currentType = getInCallServiceType(userHandle,
                         entry.serviceInfo, packageManager, packageName);
+
+                boolean hasInteractAcrossUserOrProfilePerm = canInteractAcrossUsersOrProfiles(
+                        entry.serviceInfo, packageManager);
+
                 ComponentName foundComponentName =
                         new ComponentName(serviceInfo.packageName, serviceInfo.name);
-                if (requestedType == IN_CALL_SERVICE_TYPE_NON_UI) {
+                if (currentType == IN_CALL_SERVICE_TYPE_NON_UI) {
                     mKnownNonUiInCallServices.add(foundComponentName);
                 }
 
@@ -1904,9 +2177,16 @@
                     isRequestedType = requestedType == currentType;
                 }
 
+                Log.i(this,
+                        "found:%s isRequestedtype:%b isEnabled:%b ignoreDisabled:%b "
+                                + "hasCrossProfilePerm:%b",
+                        foundComponentName, isRequestedType, isEnabled, ignoreDisabled,
+                        hasInteractAcrossUserOrProfilePerm);
+
                 if ((!ignoreDisabled || isEnabled) && isRequestedType) {
                     retval.add(new InCallServiceInfo(foundComponentName, isExternalCallsSupported,
-                            isSelfManageCallsSupported, requestedType));
+                            isSelfManageCallsSupported, requestedType,
+                            hasInteractAcrossUserOrProfilePerm));
                 }
             }
         }
@@ -2056,7 +2336,9 @@
         }
 
         // Upon successful connection, send the state of the world to the service.
-        List<Call> calls = orderCallsWithChildrenFirst(mCallsManager.getCalls());
+        List<Call> calls = orderCallsWithChildrenFirst(mCallsManager.getCalls().stream().filter(
+                call -> getUserFromCall(call).equals(userHandle))
+                .collect(Collectors.toUnmodifiableList()));
         Log.i(this, "Adding %s calls to InCallService after onConnected: %s, including external " +
                 "calls", calls.size(), info.getComponentName());
         int numCallsSent = 0;
@@ -2193,10 +2475,15 @@
                 try {
                     inCallService.updateCall(
                             sanitizeParcelableCallForService(info, parcelableCall));
-                } catch (RemoteException ignored) {
+                } catch (RemoteException exception) {
+                    Log.w(this, "Call status update did not send to: "
+                                + componentName +" successfully with error " + exception);
                 }
             }
             Log.i(this, "Components updated: %s", componentsUpdated);
+        } else {
+            Log.i(this,
+                    "Unable to update call. InCallService not found for user: %s", userFromCall);
         }
     }
 
@@ -2204,7 +2491,8 @@
      * Adds the call to the list of calls tracked by the {@link InCallController}.
      * @param call The call to add.
      */
-    private void addCall(Call call) {
+    @VisibleForTesting
+    public void addCall(Call call) {
         if (mCallIdMapper.getCalls().size() == 0) {
             mAppOpsManager.startWatchingActive(new String[] { OPSTR_RECORD_AUDIO },
                     java.lang.Runnable::run, this);
@@ -2439,19 +2727,48 @@
         Log.i(this, "updateCarModeForConnections: car mode apps: %s",
                 mCarModeTracker.getCarModeApps().stream().collect(Collectors.joining(", ")));
 
-        if (mInCallServiceConnections.containsKey(mCallsManager.getCurrentUserHandle())) {
-            CarSwappingInCallServiceConnection inCallServiceConnection = mInCallServiceConnections.
-                    get(mCallsManager.getCurrentUserHandle());
-            if (shouldUseCarModeUI()) {
-                Log.i(this, "updateCarModeForConnections: potentially update car mode app.");
-                inCallServiceConnection.changeCarModeApp(mCarModeTracker.getCurrentCarModePackage(),
-                        mCallsManager.getCurrentUserHandle());
-            } else {
-                if (inCallServiceConnection.isCarMode()) {
-                    Log.i(this, "updateCarModeForConnections: car mode no longer "
-                            + "applicable; disabling");
-                    inCallServiceConnection.disableCarMode();
-                }
+        UserManager um = mContext.getSystemService(UserManager.class);
+        UserHandle currentUser = mCallsManager.getCurrentUserHandle();
+        UserHandle childUser = findChildManagedProfileUser(currentUser, um);
+
+        CarSwappingInCallServiceConnection inCallServiceConnectionForCurrentUser = null;
+        CarSwappingInCallServiceConnection inCallServiceConnectionForChildUser = null;
+
+        Log.i(this, "update carmode current:%s parent:%s", currentUser, childUser);
+        if (mInCallServiceConnections.containsKey(currentUser)) {
+            inCallServiceConnectionForCurrentUser = mInCallServiceConnections.
+                    get(currentUser);
+        }
+        if (childUser != null && mInCallServiceConnections.containsKey(childUser)) {
+            inCallServiceConnectionForChildUser = mInCallServiceConnections.
+                    get(childUser);
+        }
+
+        if (shouldUseCarModeUI()) {
+            Log.i(this, "updateCarModeForConnections: potentially update car mode app.");
+            //always pass current user to changeCarMode. That will ultimately be used for bindAsUser
+            if (inCallServiceConnectionForCurrentUser != null) {
+                inCallServiceConnectionForCurrentUser.changeCarModeApp(
+                        mCarModeTracker.getCurrentCarModePackage(),
+                        currentUser);
+            }
+            if (inCallServiceConnectionForChildUser != null) {
+                inCallServiceConnectionForChildUser.changeCarModeApp(
+                        mCarModeTracker.getCurrentCarModePackage(),
+                        currentUser);
+            }
+        } else {
+            if (inCallServiceConnectionForCurrentUser != null
+                    && inCallServiceConnectionForCurrentUser.isCarMode()) {
+                Log.i(this, "updateCarModeForConnections: car mode no longer "
+                        + "applicable for current user; disabling");
+                inCallServiceConnectionForCurrentUser.disableCarMode();
+            }
+            if (inCallServiceConnectionForChildUser != null
+                    && inCallServiceConnectionForChildUser.isCarMode()) {
+                Log.i(this, "updateCarModeForConnections: car mode no longer "
+                        + "applicable for child user; disabling");
+                inCallServiceConnectionForChildUser.disableCarMode();
             }
         }
     }
@@ -2600,8 +2917,46 @@
 
     private UserHandle getUserFromCall(Call call) {
         // Call may never be specified, so we can fall back to using the CallManager current user.
-        return call == null
-                ? mCallsManager.getCurrentUserHandle()
-                : call.getUserHandleFromTargetPhoneAccount();
+        if (call == null) {
+            return mCallsManager.getCurrentUserHandle();
+        } else {
+            UserHandle userFromCall = call.getAssociatedUser();
+            UserManager userManager = mContext.getSystemService(UserManager.class);
+            // Emergency call should never be blocked, so if the user associated with the target
+            // phone account handle user is in quiet mode, use the current user for the ecall.
+            // Note, that this only applies to incoming calls that are received on assigned
+            // sims (i.e. work sim), where the associated user would be the target phone account
+            // handle user.
+            if ((call.isEmergencyCall() || call.isInECBM())
+                    && (userManager.isQuietModeEnabled(userFromCall)
+                    // We should also account for secondary/guest users where the profile may not
+                    // necessarily be paused.
+                    || !userManager.isUserAdmin(mCallsManager.getCurrentUserHandle()
+                    .getIdentifier()))) {
+                return mCallsManager.getCurrentUserHandle();
+            }
+            return userFromCall;
+        }
+    }
+
+    /**
+     * Useful for debugging purposes and called on the command line via
+     * an "adb shell telecom command".
+     *
+     * @return true if a particular non-ui InCallService package is bound in a call.
+     */
+    public boolean isNonUiInCallServiceBound(String packageName) {
+        for (NonUIInCallServiceConnectionCollection ics : mNonUIInCallServiceConnections.values()) {
+            for (InCallServiceBindingConnection connection : ics.getSubConnections()) {
+                InCallServiceInfo serviceInfo = connection.mInCallServiceInfo;
+                Log.i(this, "isNonUiInCallServiceBound: found serviceInfo=[%s]", serviceInfo);
+                if (serviceInfo != null &&
+                        serviceInfo.mComponentName.getPackageName().contains(packageName)) {
+                    Log.i(this, "isNonUiInCallServiceBound: found target package");
+                    return true;
+                }
+            }
+        }
+        return false;
     }
 }
diff --git a/src/com/android/server/telecom/InCallTonePlayer.java b/src/com/android/server/telecom/InCallTonePlayer.java
index 0957eb4..a5942f0 100644
--- a/src/com/android/server/telecom/InCallTonePlayer.java
+++ b/src/com/android/server/telecom/InCallTonePlayer.java
@@ -69,8 +69,8 @@
             mCallAudioManager = callAudioManager;
         }
 
-        public InCallTonePlayer createPlayer(int tone) {
-            return new InCallTonePlayer(tone, mCallAudioManager,
+        public InCallTonePlayer createPlayer(Call call, int tone) {
+            return new InCallTonePlayer(call, tone, mCallAudioManager,
                     mCallAudioRoutePeripheralAdapter, mLock, mToneGeneratorFactory,
                     mMediaPlayerFactory, mAudioManagerAdapter);
         }
@@ -170,7 +170,7 @@
 
     private static final int RELATIVE_VOLUME_EMERGENCY = 100;
     private static final int RELATIVE_VOLUME_HIPRI = 80;
-    private static final int RELATIVE_VOLUME_LOPRI = 50;
+    private static final int RELATIVE_VOLUME_LOPRI = 30;
     private static final int RELATIVE_VOLUME_UNDEFINED = -1;
 
     // Buffer time (in msec) to add on to the tone timeout value. Needed mainly when the timeout
@@ -212,6 +212,7 @@
     private Session mSession;
     private final Object mSessionLock = new Object();
 
+    private final Call mCall;
     private final ToneGeneratorFactory mToneGenerator;
     private final MediaPlayerFactory mMediaPlayerFactory;
     private final AudioManagerAdapter mAudioManagerAdapter;
@@ -228,6 +229,7 @@
      * @param toneId ID of the tone to play, see TONE_* constants.
      */
     private InCallTonePlayer(
+            Call call,
             int toneId,
             CallAudioManager callAudioManager,
             CallAudioRoutePeripheralAdapter callAudioRoutePeripheralAdapter,
@@ -235,6 +237,7 @@
             ToneGeneratorFactory toneGeneratorFactory,
             MediaPlayerFactory mediaPlayerFactor,
             AudioManagerAdapter audioManagerAdapter) {
+        mCall = call;
         mState = STATE_OFF;
         mToneId = toneId;
         mCallAudioManager = callAudioManager;
@@ -476,7 +479,7 @@
         }
 
         if (sTonesPlaying.incrementAndGet() == 1) {
-            mCallAudioManager.setIsTonePlaying(true);
+            mCallAudioManager.setIsTonePlaying(mCall, true);
         }
 
         synchronized (mSessionLock) {
@@ -524,7 +527,7 @@
                     Log.i(InCallTonePlayer.this,
                             "cleanUpTonePlayer(): tonesPlaying=%d, tone completed", newToneCount);
                     if (mCallAudioManager != null) {
-                        mCallAudioManager.setIsTonePlaying(false);
+                        mCallAudioManager.setIsTonePlaying(mCall, false);
                     } else {
                         Log.w(InCallTonePlayer.this,
                                 "cleanUpTonePlayer(): mCallAudioManager is null!");
diff --git a/src/com/android/server/telecom/LogUtils.java b/src/com/android/server/telecom/LogUtils.java
index 35f8d1e..0d6acd5 100644
--- a/src/com/android/server/telecom/LogUtils.java
+++ b/src/com/android/server/telecom/LogUtils.java
@@ -105,10 +105,17 @@
         public static final String SET_RINGING = "SET_RINGING";
         public static final String SET_ANSWERED = "SET_ANSWERED";
         public static final String SET_DISCONNECTED = "SET_DISCONNECTED";
+        public static final String SKIP_CALL_LOG = "SKIP_CALL_LOG";
+        public static final String LOG_CALL = "LOG_CALL";
         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_RTT = "REQUEST_RTT";
+        public static final String RESPOND_TO_RTT_REQUEST = "RESPOND_TO_RTT_REQUEST";
+        public static final String SET_RRT_MODE = "SET_RTT_MODE";
+        public static final String ON_RTT_FAILED = "ON_RTT_FAILED";
+        public static final String ON_RTT_REQUEST = "ON_RTT_REQUEST";
         public static final String REQUEST_HOLD = "REQUEST_HOLD";
         public static final String REQUEST_UNHOLD = "REQUEST_UNHOLD";
         public static final String REQUEST_DISCONNECT = "REQUEST_DISCONNECT";
@@ -216,6 +223,11 @@
         public static final String ICS_EXTRAS_CHANGED = "ICS_EXTRAS_CHANGED";
         public static final String FLASH_NOTIFICATION_START = "FLASH_NOTIFICATION_START";
         public static final String FLASH_NOTIFICATION_STOP = "FLASH_NOTIFICATION_STOP";
+        public static final String GAINED_FGS_DELEGATION = "GAINED_FGS_DELEGATION";
+        public static final String GAIN_FGS_DELEGATION_FAILED = "GAIN_FGS_DELEGATION_FAILED";
+        public static final String LOST_FGS_DELEGATION = "LOST_FGS_DELEGATION";
+        public static final String START_STREAMING = "START_STREAMING";
+        public static final String STOP_STREAMING = "STOP_STREAMING";
 
         public static class Timings {
             public static final String ACCEPT_TIMING = "accept";
diff --git a/src/com/android/server/telecom/MissedCallNotifier.java b/src/com/android/server/telecom/MissedCallNotifier.java
index 0e5a287..b0a7c8e 100644
--- a/src/com/android/server/telecom/MissedCallNotifier.java
+++ b/src/com/android/server/telecom/MissedCallNotifier.java
@@ -16,6 +16,7 @@
 
 package com.android.server.telecom;
 
+import android.annotation.Nullable;
 import android.net.Uri;
 import android.os.UserHandle;
 import android.telecom.PhoneAccountHandle;
@@ -85,7 +86,7 @@
 
     void clearMissedCalls(UserHandle userHandle);
 
-    void showMissedCallNotification(CallInfo call);
+    void showMissedCallNotification(CallInfo call, @Nullable Uri uri);
 
     void reloadAfterBootComplete(CallerInfoLookupHelper callerInfoLookupHelper,
             CallInfoFactory callInfoFactory);
diff --git a/src/com/android/server/telecom/MmiUtils.java b/src/com/android/server/telecom/MmiUtils.java
new file mode 100644
index 0000000..11f6d59
--- /dev/null
+++ b/src/com/android/server/telecom/MmiUtils.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.telecom;
+
+import android.net.Uri;
+import android.telecom.PhoneAccount;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class MmiUtils {
+    // See TS 22.030 6.5.2 "Structure of the MMI"
+
+    private static Pattern sPatternSuppService = Pattern.compile(
+            "((\\*|#|\\*#|\\*\\*|##)(\\d{2,3})(\\*([^*#]*)(\\*([^*#]*)(\\*([^*#]*)(\\*([^*#]*))?)?)?)?#)(.*)");
+        /*       1  2                    3          4  5       6   7         8    9     10  11
+              12
+
+                 1 = Full string up to and including #
+                 2 = action (activation/interrogation/registration/erasure)
+                 3 = service code
+                 5 = SIA
+                 7 = SIB
+                 9 = SIC
+                 10 = dialing number
+        */
+    //regex groups
+    static final int MATCH_GROUP_POUND_STRING = 1;
+    static final int MATCH_GROUP_ACTION = 2; //(activation/interrogation/registration/erasure)
+    static final int MATCH_GROUP_SERVICE_CODE = 3;
+    static final int MATCH_GROUP_SIA = 5;
+    static final int MATCH_GROUP_SIB = 7;
+    static final int MATCH_GROUP_SIC = 9;
+    static final int MATCH_GROUP_PWD_CONFIRM = 11;
+    static final int MATCH_GROUP_DIALING_NUMBER = 12;
+    // Call Forwarding service codes
+    static final String SC_CFU = "21";
+    static final String SC_CFB = "67";
+    static final String SC_CFNRy = "61";
+    static final String SC_CFNR = "62";
+    static final String SC_CF_All = "002";
+    static final String SC_CF_All_Conditional = "004";
+
+    //see: https://nationalnanpa.com/number_resource_info/vsc_assignments.html
+    @SuppressWarnings("DoubleBraceInitialization")
+    private static Set<String> sDangerousVerticalServiceCodes = new HashSet<String>()
+    {{
+        add("*09"); //Selective Call Blocking/Reporting
+        add("*42"); //Change Forward-To Number for Cust Programmable Call Forwarding Don't Answer
+        add("*56"); //Change Forward-To Number for ISDN Call Forwarding
+        add("*60"); //Selective Call Rejection Activation
+        add("*63"); //Selective Call Forwarding Activation
+        add("*64"); //Selective Call Acceptance Activation
+        add("*68"); //Call Forwarding Busy Line/Don't Answer Activation
+        add("*72"); //Call Forwarding Activation
+        add("*77"); //Anonymous Call Rejection Activation
+        add("*78"); //Do Not Disturb Activation
+    }};
+    private final int mMinLenInDangerousSet;
+    private final int mMaxLenInDangerousSet;
+
+    public MmiUtils() {
+        mMinLenInDangerousSet = sDangerousVerticalServiceCodes.stream()
+                .mapToInt(String::length)
+                .min()
+                .getAsInt();
+        mMaxLenInDangerousSet = sDangerousVerticalServiceCodes.stream()
+                .mapToInt(String::length)
+                .max()
+                .getAsInt();
+    }
+
+    /**
+     * Determines if the Uri represents a call forwarding related mmi code
+     *
+     * @param handle The URI to call.
+     * @return {@code True} if the URI represents a call forwarding related MMI
+     */
+    private static boolean isCallForwardingMmiCode(Uri handle) {
+        Matcher m;
+        String dialString = handle.getSchemeSpecificPart();
+        m = sPatternSuppService.matcher(dialString);
+
+        if (m.matches()) {
+            String sc = m.group(MATCH_GROUP_SERVICE_CODE);
+            return sc != null &&
+                    (sc.equals(SC_CFU)
+                            || sc.equals(SC_CFB) || sc.equals(SC_CFNRy)
+                            || sc.equals(SC_CFNR) || sc.equals(SC_CF_All)
+                            || sc.equals(SC_CF_All_Conditional));
+        }
+
+        return false;
+
+    }
+
+    private static boolean isTelScheme(Uri handle) {
+        return (handle != null && handle.getSchemeSpecificPart() != null &&
+                handle.getScheme() != null &&
+                handle.getScheme().equals(PhoneAccount.SCHEME_TEL));
+    }
+
+    private boolean isDangerousVerticalServiceCode(Uri handle) {
+        if (isTelScheme(handle)) {
+            String dialedNumber = handle.getSchemeSpecificPart();
+            if (dialedNumber.length() >= mMinLenInDangerousSet && dialedNumber.charAt(0) == '*') {
+                //we only check vertical codes defined by The North American Numbering Plan Admin
+                //see: https://nationalnanpa.com/number_resource_info/vsc_assignments.html
+                //only two or 3-digit codes are valid as of today, but the code is generic enough.
+                for (int prefixLen = mMaxLenInDangerousSet; prefixLen <= mMaxLenInDangerousSet;
+                        prefixLen++) {
+                    String prefix = dialedNumber.substring(0, prefixLen);
+                    if (sDangerousVerticalServiceCodes.contains(prefix)) {
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Determines if a dialed number is potentially an In-Call MMI code.  In-Call MMI codes are
+     * MMI codes which can be dialed when one or more calls are in progress.
+     * <P>
+     * Checks for numbers formatted similar to the MMI codes defined in:
+     * {@link com.android.internal.telephony.Phone#handleInCallMmiCommands(String)}
+     *
+     * @param handle The URI to call.
+     * @return {@code True} if the URI represents a number which could be an in-call MMI code.
+     */
+    public boolean isPotentialInCallMMICode(Uri handle) {
+        if (isTelScheme(handle)) {
+            String dialedNumber = handle.getSchemeSpecificPart();
+            return (dialedNumber.equals("0") ||
+                    (dialedNumber.startsWith("1") && dialedNumber.length() <= 2) ||
+                    (dialedNumber.startsWith("2") && dialedNumber.length() <= 2) ||
+                    dialedNumber.equals("3") ||
+                    dialedNumber.equals("4") ||
+                    dialedNumber.equals("5"));
+        }
+        return false;
+    }
+
+    public boolean isPotentialMMICode(Uri handle) {
+        return (handle != null && handle.getSchemeSpecificPart() != null
+                && handle.getSchemeSpecificPart().contains("#"));
+    }
+
+    /**
+     * Determines if the Uri represents a dangerous MMI code or Vertical Service code. Dangerous
+     * codes are ones, for which,
+     * we normally expect the user to be aware that an application has dialed them
+     *
+     * @param handle The URI to call.
+     * @return {@code True} if the URI represents a dangerous code
+     */
+    public boolean isDangerousMmiOrVerticalCode(Uri handle) {
+        if (isPotentialMMICode(handle)) {
+            return isCallForwardingMmiCode(handle);
+            //since some dangerous mmi codes could be carrier specific, in the future,
+            //we can add a carrier config item which can list carrier specific dangerous mmi codes
+        } else if (isDangerousVerticalServiceCode(handle)) {
+            return true;
+        }
+        return false;
+    }
+}
diff --git a/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java b/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
index 41aa2fb..6070baa 100644
--- a/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
+++ b/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
@@ -16,14 +16,13 @@
 
 package com.android.server.telecom;
 
-import android.app.AppOpsManager;
-
+import android.Manifest;
 import android.app.Activity;
+import android.app.AppOpsManager;
 import android.app.BroadcastOptions;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
-import android.content.res.Resources;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Trace;
@@ -39,6 +38,7 @@
 import android.text.TextUtils;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.telecom.flags.FeatureFlags;
 import com.android.server.telecom.callredirection.CallRedirectionProcessor;
 
 // TODO: Needed for move to system service: import com.android.internal.R;
@@ -78,6 +78,8 @@
     private final PhoneNumberUtilsAdapter mPhoneNumberUtilsAdapter;
     private final TelecomSystem.SyncRoot mLock;
     private final DefaultDialerCache mDefaultDialerCache;
+    private final MmiUtils mMmiUtils;
+    private final FeatureFlags mFeatureFlags;
 
     /*
      * Whether or not the outgoing call intent originated from the default phone application. If
@@ -101,7 +103,8 @@
     @VisibleForTesting
     public NewOutgoingCallIntentBroadcaster(Context context, CallsManager callsManager,
             Intent intent, PhoneNumberUtilsAdapter phoneNumberUtilsAdapter,
-            boolean isDefaultPhoneApp, DefaultDialerCache defaultDialerCache) {
+            boolean isDefaultPhoneApp, DefaultDialerCache defaultDialerCache, MmiUtils mmiUtils,
+            FeatureFlags featureFlags) {
         mContext = context;
         mCallsManager = callsManager;
         mIntent = intent;
@@ -109,6 +112,8 @@
         mIsDefaultOrSystemPhoneApp = isDefaultPhoneApp;
         mLock = mCallsManager.getLock();
         mDefaultDialerCache = defaultDialerCache;
+        mMmiUtils = mmiUtils;
+        mFeatureFlags = featureFlags;
     }
 
     /**
@@ -128,7 +133,8 @@
                     // Once the NEW_OUTGOING_CALL broadcast is finished, the resultData is
                     // used as the actual number to call. (If null, no call will be placed.)
                     String resultNumber = getResultData();
-                    Log.i(this, "Received new-outgoing-call-broadcast for %s with data %s", mCall,
+                    Log.i(NewOutgoingCallIntentBroadcaster.this,
+                            "Received new-outgoing-call-broadcast for %s with data %s", mCall,
                             Log.pii(resultNumber));
 
                     boolean endEarly = false;
@@ -291,6 +297,16 @@
                     result.callImmediately = true;
                     result.requestRedirection = false;
                 }
+            } else if (mMmiUtils.isDangerousMmiOrVerticalCode(intent.getData())) {
+                if (!mIsDefaultOrSystemPhoneApp) {
+                    Log.w(this,
+                            "Potentially dangerous MMI code %s with CALL Intent %s can only be "
+                                    + "sent if caller is the system or default dialer",
+                            number, intent);
+                    launchSystemDialer(intent.getData());
+                    result.disconnectCause = DisconnectCause.OUTGOING_CANCELED;
+                    return result;
+                }
             }
         } else if (Intent.ACTION_CALL_EMERGENCY.equals(action)) {
             if (!isEmergencyNumber) {
@@ -310,6 +326,7 @@
         String scheme = mPhoneNumberUtilsAdapter.isUriNumber(number)
                 ? PhoneAccount.SCHEME_SIP : PhoneAccount.SCHEME_TEL;
         result.callingAddress = Uri.fromParts(scheme, number, null);
+
         return result;
     }
 
@@ -341,14 +358,57 @@
 
     public void processCall(Call call, CallDisposition disposition) {
         mCall = call;
+
+        // If the new outgoing call broadast doesn't block, trigger the legacy process call
+        // behavior and exit out here.
+        if (!mFeatureFlags.isNewOutgoingCallBroadcastUnblocking()) {
+            legacyProcessCall(disposition);
+            return;
+        }
+        boolean callRedirectionWithService = false;
+        // Only try to do redirection if it was requested and we're not calling immediately.
+        // We can expect callImmediately to be true for emergency calls and voip calls.
+        if (disposition.requestRedirection && !disposition.callImmediately) {
+            CallRedirectionProcessor callRedirectionProcessor = new CallRedirectionProcessor(
+                    mContext, mCallsManager, mCall, disposition.callingAddress,
+                    mCallsManager.getPhoneAccountRegistrar(),
+                    getGateWayInfoFromIntent(mIntent, mIntent.getData()),
+                    mIntent.getBooleanExtra(TelecomManager.EXTRA_START_CALL_WITH_SPEAKERPHONE,
+                            false),
+                    mIntent.getIntExtra(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
+                            VideoProfile.STATE_AUDIO_ONLY));
+            /**
+             * If there is an available {@link android.telecom.CallRedirectionService}, use the
+             * {@link CallRedirectionProcessor} to perform call redirection instead of using
+             * broadcasting.
+             */
+            callRedirectionWithService = callRedirectionProcessor
+                    .canMakeCallRedirectionWithServiceAsUser(mCall.getAssociatedUser());
+            if (callRedirectionWithService) {
+                callRedirectionProcessor.performCallRedirection(mCall.getAssociatedUser());
+            }
+        }
+
+        // If no redirection was kicked off, place the call now.
+        if (!callRedirectionWithService) {
+            callImmediately(disposition);
+        }
+
+        // Finally, send the non-blocking broadcast if we're supposed to (ie for any non-voip call).
+        if (disposition.sendBroadcast) {
+            UserHandle targetUser = mCall.getAssociatedUser();
+            broadcastIntent(mIntent, disposition.number, false /* receiverRequired */, targetUser);
+        }
+    }
+
+    /**
+     * The legacy non-flagged version of processing a call.  Although there is some code duplication
+     * if makes the new flow cleaner to read.
+     * @param disposition
+     */
+    private void legacyProcessCall(CallDisposition disposition) {
         if (disposition.callImmediately) {
-            boolean speakerphoneOn = mIntent.getBooleanExtra(
-                    TelecomManager.EXTRA_START_CALL_WITH_SPEAKERPHONE, false);
-            int videoState = mIntent.getIntExtra(
-                    TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
-                    VideoProfile.STATE_AUDIO_ONLY);
-            placeOutgoingCallImmediately(mCall, disposition.callingAddress, null,
-                    speakerphoneOn, videoState);
+            callImmediately(disposition);
 
             // Don't return but instead continue and send the ACTION_NEW_OUTGOING_CALL broadcast
             // so that third parties can still inspect (but not intercept) the outgoing call. When
@@ -372,21 +432,34 @@
              * broadcasting.
              */
             callRedirectionWithService = callRedirectionProcessor
-                    .canMakeCallRedirectionWithServiceAsUser(mCall.getInitiatingUser());
+                    .canMakeCallRedirectionWithServiceAsUser(mCall.getAssociatedUser());
             if (callRedirectionWithService) {
-                callRedirectionProcessor.performCallRedirection(mCall.getInitiatingUser());
+                callRedirectionProcessor.performCallRedirection(mCall.getAssociatedUser());
             }
         }
 
         if (disposition.sendBroadcast) {
-            UserHandle targetUser = mCall.getInitiatingUser();
-            Log.i(this, "Sending NewOutgoingCallBroadcast for %s to %s", mCall, targetUser);
+            UserHandle targetUser = mCall.getAssociatedUser();
             broadcastIntent(mIntent, disposition.number,
                     !disposition.callImmediately && !callRedirectionWithService, targetUser);
         }
     }
 
     /**
+     * Place a call immediately.
+     * @param disposition The disposition; used for retrieving the address of the call.
+     */
+    private void callImmediately(CallDisposition disposition) {
+        boolean speakerphoneOn = mIntent.getBooleanExtra(
+                TelecomManager.EXTRA_START_CALL_WITH_SPEAKERPHONE, false);
+        int videoState = mIntent.getIntExtra(
+                TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
+                VideoProfile.STATE_AUDIO_ONLY);
+        placeOutgoingCallImmediately(mCall, disposition.callingAddress, null,
+                speakerphoneOn, videoState);
+    }
+
+    /**
      * Sends a new outgoing call ordered broadcast so that third party apps can cancel the
      * placement of the call or redirect it to a different number.
      *
@@ -405,28 +478,51 @@
         if (number != null) {
             broadcastIntent.putExtra(Intent.EXTRA_PHONE_NUMBER, number);
         }
-
-        // Force receivers of this broadcast intent to run at foreground priority because we
-        // want to finish processing the broadcast intent as soon as possible.
-        broadcastIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND
-                | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
         Log.v(this, "Broadcasting intent: %s.", broadcastIntent);
 
         checkAndCopyProviderExtras(originalCallIntent, broadcastIntent);
 
-        final BroadcastOptions options = BroadcastOptions.makeBasic();
-        options.setBackgroundActivityStartsAllowed(true);
-        mContext.sendOrderedBroadcastAsUser(
-                broadcastIntent,
-                targetUser,
-                android.Manifest.permission.PROCESS_OUTGOING_CALLS,
-                AppOpsManager.OP_PROCESS_OUTGOING_CALLS,
-                options.toBundle(),
-                receiverRequired ? new NewOutgoingCallBroadcastIntentReceiver() : null,
-                null,  // scheduler
-                Activity.RESULT_OK,  // initialCode
-                number,  // initialData: initial value for the result data (number to be modified)
-                null);  // initialExtras
+        if (mFeatureFlags.isNewOutgoingCallBroadcastUnblocking()) {
+            // Where the new outgoing call broadcast is unblocking, do not give receiver FG priority
+            // and do not allow background activity starts.
+            broadcastIntent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+            Log.i(this, "broadcastIntent: Sending non-blocking for %s to %s", mCall.getId(),
+                    targetUser);
+            if (mFeatureFlags.telecomResolveHiddenDependencies()) {
+                mContext.sendBroadcastAsUser(
+                        broadcastIntent,
+                        targetUser,
+                        Manifest.permission.PROCESS_OUTGOING_CALLS);
+            } else {
+                mContext.sendBroadcastAsUser(
+                        broadcastIntent,
+                        targetUser,
+                        android.Manifest.permission.PROCESS_OUTGOING_CALLS,
+                        AppOpsManager.OP_PROCESS_OUTGOING_CALLS);  // initialExtras
+            }
+        } else {
+            Log.i(this, "broadcastIntent: Sending ordered for %s to %s, waitForResult=%b",
+                    mCall.getId(), targetUser, receiverRequired);
+            final BroadcastOptions options = BroadcastOptions.makeBasic();
+            options.setBackgroundActivityStartsAllowed(true);
+            // Force receivers of this broadcast intent to run at foreground priority because we
+            // want to finish processing the broadcast intent as soon as possible.
+            broadcastIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND
+                    | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+
+            mContext.sendOrderedBroadcastAsUser(
+                    broadcastIntent,
+                    targetUser,
+                    android.Manifest.permission.PROCESS_OUTGOING_CALLS,
+                    AppOpsManager.OP_PROCESS_OUTGOING_CALLS,
+                    options.toBundle(),
+                    receiverRequired ? new NewOutgoingCallBroadcastIntentReceiver() : null,
+                    null,  // scheduler
+                    Activity.RESULT_OK,  // initialCode
+                    number,  // initialData: initial value for the result data (number to be
+                             // modified)
+                    null);  // initialExtras
+        }
     }
 
     /**
diff --git a/src/com/android/server/telecom/ParcelableCallUtils.java b/src/com/android/server/telecom/ParcelableCallUtils.java
index 673b99a..c77e605 100644
--- a/src/com/android/server/telecom/ParcelableCallUtils.java
+++ b/src/com/android/server/telecom/ParcelableCallUtils.java
@@ -158,6 +158,10 @@
             properties |= android.telecom.Call.Details.PROPERTY_VOIP_AUDIO_MODE;
         }
 
+        if (call.isTransactionalCall()) {
+            properties |= android.telecom.Call.Details.PROPERTY_IS_TRANSACTIONAL;
+        }
+
         // If this is a single-SIM device, the "default SIM" will always be the only SIM.
         boolean isDefaultSmsAccount = phoneAccountRegistrar != null &&
                 phoneAccountRegistrar.isUserSelectedSmsPhoneAccount(call.getTargetPhoneAccount());
diff --git a/src/com/android/server/telecom/PhoneAccountRegistrar.java b/src/com/android/server/telecom/PhoneAccountRegistrar.java
index 3c6934a..5f23e4d 100644
--- a/src/com/android/server/telecom/PhoneAccountRegistrar.java
+++ b/src/com/android/server/telecom/PhoneAccountRegistrar.java
@@ -61,6 +61,7 @@
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.XmlUtils;
 import com.android.modules.utils.ModifiedUtf8;
+import com.android.server.telecom.flags.Flags;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -205,6 +206,7 @@
 
         // register context based receiver to clean up orphan phone accounts
         IntentFilter intentFilter = new IntentFilter(Intent.ACTION_MANAGED_PROFILE_REMOVED);
+        intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
         mContext.registerReceiver(mManagedProfileReceiver, intentFilter);
 
         read();
@@ -388,26 +390,62 @@
                             account.getGroupId()));
         }
 
-        // Potentially update the default voice subid in SubscriptionManager.
+        // Potentially update the default voice subid in SubscriptionManager so that Telephony and
+        // Telecom are in sync.
         int newSubId = accountHandle == null ? SubscriptionManager.INVALID_SUBSCRIPTION_ID :
                 getSubscriptionIdForPhoneAccount(accountHandle);
-        if (isSimAccount || accountHandle == null) {
-            int currentVoiceSubId = mSubscriptionManager.getDefaultVoiceSubscriptionId();
-            if (newSubId != currentVoiceSubId) {
-                Log.i(this, "setUserSelectedOutgoingPhoneAccount: update voice sub; "
-                        + "account=%s, subId=%d", accountHandle, newSubId);
-                mSubscriptionManager.setDefaultVoiceSubscriptionId(newSubId);
+        if (Flags.onlyUpdateTelephonyOnValidSubIds()) {
+            if (shouldUpdateTelephonyDefaultVoiceSubId(accountHandle, isSimAccount, newSubId)) {
+                updateDefaultVoiceSubId(newSubId, accountHandle);
             } else {
-                Log.i(this, "setUserSelectedOutgoingPhoneAccount: no change to voice sub");
+                Log.i(this, "setUserSelectedOutgoingPhoneAccount: %s is not a sub", accountHandle);
             }
         } else {
-            Log.i(this, "setUserSelectedOutgoingPhoneAccount: %s is not a sub", accountHandle);
+            if (isSimAccount || accountHandle == null) {
+                updateDefaultVoiceSubId(newSubId, accountHandle);
+            } else {
+                Log.i(this, "setUserSelectedOutgoingPhoneAccount: %s is not a sub", accountHandle);
+            }
         }
-
         write();
         fireDefaultOutgoingChanged();
     }
 
+    private void updateDefaultVoiceSubId(int newSubId, PhoneAccountHandle accountHandle){
+        int currentVoiceSubId = mSubscriptionManager.getDefaultVoiceSubscriptionId();
+        if (newSubId != currentVoiceSubId) {
+            Log.i(this, "setUserSelectedOutgoingPhoneAccount: update voice sub; "
+                    + "account=%s, subId=%d", accountHandle, newSubId);
+            mSubscriptionManager.setDefaultVoiceSubscriptionId(newSubId);
+        } else {
+            Log.i(this, "setUserSelectedOutgoingPhoneAccount: no change to voice sub");
+        }
+    }
+
+    // This helper is important for CTS testing.  [PhoneAccount]s created by Telecom in CTS are
+    // assigned a  subId value of INVALID_SUBSCRIPTION_ID (-1) by Telephony.  However, when
+    // Telephony has a default outgoing calling voice account of -1, that translates to no default
+    // account (user should be prompted to select an acct when making MOs).  In order to avoid
+    // Telephony clearing out the newly changed default [PhoneAccount] in Telecom, Telephony should
+    // not be updated. This situation will never occur in production since [PhoneAccount]s in
+    // production are assigned non-negative subId values.
+    private boolean shouldUpdateTelephonyDefaultVoiceSubId(PhoneAccountHandle phoneAccountHandle,
+            boolean isSimAccount, int newSubId) {
+        // user requests no call preference
+        if (phoneAccountHandle == null) {
+            return true;
+        }
+        // do not update Telephony if the newSubId is invalid
+        if (newSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+            Log.w(this, "shouldUpdateTelephonyDefaultVoiceSubId: "
+                            + "invalid subId scenario, not updating Telephony. "
+                            + "phoneAccountHandle=[%s], isSimAccount=[%b], newSubId=[%s]",
+                    phoneAccountHandle, isSimAccount, newSubId);
+            return false;
+        }
+        return isSimAccount;
+    }
+
     boolean isUserSelectedSmsPhoneAccount(PhoneAccountHandle accountHandle) {
         return getSubscriptionIdForPhoneAccount(accountHandle) ==
                 SubscriptionManager.getDefaultSmsSubscriptionId();
@@ -562,10 +600,7 @@
         if (call == null) {
             return null;
         }
-        UserHandle userHandle = call.getInitiatingUser();
-        if (userHandle == null) {
-            userHandle = call.getTargetPhoneAccount().getUserHandle();
-        }
+        UserHandle userHandle = call.getAssociatedUser();
         PhoneAccountHandle targetPhoneAccount = call.getTargetPhoneAccount();
         Log.d(this, "getSimCallManagerFromCall: callId=%s, targetPhac=%s",
                 call.getId(), targetPhoneAccount);
@@ -873,13 +908,17 @@
     public void registerPhoneAccount(PhoneAccount account) {
         // Enforce the requirement that a connection service for a phone account has the correct
         // permission.
-        if (!hasTransactionalCallCapabilites(account) &&
+        if (!hasTransactionalCallCapabilities(account) &&
                 !phoneAccountRequiresBindPermission(account.getAccountHandle())) {
             Log.w(this,
                     "Phone account %s does not have BIND_TELECOM_CONNECTION_SERVICE permission.",
                     account.getAccountHandle());
-            throw new SecurityException("PhoneAccount connection service requires "
-                    + "BIND_TELECOM_CONNECTION_SERVICE permission.");
+            throw new SecurityException("Registering a PhoneAccount requires either: "
+                    + "(1) The Service definition requires that the ConnectionService is guarded"
+                    + " with the BIND_TELECOM_CONNECTION_SERVICE, which can be defined using the"
+                    + " android:permission tag as part of the Service definition. "
+                    + "(2) The PhoneAccount capability called"
+                    + " CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS.");
         }
         enforceCharacterLimit(account);
         enforceIconSizeLimit(account);
@@ -895,13 +934,15 @@
      * @throws IllegalArgumentException if MAX_PHONE_ACCOUNT_REGISTRATIONS are reached
      */
     private void enforceMaxPhoneAccountLimit(@NonNull PhoneAccount account) {
-        final PhoneAccountHandle accountHandle = account.getAccountHandle();
-        final UserHandle user = accountHandle.getUserHandle();
-        final ComponentName componentName = accountHandle.getComponentName();
-
-        if (getPhoneAccountHandles(0, null, componentName.getPackageName(),
-                true /* includeDisabled */, user, false /* crossUserAccess */).size()
-                >= MAX_PHONE_ACCOUNT_REGISTRATIONS) {
+        List<PhoneAccount> unverifiedAccounts = getAccountsForPackage_BypassResolveComp(
+                account.getAccountHandle().getComponentName().getPackageName(),
+                account.getAccountHandle().getUserHandle());
+        // verify each phone account is backed by a valid ConnectionService. If the
+        // ConnectionService has been disabled or cannot be resolved, unregister the accounts.
+        List<PhoneAccount> verifiedAccounts =
+                cleanupUnresolvableConnectionServiceAccounts(unverifiedAccounts);
+        // enforce the max phone account limit for the application registering accounts
+        if (verifiedAccounts.size() >= MAX_PHONE_ACCOUNT_REGISTRATIONS) {
             EventLog.writeEvent(0x534e4554, "259064622", Binder.getCallingUid(),
                     "enforceMaxPhoneAccountLimit");
             throw new IllegalArgumentException(
@@ -1062,7 +1103,7 @@
         boolean isNewAccount;
 
         // add self-managed capability for transactional accounts that are missing it
-        if (hasTransactionalCallCapabilites(account) &&
+        if (hasTransactionalCallCapabilities(account) &&
                 !account.hasCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)) {
             account = account.toBuilder()
                     .setCapabilities(account.getCapabilities()
@@ -1373,10 +1414,6 @@
             return false;
         }
 
-        if (hasTransactionalCallCapabilites(getPhoneAccountUnchecked(phoneAccountHandle))) {
-            return false;
-        }
-
         for (ResolveInfo resolveInfo : resolveInfos) {
             ServiceInfo serviceInfo = resolveInfo.serviceInfo;
             if (serviceInfo == null) {
@@ -1396,7 +1433,7 @@
     }
 
     @VisibleForTesting
-    public boolean hasTransactionalCallCapabilites(PhoneAccount phoneAccount) {
+    public boolean hasTransactionalCallCapabilities(PhoneAccount phoneAccount) {
         if (phoneAccount == null) {
             return false;
         }
@@ -1530,7 +1567,10 @@
             }
             PhoneAccountHandle handle = m.getAccountHandle();
 
-            if (resolveComponent(handle).isEmpty()) {
+            // PhoneAccounts with CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS do not require a
+            // ConnectionService and will fail [resolveComponent(PhoneAccountHandle)]. Bypass
+            // the [resolveComponent(PhoneAccountHandle)] for transactional accounts.
+            if (!hasTransactionalCallCapabilities(m) && resolveComponent(handle).isEmpty()) {
                 // This component cannot be resolved anymore; skip this one.
                 continue;
             }
@@ -1549,6 +1589,51 @@
     }
 
     /**
+     * This getter should be used when you want to bypass the {@link
+     * PhoneAccountRegistrar#resolveComponent(PhoneAccountHandle)} check when fetching accounts
+     */
+    @VisibleForTesting
+    public List<PhoneAccount> getAccountsForPackage_BypassResolveComp(String packageName,
+            UserHandle userHandle) {
+        List<PhoneAccount> accounts = new ArrayList<>(mState.accounts.size());
+        for (PhoneAccount m : mState.accounts) {
+            PhoneAccountHandle handle = m.getAccountHandle();
+
+            if (packageName != null && !packageName.equals(
+                    handle.getComponentName().getPackageName())) {
+                // Not the right package name; skip this one.
+                continue;
+            }
+
+            if (!isVisibleForUser(m, userHandle, false)) {
+                // Account is not visible for the current user; skip this one.
+                continue;
+            }
+            accounts.add(m);
+        }
+        return accounts;
+    }
+
+    @VisibleForTesting
+    public List<PhoneAccount> cleanupUnresolvableConnectionServiceAccounts(
+            List<PhoneAccount> accounts) {
+        ArrayList<PhoneAccount> verifiedAccounts = new ArrayList<>();
+        for (PhoneAccount account : accounts) {
+            PhoneAccountHandle handle = account.getAccountHandle();
+            // if the ConnectionService has been disabled or can longer be found, remove the handle
+            if (resolveComponent(handle).isEmpty()) {
+                Log.i(this,
+                        "Cannot resolve the ConnectionService for handle=[%s]; unregistering"
+                                + " account", handle);
+                unregisterPhoneAccount(handle);
+            } else {
+                verifiedAccounts.add(account);
+            }
+        }
+        return verifiedAccounts;
+    }
+
+    /**
      * Clean up the orphan {@code PhoneAccount}. An orphan {@code PhoneAccount} is a phone
      * account that does not have a {@code UserHandle} or belongs to a deleted package.
      *
@@ -1661,6 +1746,7 @@
             } else {
                 pw.println(defaultOutgoing);
             }
+            pw.println("defaultVoiceSubId: " + SubscriptionManager.getDefaultVoiceSubscriptionId());
             pw.println("simCallManager: " + getSimCallManager(mCurrentUserHandle));
             pw.println("phoneAccounts:");
             pw.increaseIndent();
diff --git a/src/com/android/server/telecom/RespondViaSmsManager.java b/src/com/android/server/telecom/RespondViaSmsManager.java
index 8507703..1d42db4 100644
--- a/src/com/android/server/telecom/RespondViaSmsManager.java
+++ b/src/com/android/server/telecom/RespondViaSmsManager.java
@@ -215,8 +215,9 @@
             MessageSentReceiver receiver = new MessageSentReceiver(
                     !TextUtils.isEmpty(contactName) ? contactName : phoneNumber,
                     messageParts.size());
-            context.registerReceiver(receiver, new IntentFilter(ACTION_MESSAGE_SENT),
-                    Context.RECEIVER_NOT_EXPORTED);
+            IntentFilter messageSentFilter = new IntentFilter(ACTION_MESSAGE_SENT);
+            messageSentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
+            context.registerReceiver(receiver, messageSentFilter, Context.RECEIVER_NOT_EXPORTED);
             smsManager.sendMultipartTextMessage(phoneNumber, null, messageParts,
                     sentIntents/*sentIntent*/, null /*deliveryIntent*/, context.getOpPackageName(),
                     context.getAttributionTag());
diff --git a/src/com/android/server/telecom/RingbackPlayer.java b/src/com/android/server/telecom/RingbackPlayer.java
index a8af3ac..5ace9ba 100644
--- a/src/com/android/server/telecom/RingbackPlayer.java
+++ b/src/com/android/server/telecom/RingbackPlayer.java
@@ -19,6 +19,7 @@
 import static com.android.server.telecom.LogUtils.Events.START_RINBACK;
 import static com.android.server.telecom.LogUtils.Events.STOP_RINGBACK;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
 import android.telecom.Log;
 
@@ -42,8 +43,12 @@
      */
     private InCallTonePlayer mTonePlayer;
 
-    RingbackPlayer(InCallTonePlayer.Factory playerFactory) {
+    private final Object mLock;
+
+    @VisibleForTesting
+    public RingbackPlayer(InCallTonePlayer.Factory playerFactory) {
         mPlayerFactory = playerFactory;
+        mLock = new Object();
     }
 
     /**
@@ -52,25 +57,27 @@
      * @param call The call for which to ringback.
      */
     public void startRingbackForCall(Call call) {
-        Preconditions.checkState(call.getState() == CallState.DIALING);
+        synchronized (mLock) {
+            Preconditions.checkState(call.getState() == CallState.DIALING);
 
-        if (mCall == call) {
-            Log.w(this, "Ignoring duplicate requests to ring for %s.", call);
-            return;
-        }
+            if (mCall == call) {
+                Log.w(this, "Ignoring duplicate requests to ring for %s.", call);
+                return;
+            }
 
-        if (mCall != null) {
-            // We only get here for the foreground call so, there's no reason why there should
-            // exist a current dialing call.
-            Log.wtf(this, "Ringback player thinks there are two foreground-dialing calls.");
-        }
+            if (mCall != null) {
+                // We only get here for the foreground call so, there's no reason why there should
+                // exist a current dialing call.
+                Log.wtf(this, "Ringback player thinks there are two foreground-dialing calls.");
+            }
 
-        mCall = call;
-        if (mTonePlayer == null) {
-            Log.i(this, "Playing the ringback tone for %s.", call);
-            Log.addEvent(call, START_RINBACK);
-            mTonePlayer = mPlayerFactory.createPlayer(InCallTonePlayer.TONE_RING_BACK);
-            mTonePlayer.startTone();
+            mCall = call;
+            if (mTonePlayer == null) {
+                Log.i(this, "Playing the ringback tone for %s.", call);
+                Log.addEvent(call, START_RINBACK);
+                mTonePlayer = mPlayerFactory.createPlayer(call, InCallTonePlayer.TONE_RING_BACK);
+                mTonePlayer.startTone();
+            }
         }
     }
 
@@ -80,19 +87,27 @@
      * @param call The call for which to stop ringback.
      */
     public void stopRingbackForCall(Call call) {
-        if (mCall == call) {
-            // The foreground call is no longer dialing or is no longer the foreground call. In
-            // either case, stop the ringback tone.
-            mCall = null;
+        synchronized (mLock) {
+            if (mCall == call) {
+                // The foreground call is no longer dialing or is no longer the foreground call. In
+                // either case, stop the ringback tone.
+                mCall = null;
 
-            if (mTonePlayer == null) {
-                Log.w(this, "No player found to stop.");
-            } else {
-                Log.i(this, "Stopping the ringback tone for %s.", call);
-                Log.addEvent(call, STOP_RINGBACK);
-                mTonePlayer.stopTone();
-                mTonePlayer = null;
+                if (mTonePlayer == null) {
+                    Log.w(this, "No player found to stop.");
+                } else {
+                    Log.i(this, "Stopping the ringback tone for %s.", call);
+                    Log.addEvent(call, STOP_RINGBACK);
+                    mTonePlayer.stopTone();
+                    mTonePlayer = null;
+                }
             }
         }
     }
+
+    public boolean isRingbackPlaying() {
+        synchronized (mLock) {
+            return mTonePlayer != null;
+        }
+    }
 }
\ No newline at end of file
diff --git a/src/com/android/server/telecom/Ringer.java b/src/com/android/server/telecom/Ringer.java
index c04cf6b..3ec4ebe 100644
--- a/src/com/android/server/telecom/Ringer.java
+++ b/src/com/android/server/telecom/Ringer.java
@@ -22,10 +22,12 @@
 import static android.provider.Settings.Global.ZEN_MODE_OFF;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.Person;
 import android.content.Context;
+import android.content.res.Resources;
 import android.media.AudioManager;
 import android.media.Ringtone;
 import android.media.VolumeShaper;
@@ -33,28 +35,41 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.os.UserHandle;
+import android.os.UserManager;
 import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
+import android.os.vibrator.persistence.ParsedVibration;
+import android.os.vibrator.persistence.VibrationXmlParser;
 import android.telecom.Log;
 import android.telecom.TelecomManager;
 import android.view.accessibility.AccessibilityManager;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.telecom.LogUtils.EventTimer;
+import com.android.server.telecom.flags.FeatureFlags;
 
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
+import java.util.function.BiConsumer;
+import java.util.function.Supplier;
 
 /**
  * Controls the ringtone player.
  */
 @VisibleForTesting
 public class Ringer {
+    private static final String TAG = "TelecomRinger";
+
     public interface AccessibilityManagerAdapter {
         boolean startFlashNotificationSequence(@NonNull Context context,
                 @AccessibilityManager.FlashNotificationReason int reason);
@@ -80,6 +95,16 @@
     // Used for test to notify the completion of RingerAttributes
     private CountDownLatch mAttributesLatch;
 
+    /**
+     * Delay to be used between consecutive vibrations when a non-repeating vibration effect is
+     * provided by the device.
+     *
+     * <p>If looking to customize the loop delay for a device's ring vibration, the desired repeat
+     * behavior should be encoded directly in the effect specification in the device configuration
+     * rather than changing the here (i.e. in `R.raw.default_ringtone_vibration_effect` resource).
+     */
+    private static int DEFAULT_RING_VIBRATION_LOOP_DELAY_MS = 1000;
+
     private static final long[] PULSE_PRIMING_PATTERN = {0,12,250,12,500}; // priming  + interval
 
     private static final int[] PULSE_PRIMING_AMPLITUDE = {0,255,0,255,0};  // priming  + interval
@@ -92,9 +117,11 @@
     private static final int[] PULSE_RAMPING_AMPLITUDE = {
         77,77,78,79,81,84,87,93,101,114,133,162,205,255,255,0};
 
-    private static final long[] PULSE_PATTERN;
+    @VisibleForTesting
+    public static final long[] PULSE_PATTERN;
 
-    private static final int[] PULSE_AMPLITUDE;
+    @VisibleForTesting
+    public static final int[] PULSE_AMPLITUDE;
 
     private static final int RAMPING_RINGER_VIBRATION_DURATION = 5000;
     private static final int RAMPING_RINGER_DURATION = 10000;
@@ -158,6 +185,7 @@
     private final InCallController mInCallController;
     private final VibrationEffectProxy mVibrationEffectProxy;
     private final boolean mIsHapticPlaybackSupportedByDevice;
+    private final FeatureFlags mFlags;
     /**
      * For unit testing purposes only; when set, {@link #startRinging(Call, boolean)} will complete
      * the future provided by the test using {@link #setBlockOnRingingFuture(CompletableFuture)}.
@@ -172,7 +200,7 @@
 
     /**
      * Call objects that are ringing, vibrating or call-waiting. These are used only for logging
-     * purposes.
+     * purposes (except mVibratingCall is also used to ensure consistency).
      */
     private Call mRingingCall;
     private Call mVibratingCall;
@@ -203,7 +231,8 @@
             VibrationEffectProxy vibrationEffectProxy,
             InCallController inCallController,
             NotificationManager notificationManager,
-            AccessibilityManagerAdapter accessibilityManagerAdapter) {
+            AccessibilityManagerAdapter accessibilityManagerAdapter,
+            FeatureFlags featureFlags) {
 
         mLock = new Object();
         mSystemSettingsUtil = systemSettingsUtil;
@@ -219,18 +248,15 @@
         mNotificationManager = notificationManager;
         mAccessibilityManagerAdapter = accessibilityManagerAdapter;
 
-        if (mContext.getResources().getBoolean(R.bool.use_simple_vibration_pattern)) {
-            mDefaultVibrationEffect = mVibrationEffectProxy.createWaveform(SIMPLE_VIBRATION_PATTERN,
-                    SIMPLE_VIBRATION_AMPLITUDE, REPEAT_SIMPLE_VIBRATION_AT);
-        } else {
-            mDefaultVibrationEffect = mVibrationEffectProxy.createWaveform(PULSE_PATTERN,
-                    PULSE_AMPLITUDE, REPEAT_VIBRATION_AT);
-        }
+        mDefaultVibrationEffect =
+                loadDefaultRingVibrationEffect(
+                        mContext, mVibrator, mVibrationEffectProxy, featureFlags);
 
         mIsHapticPlaybackSupportedByDevice =
                 mSystemSettingsUtil.isHapticPlaybackSupported(mContext);
 
         mAudioManager = mContext.getSystemService(AudioManager.class);
+        mFlags = featureFlags;
     }
 
     @VisibleForTesting
@@ -244,195 +270,257 @@
     }
 
     public boolean startRinging(Call foregroundCall, boolean isHfpDeviceAttached) {
-        if (foregroundCall == null) {
-            Log.wtf(this, "startRinging called with null foreground call.");
-            return false;
-        }
-
-        if (foregroundCall.getState() != CallState.RINGING
-                && foregroundCall.getState() != CallState.SIMULATED_RINGING) {
-            // Its possible for bluetooth to connect JUST as a call goes active, which would mean
-            // the call would start ringing again.
-            Log.i(this, "startRinging called for non-ringing foreground callid=%s",
-                    foregroundCall.getId());
-            return false;
-        }
-
-        // 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));
-
-        RingerAttributes attributes = null;
+        boolean deferBlockOnRingingFuture = false;
+        // try-finally to ensure that the block on ringing future is always called.
         try {
-            mAttributesLatch = new CountDownLatch(1);
-            attributes = ringerAttributesFuture.get(
-                    RINGER_ATTRIBUTES_TIMEOUT, TimeUnit.MILLISECONDS);
-        } catch (ExecutionException | InterruptedException | TimeoutException e) {
-            // Keep attributes as null
-            Log.i(this, "getAttributes error: " + e);
-        }
-
-        if (attributes == null) {
-            Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING, "RingerAttributes error");
-            return false;
-        }
-
-        if (attributes.isEndEarly()) {
-            boolean acquireAudioFocus = attributes.shouldAcquireAudioFocus();
-            if (attributes.letDialerHandleRinging()) {
-                Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING, "Dialer handles");
-                // Dialer will setup a ringtone, provide the audio focus if its audible.
-                acquireAudioFocus |= attributes.isRingerAudible();
+            if (foregroundCall == null) {
+                Log.wtf(this, "startRinging called with null foreground call.");
+                return false;
             }
 
-            if (attributes.isSilentRingingRequested()) {
-                Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING, "Silent ringing "
-                        + "requested");
+            if (foregroundCall.getState() != CallState.RINGING
+                    && foregroundCall.getState() != CallState.SIMULATED_RINGING) {
+                // It's possible for bluetooth to connect JUST as a call goes active, which would
+                // mean the call would start ringing again.
+                Log.i(this, "startRinging called for non-ringing foreground callid=%s",
+                        foregroundCall.getId());
+                return false;
             }
-            if (mBlockOnRingingFuture != null) {
-                mBlockOnRingingFuture.complete(null);
+
+            // 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));
+
+            RingerAttributes attributes = null;
+            try {
+                mAttributesLatch = new CountDownLatch(1);
+                attributes = ringerAttributesFuture.get(
+                        RINGER_ATTRIBUTES_TIMEOUT, TimeUnit.MILLISECONDS);
+            } catch (ExecutionException | InterruptedException | TimeoutException e) {
+                // Keep attributes as null
+                Log.i(this, "getAttributes error: " + e);
             }
-            return acquireAudioFocus;
-        }
 
-        stopCallWaiting();
+            if (attributes == null) {
+                Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING,
+                        "RingerAttributes error");
+                return false;
+            }
 
-        final boolean shouldFlash = attributes.shouldRingForContact();
-        if (mAccessibilityManagerAdapter != null && shouldFlash) {
-            Log.addEvent(foregroundCall, LogUtils.Events.FLASH_NOTIFICATION_START);
-            getHandler().post(() ->
-                    mAccessibilityManagerAdapter.startFlashNotificationSequence(mContext,
-                        AccessibilityManager.FLASH_REASON_CALL));
-        }
+            if (attributes.isEndEarly()) {
+                boolean acquireAudioFocus = attributes.shouldAcquireAudioFocus();
+                if (attributes.letDialerHandleRinging()) {
+                    Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING, "Dialer handles");
+                    // Dialer will setup a ringtone, provide the audio focus if its audible.
+                    acquireAudioFocus |= attributes.isRingerAudible();
+                }
 
-        // Determine if the settings and DND mode indicate that the vibrator can be used right now.
-        final boolean isVibratorEnabled =
-            isVibratorEnabled(mContext, attributes.shouldRingForContact());
-        boolean shouldApplyRampingRinger =
-                isVibratorEnabled && mSystemSettingsUtil.isRampingRingerEnabled(mContext);
+                if (attributes.isSilentRingingRequested()) {
+                    Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING, "Silent ringing "
+                            + "requested");
+                }
+                if (attributes.isWorkProfileInQuietMode()) {
+                    Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING,
+                            "Work profile in quiet mode");
+                }
+                return acquireAudioFocus;
+            }
 
-        boolean isHapticOnly = false;
-        boolean useCustomVibrationEffect = false;
+            stopCallWaiting();
 
-        mVolumeShaperConfig = null;
+            final boolean shouldFlash = attributes.shouldRingForContact();
+            if (mAccessibilityManagerAdapter != null && shouldFlash) {
+                Log.addEvent(foregroundCall, LogUtils.Events.FLASH_NOTIFICATION_START);
+                getHandler().post(() ->
+                        mAccessibilityManagerAdapter.startFlashNotificationSequence(mContext,
+                                AccessibilityManager.FLASH_REASON_CALL));
+            }
 
-        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
-            // call (for the purposes of direct-to-voicemail), the information about custom
-            // ringtones should be available by the time this code executes. We can safely
-            // request the custom ringtone from the call and expect it to be current.
-            if (shouldApplyRampingRinger) {
-                Log.i(this, "create ramping ringer.");
-                float silencePoint = (float) (RAMPING_RINGER_VIBRATION_DURATION)
-                    / (float) (RAMPING_RINGER_VIBRATION_DURATION + RAMPING_RINGER_DURATION);
-                mVolumeShaperConfig =
-                    new VolumeShaper.Configuration.Builder()
-                        .setDuration(RAMPING_RINGER_VIBRATION_DURATION + RAMPING_RINGER_DURATION)
-                        .setCurve(
-                            new float[] {0.f, silencePoint + EPSILON /*keep monotonicity*/, 1.f},
-                            new float[] {0.f, 0.f, 1.f})
-                        .setInterpolatorType(VolumeShaper.Configuration.INTERPOLATOR_TYPE_LINEAR)
-                        .build();
-                if (mSystemSettingsUtil.isAudioCoupledVibrationForRampingRingerEnabled()) {
-                  useCustomVibrationEffect = true;
+            // Determine if the settings and DND mode indicate that the vibrator can be used right
+            // now.
+            final boolean isVibratorEnabled =
+                    isVibratorEnabled(mContext, attributes.shouldRingForContact());
+            boolean shouldApplyRampingRinger =
+                    isVibratorEnabled && mSystemSettingsUtil.isRampingRingerEnabled(mContext);
+
+            boolean isHapticOnly = false;
+            boolean useCustomVibrationEffect = false;
+
+            mVolumeShaperConfig = null;
+
+            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
+                // call (for the purposes of direct-to-voicemail), the information about custom
+                // ringtones should be available by the time this code executes. We can safely
+                // request the custom ringtone from the call and expect it to be current.
+                if (shouldApplyRampingRinger) {
+                    Log.i(this, "create ramping ringer.");
+                    float silencePoint = (float) (RAMPING_RINGER_VIBRATION_DURATION)
+                            / (float) (RAMPING_RINGER_VIBRATION_DURATION + RAMPING_RINGER_DURATION);
+                    mVolumeShaperConfig =
+                            new VolumeShaper.Configuration.Builder()
+                                    .setDuration(RAMPING_RINGER_VIBRATION_DURATION
+                                            + RAMPING_RINGER_DURATION)
+                                    .setCurve(
+                                            new float[]{0.f, silencePoint + EPSILON
+                                                    /*keep monotonicity*/, 1.f},
+                                            new float[]{0.f, 0.f, 1.f})
+                                    .setInterpolatorType(
+                                            VolumeShaper.Configuration.INTERPOLATOR_TYPE_LINEAR)
+                                    .build();
+                    if (mSystemSettingsUtil.isAudioCoupledVibrationForRampingRingerEnabled()) {
+                        useCustomVibrationEffect = true;
+                    }
+                } else {
+                    if (DEBUG_RINGER) {
+                        Log.i(this, "Create ringer with custom vibration effect");
+                    }
+                    // Ramping ringtone is not enabled.
+                    useCustomVibrationEffect = true;
                 }
             } else {
-                if (DEBUG_RINGER) {
-                  Log.i(this, "Create ringer with custom vibration effect");
-                }
-                // Ramping ringtone is not enabled.
-                useCustomVibrationEffect = true;
-            }
-        } else {
-            Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING,
-                "Inaudible: " + attributes.getInaudibleReason()
-                    + " isVibratorEnabled=" + isVibratorEnabled);
+                Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING,
+                        "Inaudible: " + attributes.getInaudibleReason()
+                                + " isVibratorEnabled=" + isVibratorEnabled);
 
-            if (isVibratorEnabled) {
-                // If ringer is not audible for this call, then the phone is in "Vibrate" mode.
-                // Use haptic-only ringtone or do not play anything.
-                isHapticOnly = true;
-                if (DEBUG_RINGER) {
-                  Log.i(this, "Set ringtone as haptic only: " + isHapticOnly);
+                if (isVibratorEnabled) {
+                    // If ringer is not audible for this call, then the phone is in "Vibrate" mode.
+                    // Use haptic-only ringtone or do not play anything.
+                    isHapticOnly = true;
+                    if (DEBUG_RINGER) {
+                        Log.i(this, "Set ringtone as haptic only: " + isHapticOnly);
+                    }
+                } else {
+                    foregroundCall.setUserMissed(USER_MISSED_NO_VIBRATE);
+                    return attributes.shouldAcquireAudioFocus(); // ringer not audible
+                }
+            }
+
+            boolean hapticChannelsMuted = !isVibratorEnabled || !mIsHapticPlaybackSupportedByDevice;
+            if (shouldApplyRampingRinger
+                    && !mSystemSettingsUtil.isAudioCoupledVibrationForRampingRingerEnabled()
+                    && isVibratorEnabled) {
+                Log.i(this, "Muted haptic channels since audio coupled ramping ringer is disabled");
+                hapticChannelsMuted = true;
+            } else if (hapticChannelsMuted) {
+                Log.i(this,
+                        "Muted haptic channels isVibratorEnabled=%s, hapticPlaybackSupported=%s",
+                        isVibratorEnabled, mIsHapticPlaybackSupportedByDevice);
+            }
+            // Defer ringtone creation to the async player thread.
+            Supplier<Ringtone> ringtoneSupplier;
+            final boolean finalHapticChannelsMuted = hapticChannelsMuted;
+            if (isHapticOnly) {
+                if (hapticChannelsMuted) {
+                    Log.i(this,
+                            "want haptic only ringtone but haptics are muted, skip ringtone play");
+                    ringtoneSupplier = null;
+                } else {
+                    ringtoneSupplier = mRingtoneFactory::getHapticOnlyRingtone;
                 }
             } else {
-                if (mBlockOnRingingFuture != null) {
-                  mBlockOnRingingFuture.complete(null);
-                }
+                ringtoneSupplier = () -> mRingtoneFactory.getRingtone(
+                        foregroundCall, mVolumeShaperConfig, finalHapticChannelsMuted);
+            }
+
+            // If vibration will be done, reserve the vibrator.
+            boolean vibratorReserved = isVibratorEnabled && attributes.shouldRingForContact()
+                && tryReserveVibration(foregroundCall);
+            if (!vibratorReserved) {
                 foregroundCall.setUserMissed(USER_MISSED_NO_VIBRATE);
-                return attributes.shouldAcquireAudioFocus(); // ringer not audible
+                Log.addEvent(foregroundCall, LogUtils.Events.SKIP_VIBRATION,
+                        "hasVibrator=%b, userRequestsVibrate=%b, ringerMode=%d, "
+                                + "isVibratorEnabled=%b",
+                        mVibrator.hasVibrator(),
+                        mSystemSettingsUtil.isRingVibrationEnabled(mContext),
+                        mAudioManager.getRingerMode(), isVibratorEnabled);
             }
-        }
 
-        boolean hapticChannelsMuted = !isVibratorEnabled || !mIsHapticPlaybackSupportedByDevice;
-        if (shouldApplyRampingRinger
-            && !mSystemSettingsUtil.isAudioCoupledVibrationForRampingRingerEnabled()
-            && isVibratorEnabled) {
-            Log.i(this, "Muted haptic channels since audio coupled ramping ringer is disabled");
-            hapticChannelsMuted = true;
-        } else if (hapticChannelsMuted) {
-            Log.i(this, "Muted haptic channels isVibratorEnabled=%s, hapticPlaybackSupported=%s",
-                isVibratorEnabled, mIsHapticPlaybackSupportedByDevice);
-        }
-        Ringtone ringtone;
-        if (isHapticOnly) {
-            if (hapticChannelsMuted) {
-                Log.i(this, "want haptic only ringtone but haptics are muted, skip ringtone play");
-                ringtone = null;
+            // The vibration logic depends on the loaded ringtone, but we need to defer the ringtone
+            // load to the async ringtone thread. Hence, we bundle up the final part of this method
+            // for that thread to run after loading the ringtone. This logic is intended to run even
+            // if the loaded ringtone is null. However if a stop event arrives before the ringtone
+            // creation finishes, then this consumer can be skipped.
+            final boolean finalUseCustomVibrationEffect = useCustomVibrationEffect;
+            BiConsumer<Ringtone, Boolean> afterRingtoneLogic =
+                    (Ringtone ringtone, Boolean stopped) -> {
+                try {
+                    if (stopped.booleanValue() || !vibratorReserved) {
+                        // don't start vibration if the ringing is already abandoned, or the
+                        // vibrator wasn't reserved. This still triggers the mBlockOnRingingFuture.
+                        return;
+                    }
+                    final VibrationEffect vibrationEffect;
+                    if (ringtone != null && finalUseCustomVibrationEffect) {
+                        if (DEBUG_RINGER) {
+                            Log.d(this, "Using ringtone defined vibration effect.");
+                        }
+                        vibrationEffect = getVibrationEffectForRingtone(ringtone);
+                    } else {
+                        vibrationEffect = mDefaultVibrationEffect;
+                    }
+
+                    boolean isUsingAudioCoupledHaptics =
+                            !finalHapticChannelsMuted && ringtone != null
+                                    && ringtone.hasHapticChannels();
+                    vibrateIfNeeded(isUsingAudioCoupledHaptics, foregroundCall, vibrationEffect);
+                } finally {
+                    // This is used to signal to tests that the async play() call has completed.
+                    if (mBlockOnRingingFuture != null) {
+                        mBlockOnRingingFuture.complete(null);
+                    }
+                }
+            };
+            deferBlockOnRingingFuture = true;  // Run in vibrationLogic.
+            if (ringtoneSupplier != null) {
+                mRingtonePlayer.play(ringtoneSupplier, afterRingtoneLogic, isHfpDeviceAttached);
             } else {
-                ringtone = mRingtoneFactory.getHapticOnlyRingtone();
+                afterRingtoneLogic.accept(/* ringtone= */ null, /* stopped= */ false);
             }
-        } else {
-            ringtone = mRingtoneFactory.getRingtone(
-                foregroundCall, mVolumeShaperConfig, hapticChannelsMuted);
-        }
 
-        final VibrationEffect vibrationEffect;
-        if (useCustomVibrationEffect) {
-            if (DEBUG_RINGER) {
-                Log.d(this, "Using ringtone defined vibration effect.");
-            }
-            vibrationEffect = getVibrationEffectForRingtone(ringtone);
-        } else {
-            vibrationEffect = mDefaultVibrationEffect;
-        }
-
-        if (ringtone == null) {
-            Log.w(this, "No ringtone was found bail out from playing.");
-            // No ringtone was found, try as a last resort to check if vibration can be performed.
-            vibrateIfNeeded(/* isUsingAudioCoupledHaptics */ false, attributes, foregroundCall,
-                vibrationEffect, isVibratorEnabled);
-            if (mBlockOnRingingFuture != null) {
+            // shouldAcquireAudioFocus is meant to be true, but that check is deferred to here
+            // because until now is when we actually know if the ringtone loading worked.
+            return attributes.shouldAcquireAudioFocus()
+                    || (!isHapticOnly && attributes.isRingerAudible());
+        } finally {
+            // This is used to signal to tests that the async play() call has completed. It can
+            // be deferred into AsyncRingtonePlayer
+            if (mBlockOnRingingFuture != null && !deferBlockOnRingingFuture) {
                 mBlockOnRingingFuture.complete(null);
             }
-            return attributes.shouldAcquireAudioFocus();
         }
-
-        boolean isUsingAudioCoupledHaptics = !hapticChannelsMuted && ringtone.hasHapticChannels();
-
-        // There is a ringtone and we want to play it.
-        mRingtonePlayer.play(ringtone);
-
-        vibrateIfNeeded(isUsingAudioCoupledHaptics, attributes, foregroundCall, vibrationEffect,
-            isVibratorEnabled);
-        if (mBlockOnRingingFuture != null) {
-            mBlockOnRingingFuture.complete(null);
-        }
-
-        // shouldAcquireAudioFocus is meant to be true, but that check is deferred to here
-        // because until now is when we actually know if the ringtone loading worked.
-        return attributes.shouldAcquireAudioFocus() || attributes.isRingerAudible();
     }
 
-    private void vibrateIfNeeded(boolean isUsingAudioCoupledHaptics, RingerAttributes attributes,
-        Call foregroundCall, VibrationEffect effect, boolean isVibratorEnabled) {
-        final boolean shouldRingForContact = attributes.shouldRingForContact();
+    /**
+     * Try to reserve the vibrator for this call, returning false if it's already committed.
+     * The vibration will be started by AsyncRingtonePlayer to ensure timing is aligned with the
+     * audio. The logic uses mVibratingCall to say which call is currently getting ready to vibrate,
+     * or actually vibrating (indicated by mIsVibrating).
+     *
+     * Once reserved, the vibrateIfNeeded method is expected to be called. Note that if
+     * audio-coupled haptics were used instead of vibrator, the reservation still stays until
+     * ringing is stopped, because the vibrator is exclusive to a single vibration source.
+     *
+     * Note that this "reservation" is only local to the Ringer - it's not locking the vibrator, so
+     * if it's busy with some other important vibration, this ringer's one may not displace it.
+     */
+    private boolean tryReserveVibration(Call foregroundCall) {
+        synchronized (mLock) {
+            if (mVibratingCall != null || mIsVibrating) {
+                return false;
+            }
+            mVibratingCall = foregroundCall;
+            return true;
+        }
+   }
 
+    private void vibrateIfNeeded(boolean isUsingAudioCoupledHaptics, Call foregroundCall,
+            VibrationEffect effect) {
         if (isUsingAudioCoupledHaptics) {
             Log.addEvent(
                 foregroundCall, LogUtils.Events.SKIP_VIBRATION, "using audio-coupled haptics");
@@ -440,48 +528,40 @@
         }
 
         synchronized (mLock) {
-            if (isVibratorEnabled && !mIsVibrating && shouldRingForContact) {
+            // Ensure the reservation is live. The mIsVibrating check should be redundant.
+            if (foregroundCall == mVibratingCall && !mIsVibrating) {
                 Log.addEvent(foregroundCall, LogUtils.Events.START_VIBRATOR,
                     "hasVibrator=%b, userRequestsVibrate=%b, ringerMode=%d, isVibrating=%b",
                     mVibrator.hasVibrator(), mSystemSettingsUtil.isRingVibrationEnabled(mContext),
                     mAudioManager.getRingerMode(), mIsVibrating);
-                mVibratingCall = foregroundCall;
                 mIsVibrating = true;
                 mVibrator.vibrate(effect, VIBRATION_ATTRIBUTES);
                 Log.i(this, "start vibration.");
-            } else {
-                foregroundCall.setUserMissed(USER_MISSED_NO_VIBRATE);
-                Log.addEvent(foregroundCall, LogUtils.Events.SKIP_VIBRATION,
-                    "hasVibrator=%b, userRequestsVibrate=%b, ringerMode=%d, isVibrating=%b",
-                    mVibrator.hasVibrator(), mSystemSettingsUtil.isRingVibrationEnabled(mContext),
-                    mAudioManager.getRingerMode(), mIsVibrating);
             }
+            // else stopped already: this isn't started unless a reservation was made.
         }
     }
 
-    private VibrationEffect getVibrationEffectForRingtone(Ringtone ringtone) {
-        VibrationEffect effect = null;
-        Uri ringtoneUri = ringtone != null ? ringtone.getUri() : null;
-        if (ringtoneUri != null) {
-            try {
-                effect = mVibrationEffectProxy.get(ringtoneUri, mContext);
-                if (effect == null) {
-                  Log.i(this, "did not find vibration effect, falling back to default vibration");
-                }
-            } catch (IllegalArgumentException iae) {
-                // Deep in the bowels of the VibrationEffect class it is possible for an
-                // IllegalArgumentException to be thrown if there is an invalid URI specified in the
-                // device config, or a content provider failure.  Rather than crashing the Telecom
-                // process we will just use the default vibration effect.
-                Log.e(this, iae, "getVibrationEffectForRingtone: failed to get vibration effect");
-                effect = null;
+    private VibrationEffect getVibrationEffectForRingtone(@NonNull Ringtone ringtone) {
+        Uri ringtoneUri = ringtone.getUri();
+        if (ringtoneUri == null) {
+            return mDefaultVibrationEffect;
+        }
+        try {
+            VibrationEffect effect = mVibrationEffectProxy.get(ringtoneUri, mContext);
+            if (effect == null) {
+              Log.i(this, "did not find vibration effect, falling back to default vibration");
+              return mDefaultVibrationEffect;
             }
+            return effect;
+        } catch (IllegalArgumentException iae) {
+            // Deep in the bowels of the VibrationEffect class it is possible for an
+            // IllegalArgumentException to be thrown if there is an invalid URI specified in the
+            // device config, or a content provider failure.  Rather than crashing the Telecom
+            // process we will just use the default vibration effect.
+            Log.e(this, iae, "getVibrationEffectForRingtone: failed to get vibration effect");
+            return mDefaultVibrationEffect;
         }
-
-        if (effect == null) {
-            effect = mDefaultVibrationEffect;
-        }
-        return effect;
     }
 
     public void startCallWaiting(Call call) {
@@ -494,7 +574,7 @@
         }
 
         if (mInCallController.doesConnectedDialerSupportRinging(
-                call.getUserHandleFromTargetPhoneAccount())) {
+                call.getAssociatedUser())) {
             Log.addEvent(call, LogUtils.Events.SKIP_RINGING, "Dialer handles");
             return;
         }
@@ -512,7 +592,7 @@
             Log.addEvent(call, LogUtils.Events.START_CALL_WAITING_TONE, reason);
             mCallWaitingCall = call;
             mCallWaitingPlayer =
-                    mPlayerFactory.createPlayer(InCallTonePlayer.TONE_CALL_WAITING);
+                    mPlayerFactory.createPlayer(call, InCallTonePlayer.TONE_CALL_WAITING);
             mCallWaitingPlayer.startTone();
         }
     }
@@ -537,8 +617,8 @@
                 Log.addEvent(mVibratingCall, LogUtils.Events.STOP_VIBRATOR);
                 mVibrator.cancel();
                 mIsVibrating = false;
-                mVibratingCall = null;
             }
+            mVibratingCall = null;  // Prevents vibrations from starting via AsyncRingtonePlayer.
         }
     }
 
@@ -568,24 +648,24 @@
     public boolean shouldRingForContact(Call call) {
         // avoid re-computing manager.matcherCallFilter(Bundle)
         if (call.wasDndCheckComputedForCall()) {
-            Log.v(this, "shouldRingForContact: returning computation from DndCallFilter.");
+            Log.i(this, "shouldRingForContact: returning computation from DndCallFilter.");
             return !call.isCallSuppressedByDoNotDisturb();
         }
-
-        final Uri contactUri = call.getHandle();
-        final Bundle peopleExtras = new Bundle();
-        if (contactUri != null) {
-            ArrayList<Person> personList = new ArrayList<>();
-            personList.add(new Person.Builder().setUri(contactUri.toString()).build());
-            peopleExtras.putParcelableArrayList(Notification.EXTRA_PEOPLE_LIST, personList);
+        Uri contactUri = call.getHandle();
+        if (mFlags.telecomResolveHiddenDependencies()) {
+            if (contactUri == null) {
+                contactUri = Uri.EMPTY;
+            }
+            return mNotificationManager.matchesCallFilter(contactUri);
+        } else {
+            final Bundle peopleExtras = new Bundle();
+            if (contactUri != null) {
+                ArrayList<Person> personList = new ArrayList<>();
+                personList.add(new Person.Builder().setUri(contactUri.toString()).build());
+                peopleExtras.putParcelableArrayList(Notification.EXTRA_PEOPLE_LIST, personList);
+            }
+            return mNotificationManager.matchesCallFilter(peopleExtras);
         }
-
-        // query NotificationManager
-        boolean shouldRing = mNotificationManager.matchesCallFilter(peopleExtras);
-        // store the suppressed status in the call object
-        call.setCallIsSuppressedByDoNotDisturb(!shouldRing);
-
-        return shouldRing;
     }
 
     private boolean hasExternalRinger(Call foregroundCall) {
@@ -638,18 +718,22 @@
         boolean isTheaterModeOn = mSystemSettingsUtil.isTheaterModeOn(mContext);
         timer.record("isTheaterModeOn");
         boolean letDialerHandleRinging = mInCallController.doesConnectedDialerSupportRinging(
-                call.getUserHandleFromTargetPhoneAccount());
+                call.getAssociatedUser());
         timer.record("letDialerHandleRinging");
+        boolean isWorkProfileInQuietMode =
+                isProfileInQuietMode(call.getAssociatedUser());
+        timer.record("isWorkProfileInQuietMode");
 
         Log.i(this, "startRinging timings: " + timer);
         boolean endEarly = isTheaterModeOn || letDialerHandleRinging || isSelfManaged ||
-                hasExternalRinger || isSilentRingingRequested;
+                hasExternalRinger || isSilentRingingRequested || isWorkProfileInQuietMode;
 
         if (endEarly) {
             Log.i(this, "Ending early -- isTheaterModeOn=%s, letDialerHandleRinging=%s, " +
-                            "isSelfManaged=%s, hasExternalRinger=%s, silentRingingRequested=%s",
+                            "isSelfManaged=%s, hasExternalRinger=%s, silentRingingRequested=%s, " +
+                            "isWorkProfileInQuietMode=%s",
                     isTheaterModeOn, letDialerHandleRinging, isSelfManaged, hasExternalRinger,
-                    isSilentRingingRequested);
+                    isSilentRingingRequested, isWorkProfileInQuietMode);
         }
 
         // Acquire audio focus under any of the following conditions:
@@ -657,8 +741,8 @@
         // 2. Volume is over zero, we should ring for the contact, and there's a audible ringtone
         //    present. (This check is deferred until ringer knows the ringtone)
         // 3. The call is self-managed.
-        boolean shouldAcquireAudioFocus =
-            (isHfpDeviceAttached && shouldRingForContact) || isSelfManaged;
+        boolean shouldAcquireAudioFocus = !isWorkProfileInQuietMode &&
+                ((isHfpDeviceAttached && shouldRingForContact) || isSelfManaged);
 
         // Set missed reason according to attributes
         if (!isVolumeOverZero) {
@@ -676,9 +760,15 @@
                 .setInaudibleReason(inaudibleReason)
                 .setShouldRingForContact(shouldRingForContact)
                 .setSilentRingingRequested(isSilentRingingRequested)
+                .setWorkProfileQuietMode(isWorkProfileInQuietMode)
                 .build();
     }
 
+    private boolean isProfileInQuietMode(UserHandle user) {
+        UserManager um = mContext.getSystemService(UserManager.class);
+        return um.isManagedProfile(user.getIdentifier()) && um.isQuietModeEnabled(user);
+    }
+
     private Handler getHandler() {
         if (mHandler == null) {
             HandlerThread handlerThread = new HandlerThread("Ringer");
@@ -696,4 +786,65 @@
             return false;
         }
     }
+
+    @Nullable
+    private static VibrationEffect loadSerializedDefaultRingVibration(
+            Resources resources, Vibrator vibrator) {
+        try {
+            InputStream vibrationInputStream =
+                    resources.openRawResource(
+                            com.android.internal.R.raw.default_ringtone_vibration_effect);
+            ParsedVibration parsedVibration = VibrationXmlParser
+                    .parseDocument(
+                            new InputStreamReader(vibrationInputStream, StandardCharsets.UTF_8));
+            if (parsedVibration == null) {
+                Log.w(TAG, "Got null parsed default ring vibration effect.");
+                return null;
+            }
+            return parsedVibration.resolve(vibrator);
+        } catch (IOException | Resources.NotFoundException e) {
+            Log.e(TAG, e, "Error parsing default ring vibration effect.");
+            return null;
+        }
+    }
+
+    private static VibrationEffect loadDefaultRingVibrationEffect(
+            Context context,
+            Vibrator vibrator,
+            VibrationEffectProxy vibrationEffectProxy,
+            FeatureFlags featureFlags) {
+        Resources resources = context.getResources();
+
+        if (resources.getBoolean(R.bool.use_simple_vibration_pattern)) {
+            Log.i(TAG, "Using simple default ring vibration.");
+            return createSimpleRingVibration(vibrationEffectProxy);
+        }
+
+        if (featureFlags.useDeviceProvidedSerializedRingerVibration()) {
+            VibrationEffect parsedEffect = loadSerializedDefaultRingVibration(resources, vibrator);
+            if (parsedEffect != null) {
+                Log.i(TAG, "Using parsed default ring vibration.");
+                // Make the parsed effect repeating to make it vibrate continuously during ring.
+                // If the effect is already repeating, this API call is a no-op.
+                // Otherwise, it  uses `DEFAULT_RING_VIBRATION_LOOP_DELAY_MS` when changing a
+                // non-repeating vibration to a repeating vibration.
+                // This is so that we ensure consecutive loops of the vibration play with some gap
+                // in between.
+                return parsedEffect.applyRepeatingIndefinitely(
+                        /* wantRepeating= */ true, DEFAULT_RING_VIBRATION_LOOP_DELAY_MS);
+            }
+            // Fallback to the simple vibration if the serialized effect cannot be loaded.
+            return createSimpleRingVibration(vibrationEffectProxy);
+        }
+
+        Log.i(TAG, "Using pulse default ring vibration.");
+        return vibrationEffectProxy.createWaveform(
+                PULSE_PATTERN, PULSE_AMPLITUDE, REPEAT_VIBRATION_AT);
+    }
+
+    private static VibrationEffect createSimpleRingVibration(
+            VibrationEffectProxy vibrationEffectProxy) {
+        return vibrationEffectProxy.createWaveform(SIMPLE_VIBRATION_PATTERN,
+                SIMPLE_VIBRATION_AMPLITUDE, REPEAT_SIMPLE_VIBRATION_AT);
+    }
 }
diff --git a/src/com/android/server/telecom/RingerAttributes.java b/src/com/android/server/telecom/RingerAttributes.java
index 840d815..e0d3e1c 100644
--- a/src/com/android/server/telecom/RingerAttributes.java
+++ b/src/com/android/server/telecom/RingerAttributes.java
@@ -25,6 +25,7 @@
         private String mInaudibleReason;
         private boolean mShouldRingForContact;
         private boolean mSilentRingingRequested;
+        private boolean mWorkProfileQuietMode;
 
         public RingerAttributes.Builder setEndEarly(boolean endEarly) {
             mEndEarly = endEarly;
@@ -61,10 +62,15 @@
             return this;
         }
 
+        public RingerAttributes.Builder setWorkProfileQuietMode(boolean workProfileQuietMode) {
+            mWorkProfileQuietMode = workProfileQuietMode;
+            return this;
+        }
+
         public RingerAttributes build() {
             return new RingerAttributes(mEndEarly, mLetDialerHandleRinging, mAcquireAudioFocus,
                     mRingerAudible, mInaudibleReason, mShouldRingForContact,
-                    mSilentRingingRequested);
+                    mSilentRingingRequested, mWorkProfileQuietMode);
         }
     }
 
@@ -75,10 +81,12 @@
     private String mInaudibleReason;
     private boolean mShouldRingForContact;
     private boolean mSilentRingingRequested;
+    private boolean mWorkProfileQuietMode;
 
     private RingerAttributes(boolean endEarly, boolean letDialerHandleRinging,
             boolean acquireAudioFocus, boolean ringerAudible, String inaudibleReason,
-            boolean shouldRingForContact, boolean silentRingingRequested) {
+            boolean shouldRingForContact, boolean silentRingingRequested,
+            boolean workProfileQuietMode) {
         mEndEarly = endEarly;
         mLetDialerHandleRinging = letDialerHandleRinging;
         mAcquireAudioFocus = acquireAudioFocus;
@@ -86,6 +94,7 @@
         mInaudibleReason = inaudibleReason;
         mShouldRingForContact = shouldRingForContact;
         mSilentRingingRequested = silentRingingRequested;
+        mWorkProfileQuietMode = workProfileQuietMode;
     }
 
     public boolean isEndEarly() {
@@ -115,4 +124,8 @@
     public boolean isSilentRingingRequested() {
         return mSilentRingingRequested;
     }
+
+    public boolean isWorkProfileInQuietMode() {
+        return mWorkProfileQuietMode;
+    }
 }
diff --git a/src/com/android/server/telecom/RingtoneFactory.java b/src/com/android/server/telecom/RingtoneFactory.java
index 68c95a6..6bcfb4c 100644
--- a/src/com/android/server/telecom/RingtoneFactory.java
+++ b/src/com/android/server/telecom/RingtoneFactory.java
@@ -66,12 +66,16 @@
 
     public Ringtone getRingtone(Call incomingCall,
             @Nullable VolumeShaper.Configuration volumeShaperConfig, boolean hapticChannelsMuted) {
+        // Initializing ringtones on the main thread can deadlock
+        ThreadUtil.checkNotOnMainThread();
+
         AudioAttributes audioAttrs = getDefaultRingtoneAudioAttributes(hapticChannelsMuted);
 
         // Use the default ringtone of the work profile if the contact is a work profile contact.
+        // or the default ringtone of the receiving user.
         Context userContext = isWorkContact(incomingCall) ?
                 getWorkProfileContextForUser(mCallsManager.getCurrentUserHandle()) :
-                getContextForUserHandle(mCallsManager.getCurrentUserHandle());
+                getContextForUserHandle(incomingCall.getAssociatedUser());
         Uri ringtoneUri = incomingCall.getRingtone();
         Ringtone ringtone = null;
 
@@ -116,7 +120,7 @@
         return ringtone;
     }
 
-    public AudioAttributes getDefaultRingtoneAudioAttributes(boolean hapticChannelsMuted) {
+    private AudioAttributes getDefaultRingtoneAudioAttributes(boolean hapticChannelsMuted) {
         return new AudioAttributes.Builder()
             .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
             .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
@@ -127,6 +131,8 @@
     /** Returns a ringtone to be used when ringer is not audible for the incoming call. */
     @Nullable
     public Ringtone getHapticOnlyRingtone() {
+        // Initializing ringtones on the main thread can deadlock
+        ThreadUtil.checkNotOnMainThread();
         Uri ringtoneUri = Uri.parse("file://" + mContext.getString(
                 com.android.internal.R.string.config_defaultRingtoneVibrationSound));
         AudioAttributes audioAttrs = getDefaultRingtoneAudioAttributes(
diff --git a/src/com/android/server/telecom/ServiceBinder.java b/src/com/android/server/telecom/ServiceBinder.java
index 7274993..77f7b2e 100644
--- a/src/com/android/server/telecom/ServiceBinder.java
+++ b/src/com/android/server/telecom/ServiceBinder.java
@@ -29,6 +29,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
+import com.android.server.telecom.flags.FeatureFlags;
 
 import java.util.Collections;
 import java.util.Set;
@@ -240,6 +241,8 @@
      * Abbreviated form of the package name from {@link #mComponentName}; used for session logging.
      */
     protected final String mPackageAbbreviation;
+    private final FeatureFlags mFlags;
+
 
     /** The set of callbacks waiting for notification of the binding's success or failure. */
     private final Set<BindCallback> mCallbacks = new ArraySet<>();
@@ -282,7 +285,7 @@
      * @param userHandle The {@link UserHandle} to use for binding.
      */
     protected ServiceBinder(String serviceAction, ComponentName componentName, Context context,
-            TelecomSystem.SyncRoot lock, UserHandle userHandle) {
+            TelecomSystem.SyncRoot lock, UserHandle userHandle, FeatureFlags featureFlags) {
         Preconditions.checkState(!TextUtils.isEmpty(serviceAction));
         Preconditions.checkNotNull(componentName);
 
@@ -292,6 +295,7 @@
         mComponentName = componentName;
         mPackageAbbreviation = Log.getPackageAbbreviation(componentName);
         mUserHandle = userHandle;
+        mFlags = featureFlags;
     }
 
     final UserHandle getUserHandle() {
@@ -305,10 +309,16 @@
     }
 
     final void decrementAssociatedCallCount() {
-        decrementAssociatedCallCount(false /*isSuppressingUnbind*/);
+        if (mFlags.updatedRcsCallCountTracking()) {
+            decrementAssociatedCallCountUpdated();
+        } else {
+            decrementAssociatedCallCount(false /*isSuppressingUnbind*/);
+        }
     }
 
     final void decrementAssociatedCallCount(boolean isSuppressingUnbind) {
+        // This is the legacy method - will be removed after the Flags.updatedRcsCallCountTracking
+        // mendel study completes.
         if (mAssociatedCallCount > 0) {
             mAssociatedCallCount--;
             Log.v(this, "Call count decrement %d, %s", mAssociatedCallCount,
@@ -323,6 +333,21 @@
         }
     }
 
+    final void decrementAssociatedCallCountUpdated() {
+        if (mAssociatedCallCount > 0) {
+            mAssociatedCallCount--;
+            Log.i(this, "Call count decrement %d, %s", mAssociatedCallCount,
+                    mComponentName.flattenToShortString());
+
+            if (mAssociatedCallCount == 0) {
+                unbind();
+            }
+        } else {
+            Log.wtf(this, "%s: ignoring a request to decrement mAssociatedCallCount below zero",
+                    mComponentName.getClassName());
+        }
+    }
+
     final int getAssociatedCallCount() {
         return mAssociatedCallCount;
     }
diff --git a/src/com/android/server/telecom/StatusBarNotifier.java b/src/com/android/server/telecom/StatusBarNotifier.java
index d1de958..772335e 100644
--- a/src/com/android/server/telecom/StatusBarNotifier.java
+++ b/src/com/android/server/telecom/StatusBarNotifier.java
@@ -79,13 +79,15 @@
         mIsShowingMute = isMuted;
     }
 
+    /**
+     * Update the status bar manager with the new speakerphone state.
+     *
+     * IMPORTANT: DO NOT call into any Telecom code here; this is usually scheduled on an async
+     * executor to save Telecom from blocking on outgoing binder calls.
+     * @param isSpeakerphone
+     */
     @VisibleForTesting
     public void notifySpeakerphone(boolean isSpeakerphone) {
-        // Never display anything if there are no calls.
-        if (!mCallsManager.hasAnyCalls()) {
-            isSpeakerphone = false;
-        }
-
         if (mIsShowingSpeakerphone == isSpeakerphone) {
             return;
         }
diff --git a/src/com/android/server/telecom/SystemStateHelper.java b/src/com/android/server/telecom/SystemStateHelper.java
index dd978c2..5eed1ac 100644
--- a/src/com/android/server/telecom/SystemStateHelper.java
+++ b/src/com/android/server/telecom/SystemStateHelper.java
@@ -146,9 +146,11 @@
         IntentFilter intentFilter1 = new IntentFilter(
                 UiModeManager.ACTION_ENTER_CAR_MODE_PRIORITIZED);
         intentFilter1.addAction(UiModeManager.ACTION_EXIT_CAR_MODE_PRIORITIZED);
+        intentFilter1.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
 
         IntentFilter intentFilter2 = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED);
         intentFilter2.addDataScheme("package");
+        intentFilter2.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
         mContext.registerReceiver(mBroadcastReceiver, intentFilter1);
         mContext.registerReceiver(mBroadcastReceiver, intentFilter2);
         Log.i(this, "Registering broadcast receiver: %s", intentFilter1);
diff --git a/src/com/android/server/telecom/TelecomBroadcastIntentProcessor.java b/src/com/android/server/telecom/TelecomBroadcastIntentProcessor.java
index e1f2d08..523b841 100644
--- a/src/com/android/server/telecom/TelecomBroadcastIntentProcessor.java
+++ b/src/com/android/server/telecom/TelecomBroadcastIntentProcessor.java
@@ -16,10 +16,12 @@
 
 package com.android.server.telecom;
 
+import android.app.BroadcastOptions;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.os.Bundle;
 import android.os.UserHandle;
 import android.telecom.Log;
 import android.widget.Toast;
@@ -99,6 +101,10 @@
     public static final String ACTION_CANCEL_REDIRECTED_CALL =
             "com.android.server.telecom.CANCEL_REDIRECTED_CALL";
 
+    public static final String ACTION_HANGUP_CALL = "com.android.server.telecom.HANGUP_CALL";
+    public static final String ACTION_STOP_STREAMING =
+            "com.android.server.telecom.ACTION_STOP_STREAMING";
+
     public static final String EXTRA_USERHANDLE = "userhandle";
     public static final String EXTRA_REDIRECTION_OUTGOING_CALL_ID =
             "android.telecom.extra.REDIRECTION_OUTGOING_CALL_ID";
@@ -240,6 +246,26 @@
             } finally {
                 Log.endSession();
             }
+        } else if (ACTION_HANGUP_CALL.equals(action)) {
+            Log.startSession("TBIP.aHC", "streamingDialog");
+            try {
+                Call call = mCallsManager.getCall(intent.getData().getSchemeSpecificPart());
+                if (call != null) {
+                    mCallsManager.disconnectCall(call);
+                }
+            } finally {
+                Log.endSession();
+            }
+        } else if (ACTION_STOP_STREAMING.equals(action)) {
+            Log.startSession("TBIP.aSS", "streamingDialog");
+            try {
+                Call call = mCallsManager.getCall(intent.getData().getSchemeSpecificPart());
+                if (call != null) {
+                    mCallsManager.stopCallStreaming(call);
+                }
+            } finally {
+                Log.endSession();
+            }
         }
     }
 
@@ -247,8 +273,14 @@
      * Closes open system dialogs and the notification shade.
      */
     private void closeSystemDialogs(Context context) {
-        Intent intent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
-        context.sendBroadcastAsUser(intent, UserHandle.ALL);
+        Intent intent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)
+                .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        Bundle options = BroadcastOptions.makeBasic()
+                .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
+                .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE)
+                .toBundle();
+        context.sendBroadcastAsUser(intent, UserHandle.ALL, null /* receiverPermission */,
+                options);
     }
 
     private void sendSmsIntent(Intent intent, UserHandle userHandle) {
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index 6826290..1dd68c9 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -19,6 +19,7 @@
 import static android.Manifest.permission.CALL_PHONE;
 import static android.Manifest.permission.CALL_PRIVILEGED;
 import static android.Manifest.permission.DUMP;
+import static android.Manifest.permission.MANAGE_OWN_CALLS;
 import static android.Manifest.permission.MODIFY_PHONE_STATE;
 import static android.Manifest.permission.READ_PHONE_NUMBERS;
 import static android.Manifest.permission.READ_PHONE_STATE;
@@ -26,11 +27,10 @@
 import static android.Manifest.permission.READ_SMS;
 import static android.Manifest.permission.REGISTER_SIM_SUBSCRIPTION;
 import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
-import static android.Manifest.permission.MANAGE_OWN_CALLS;
 import static android.telecom.CallAttributes.DIRECTION_INCOMING;
 import static android.telecom.CallAttributes.DIRECTION_OUTGOING;
-import static android.telecom.TelecomManager.TELECOM_TRANSACTION_SUCCESS;
 import static android.telecom.CallException.CODE_ERROR_UNKNOWN;
+import static android.telecom.TelecomManager.TELECOM_TRANSACTION_SUCCESS;
 
 import android.Manifest;
 import android.app.ActivityManager;
@@ -47,6 +47,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.ResolveInfo;
+import android.graphics.drawable.Icon;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Build;
@@ -58,7 +59,6 @@
 import android.provider.BlockedNumberContract;
 import android.provider.Settings;
 import android.telecom.CallAttributes;
-
 import android.telecom.CallException;
 import android.telecom.Log;
 import android.telecom.PhoneAccount;
@@ -79,6 +79,7 @@
 import com.android.internal.telecom.ITelecomService;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.telecom.components.UserCallIntentProcessorFactory;
+import com.android.server.telecom.flags.FeatureFlags;
 import com.android.server.telecom.settings.BlockedNumbersActivity;
 import com.android.server.telecom.voip.IncomingCallTransaction;
 import com.android.server.telecom.voip.OutgoingCallTransaction;
@@ -88,8 +89,10 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.lang.reflect.Method;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Objects;
 import java.util.Set;
 import java.util.UUID;
 
@@ -193,16 +196,21 @@
                 enforcePhoneAccountIsRegisteredEnabled(handle, handle.getUserHandle());
                 enforceCallingPackage(callingPackage, "addCall");
 
+                // add extras about info used for FGS delegation
+                Bundle extras = new Bundle();
+                extras.putInt(CallAttributes.CALLER_UID_KEY, Binder.getCallingUid());
+                extras.putInt(CallAttributes.CALLER_PID_KEY, Binder.getCallingPid());
+
                 VoipCallTransaction transaction = null;
                 // create transaction based on the call direction
                 switch (callAttributes.getDirection()) {
                     case DIRECTION_OUTGOING:
                         transaction = new OutgoingCallTransaction(callId, mContext, callAttributes,
-                                mCallsManager);
+                                mCallsManager, extras);
                         break;
                     case DIRECTION_INCOMING:
                         transaction = new IncomingCallTransaction(callId, callAttributes,
-                                mCallsManager);
+                                mCallsManager, extras);
                         break;
                     default:
                         throw new IllegalArgumentException(String.format("Invalid Call Direction. "
@@ -362,12 +370,13 @@
                 }
                 synchronized (mLock) {
                     final UserHandle callingUserHandle = Binder.getCallingUserHandle();
+                    boolean crossUserAccess = hasInAppCrossUserPermission();
                     long token = Binder.clearCallingIdentity();
                     try {
                         return new ParceledListSlice<>(
                                 mPhoneAccountRegistrar.getCallCapablePhoneAccounts(null,
                                         includeDisabledAccounts, callingUserHandle,
-                                        hasInAppCrossUserPermission()));
+                                        crossUserAccess));
                     } catch (Exception e) {
                         Log.e(this, e, "getCallCapablePhoneAccounts");
                         mAnomalyReporter.reportAnomaly(GET_CALL_CAPABLE_ACCOUNTS_ERROR_UUID,
@@ -540,15 +549,18 @@
                             throw e;
                         }
                     }
+                    Set<String> permissions = computePermissionsForBoundPackage(
+                            Set.of(MODIFY_PHONE_STATE), null);
                     long token = Binder.clearCallingIdentity();
                     try {
                         // In ideal case, we should not resolve the handle across profiles. But
                         // given the fact that profile's call is handled by its parent user's
                         // in-call UI, parent user's in call UI need to be able to get phone account
                         // from the profile's phone account handle.
-                        return mPhoneAccountRegistrar
+                        PhoneAccount account = mPhoneAccountRegistrar
                                 .getPhoneAccount(accountHandle, callingUserHandle,
                                         /* acrossProfiles */ true);
+                        return maybeCleansePhoneAccount(account, permissions);
                     } catch (Exception e) {
                         Log.e(this, e, "getPhoneAccount %s", accountHandle);
                         mAnomalyReporter.reportAnomaly(GET_PHONE_ACCOUNT_ERROR_UUID,
@@ -637,11 +649,12 @@
 
                 synchronized (mLock) {
                     final UserHandle callingUserHandle = Binder.getCallingUserHandle();
+                    boolean crossUserAccess = hasInAppCrossUserPermission();
                     long token = Binder.clearCallingIdentity();
                     try {
                         return new ParceledListSlice<>(mPhoneAccountRegistrar
                                 .getAllPhoneAccountHandles(callingUserHandle,
-                                        hasInAppCrossUserPermission()));
+                                        crossUserAccess));
                     } catch (Exception e) {
                         Log.e(this, e, "getAllPhoneAccounts");
                         throw e;
@@ -767,6 +780,9 @@
                                     .build();
                         }
 
+                        // Validate the profile boundary of the given image URI.
+                        validateAccountIconUserBoundary(account.getIcon());
+
                         final long token = Binder.clearCallingIdentity();
                         try {
                             Log.i(this, "registerPhoneAccount: account=%s",
@@ -945,12 +961,13 @@
                 synchronized (mLock) {
                     enforcePermissionOrPrivilegedDialer(MODIFY_PHONE_STATE, callingPackage);
                     UserHandle callingUserHandle = Binder.getCallingUserHandle();
+                    boolean crossUserAccess = hasInAppCrossUserPermission();
                     long token = Binder.clearCallingIdentity();
                     try {
                         Log.i(this, "Silence Ringer requested by %s", callingPackage);
                         Set<UserHandle> userHandles = mCallsManager.getCallAudioManager().
                                 silenceRingers(mContext, callingUserHandle,
-                                        hasInAppCrossUserPermission());
+                                        crossUserAccess);
                         mCallsManager.getInCallController().silenceRinger(userHandles);
                     } finally {
                         Binder.restoreCallingIdentity(token);
@@ -1536,6 +1553,23 @@
                             }
                             mCallIntentProcessorAdapter.processIncomingCallIntent(
                                     mCallsManager, intent);
+                            if (mFeatureFlags.earlyBindingToIncallService()) {
+                                PhoneAccount account =
+                                        mPhoneAccountRegistrar.getPhoneAccountUnchecked(
+                                                phoneAccountHandle);
+                                Bundle accountExtra =
+                                        account == null ? new Bundle() : account.getExtras();
+                                PackageManager packageManager = mContext.getPackageManager();
+                                // Start binding to InCallServices for wearable calls that do not
+                                // require call filtering. This is to wake up default dialer earlier
+                                // to mitigate InCallService binding latency.
+                                if (packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)
+                                        && accountExtra != null && accountExtra.getBoolean(
+                                        PhoneAccount.EXTRA_SKIP_CALL_FILTERING,
+                                        false)) {
+                                    mCallsManager.getInCallController().bindToServices(null);
+                                }
+                            }
                         } finally {
                             Binder.restoreCallingIdentity(token);
                         }
@@ -1739,7 +1773,6 @@
                 enforceCallingPackage(callingPackage, "placeCall");
 
                 PhoneAccountHandle phoneAccountHandle = null;
-                boolean clearPhoneAccountHandleExtra = false;
                 if (extras != null) {
                     phoneAccountHandle = extras.getParcelable(
                             TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
@@ -1748,33 +1781,42 @@
                         extras.remove(TelecomManager.EXTRA_IS_HANDOVER);
                     }
                 }
-                boolean isSelfManaged = phoneAccountHandle != null &&
+                ComponentName phoneAccountComponentName = phoneAccountHandle != null
+                        ? phoneAccountHandle.getComponentName() : null;
+                String phoneAccountPackageName = phoneAccountComponentName != null
+                        ? phoneAccountComponentName.getPackageName() : null;
+                boolean isCallerOwnerOfPhoneAccount =
+                        callingPackage.equals(phoneAccountPackageName);
+                boolean isSelfManagedPhoneAccount =
                         isSelfManagedConnectionService(phoneAccountHandle);
-                if (isSelfManaged) {
-                    try {
-                        mContext.enforceCallingOrSelfPermission(
-                                Manifest.permission.MANAGE_OWN_CALLS,
-                                "Self-managed ConnectionServices require "
-                                        + "MANAGE_OWN_CALLS permission.");
-                    } catch (SecurityException e) {
-                        // Fallback to use mobile network to avoid disclosing phone account handle
-                        // package information
-                        clearPhoneAccountHandleExtra = true;
-                    }
-
-                    if (!clearPhoneAccountHandleExtra && !callingPackage.equals(
-                            phoneAccountHandle.getComponentName().getPackageName())
-                            && !canCallPhone(callingPackage, callingFeatureId,
-                            "CALL_PHONE permission required to place calls.")) {
-                        // The caller is not allowed to place calls, so fallback to use mobile
-                        // network.
-                        clearPhoneAccountHandleExtra = true;
-                    }
-                } else if (!canCallPhone(callingPackage, callingFeatureId, "placeCall")) {
+                // Ensure the app's calling package matches the PhoneAccount package name before
+                // checking self-managed status so that we do not leak installed package
+                // information.
+                boolean isSelfManagedRequest = isCallerOwnerOfPhoneAccount &&
+                        isSelfManagedPhoneAccount;
+                if (isSelfManagedRequest) {
+                    // The package name of the caller matches the package name of the
+                    // PhoneAccountHandle, so ensure the app has MANAGE_OWN_CALLS permission if
+                    // self-managed.
+                    mContext.enforceCallingOrSelfPermission(
+                            Manifest.permission.MANAGE_OWN_CALLS,
+                            "Self-managed ConnectionServices require MANAGE_OWN_CALLS permission.");
+                } else if (!canCallPhone(callingPackage, callingFeatureId,
+                        "CALL_PHONE permission required to place calls.")) {
+                    // not self-managed, so CALL_PHONE is required.
                     mAnomalyReporter.reportAnomaly(PLACE_CALL_SECURITY_EXCEPTION_ERROR_UUID,
                             PLACE_CALL_SECURITY_EXCEPTION_ERROR_MSG);
-                    throw new SecurityException("Package " + callingPackage
-                            + " is not allowed to place phone calls");
+                    throw new SecurityException(
+                            "CALL_PHONE permission required to place calls.");
+                }
+
+                // An application can not place a call with a self-managed PhoneAccount that
+                // they do not own. If this is the case (and the app has CALL_PHONE permission),
+                // remove the PhoneAccount from the request and place the call as if it was a
+                // managed call request with no PhoneAccount specified.
+                if (!isCallerOwnerOfPhoneAccount && isSelfManagedPhoneAccount) {
+                    // extras can not be null if isSelfManagedPhoneAccount is true
+                    extras.remove(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
                 }
 
                 // Note: we can still get here for the default/system dialer, even if the Phone
@@ -1788,14 +1830,14 @@
                         Binder.getCallingUid(), callingPackage, callingFeatureId, null)
                         == AppOpsManager.MODE_ALLOWED;
 
-                final boolean hasCallPermission = mContext.checkCallingPermission(CALL_PHONE) ==
-                        PackageManager.PERMISSION_GRANTED;
+                final boolean hasCallPermission = mContext.checkCallingOrSelfPermission(CALL_PHONE)
+                        == PackageManager.PERMISSION_GRANTED;
                 // The Emergency Dialer has call privileged permission and uses this to place
                 // emergency calls.  We ensure permission checks in
                 // NewOutgoingCallIntentBroadcaster#process pass by sending this to
                 // Telecom as an ACTION_CALL_PRIVILEGED intent (which makes sense since the
                 // com.android.phone process has that permission).
-                final boolean hasCallPrivilegedPermission = mContext.checkCallingPermission(
+                final boolean hasCallPrivilegedPermission = mContext.checkCallingOrSelfPermission(
                         CALL_PRIVILEGED) == PackageManager.PERMISSION_GRANTED;
 
                 synchronized (mLock) {
@@ -1805,16 +1847,13 @@
                         final Intent intent = new Intent(hasCallPrivilegedPermission ?
                                 Intent.ACTION_CALL_PRIVILEGED : Intent.ACTION_CALL, handle);
                         if (extras != null) {
-                            if (clearPhoneAccountHandleExtra) {
-                                extras.remove(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
-                            }
                             extras.setDefusable(true);
                             intent.putExtras(extras);
                         }
                         mUserCallIntentProcessorFactory.create(mContext, userHandle)
-                                .processIntent(
-                                        intent, callingPackage, isSelfManaged ||
-                                                (hasCallAppOp && hasCallPermission),
+                                .processIntent(intent, callingPackage, isSelfManagedRequest,
+                                        (hasCallAppOp && hasCallPermission)
+                                                || hasCallPrivilegedPermission,
                                         true /* isLocalInvocation */);
                     } finally {
                         Binder.restoreCallingIdentity(token);
@@ -1921,19 +1960,21 @@
             }
 
 
-            if (args.length > 0 && Analytics.ANALYTICS_DUMPSYS_ARG.equals(args[0])) {
+            if (args != null && args.length > 0 && Analytics.ANALYTICS_DUMPSYS_ARG.equals(
+                    args[0])) {
                 Binder.withCleanCallingIdentity(() ->
                         Analytics.dumpToEncodedProto(mContext, writer, args));
                 return;
             }
 
-            boolean isTimeLineView = (args.length > 0 && TIME_LINE_ARG.equalsIgnoreCase(args[0]));
+            boolean isTimeLineView =
+                    (args != null && args.length > 0 && TIME_LINE_ARG.equalsIgnoreCase(args[0]));
 
             final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
             if (mCallsManager != null) {
                 pw.println("CallsManager: ");
                 pw.increaseIndent();
-                mCallsManager.dump(pw);
+                mCallsManager.dump(pw, args);
                 pw.decreaseIndent();
 
                 pw.println("PhoneAccountRegistrar: ");
@@ -1945,6 +1986,11 @@
                 pw.increaseIndent();
                 Analytics.dump(pw);
                 pw.decreaseIndent();
+
+                pw.println("Flag Configurations: ");
+                pw.increaseIndent();
+                reflectAndPrintFlagConfigs(pw);
+                pw.decreaseIndent();
             }
             if (isTimeLineView) {
                 Log.dumpEventsTimeline(pw);
@@ -1954,6 +2000,28 @@
         }
 
         /**
+         * Print all feature flag configurations that Telecom is using for debugging purposes.
+         */
+        private void reflectAndPrintFlagConfigs(IndentingPrintWriter pw) {
+
+            try {
+                // Look away, a forbidden technique (reflection) is being used to allow us to get
+                // all flag configs without having to add them manually to this method.
+                Method[] methods = FeatureFlags.class.getMethods();
+                if (methods.length == 0) {
+                    pw.println("NONE");
+                    return;
+                }
+                for (Method m : methods) {
+                    pw.println(m.getName() + "-> " + m.invoke(mFeatureFlags));
+                }
+            } catch (Exception e) {
+                pw.println("[ERROR]");
+            }
+
+        }
+
+        /**
          * @see android.telecom.TelecomManager#createManageBlockedNumbersIntent
          */
         @Override
@@ -2116,7 +2184,7 @@
                     try {
                         Log.i(this, "handleCallIntent: handling call intent");
                         mCallIntentProcessorAdapter.processOutgoingCallIntent(mContext,
-                                mCallsManager, intent, callingPackage);
+                                mCallsManager, intent, callingPackage, mFeatureFlags);
                     } finally {
                         Binder.restoreCallingIdentity(token);
                     }
@@ -2151,7 +2219,7 @@
                                     || call.getState() == CallState.DISCONNECTING) {
                                 mCallsManager.markCallAsRemoved(call);
                             }
-                            userHandles.add(call.getUserHandleFromTargetPhoneAccount());
+                            userHandles.add(call.getAssociatedUser());
                         }
                         for (UserHandle userHandle : userHandles) {
                             mCallsManager.getInCallController().unbindFromServices(userHandle);
@@ -2190,6 +2258,39 @@
         }
 
         /**
+         * A method intended for use in testing to query whether a particular non-ui inCallService
+         * is bound in a call.
+         * @param packageName of the service to query.
+         * @return whether it is bound or not.
+         */
+        @Override
+        public boolean isNonUiInCallServiceBound(String packageName) {
+            Log.startSession("TCI.iNUICSB");
+            try {
+                synchronized (mLock) {
+                    enforceShellOnly(Binder.getCallingUid(), "isNonUiInCallServiceBound");
+                    if (!(mContext.checkCallingOrSelfPermission(READ_PHONE_STATE)
+                            == PackageManager.PERMISSION_GRANTED) ||
+                            !(mContext.checkCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE)
+                                    == PackageManager.PERMISSION_GRANTED)) {
+                        throw new SecurityException("isNonUiInCallServiceBound requires the"
+                                + " READ_PHONE_STATE or READ_PRIVILEGED_PHONE_STATE permission");
+                    }
+                    long token = Binder.clearCallingIdentity();
+                    try {
+                        return mCallsManager
+                                .getInCallController()
+                                .isNonUiInCallServiceBound(packageName);
+                    } finally {
+                        Binder.restoreCallingIdentity(token);
+                    }
+                }
+            } 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.
@@ -2456,6 +2557,7 @@
     private final TelecomSystem.SyncRoot mLock;
     private TransactionManager mTransactionManager;
     private final TransactionalServiceRepository mTransactionalServiceRepository;
+    private final FeatureFlags mFeatureFlags;
 
     public TelecomServiceImpl(
             Context context,
@@ -2466,6 +2568,7 @@
             DefaultDialerCache defaultDialerCache,
             SubscriptionManagerAdapter subscriptionManagerAdapter,
             SettingsSecureAdapter settingsSecureAdapter,
+            FeatureFlags featureFlags,
             TelecomSystem.SyncRoot lock) {
         mContext = context;
         mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
@@ -2473,6 +2576,7 @@
         mPackageManager = mContext.getPackageManager();
 
         mCallsManager = callsManager;
+        mFeatureFlags = featureFlags;
         mLock = lock;
         mPhoneAccountRegistrar = phoneAccountRegistrar;
         mUserCallIntentProcessorFactory = userCallIntentProcessorFactory;
@@ -2685,6 +2789,7 @@
         int packageUid = -1;
         int callingUid = Binder.getCallingUid();
         PackageManager pm;
+        long token = Binder.clearCallingIdentity();
         try{
             pm = mContext.createContextAsUser(
                     UserHandle.getUserHandleForUid(callingUid), 0).getPackageManager();
@@ -2693,6 +2798,8 @@
             Log.i(this, "callingUidMatchesPackageManagerRecords:"
                             + " createContextAsUser hit exception=[%s]", e.toString());
             return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
         }
         if (pm != null) {
             try {
@@ -2711,6 +2818,46 @@
         return packageUid == callingUid;
     }
 
+    /**
+     * Note: This method should be called BEFORE clearing the binder identity.
+     *
+     * @param permissionsToValidate      set of permissions that should be checked
+     * @param alreadyComputedPermissions a list of permissions that were already checked
+     * @return all the permissions that
+     */
+    private Set<String> computePermissionsForBoundPackage(
+            Set<String> permissionsToValidate,
+            Set<String> alreadyComputedPermissions) {
+        Set<String> permissions = Objects.requireNonNullElseGet(alreadyComputedPermissions,
+                HashSet::new);
+        for (String permission : permissionsToValidate) {
+            if (mContext.checkCallingPermission(permission) == PackageManager.PERMISSION_GRANTED) {
+                permissions.add(permission);
+            }
+        }
+        return permissions;
+    }
+
+    /**
+     * This method should be used to clear {@link PhoneAccount} properties based on a
+     * callingPackages permissions.
+     *
+     * @param account     to clear properties from
+     * @param permissions the list of permissions the callingPackge has
+     * @return the account that callingPackage will receive
+     */
+    private PhoneAccount maybeCleansePhoneAccount(PhoneAccount account,
+            Set<String> permissions) {
+        if (account == null) {
+            return null;
+        }
+        PhoneAccount.Builder accountBuilder = new PhoneAccount.Builder(account);
+        if (!permissions.contains(MODIFY_PHONE_STATE)) {
+            accountBuilder.setGroupId("***");
+        }
+        return accountBuilder.build();
+    }
+
     private void enforceTelecomFeature() {
         PackageManager pm = mContext.getPackageManager();
         if (!pm.hasSystemFeature(PackageManager.FEATURE_TELECOM)
@@ -2944,6 +3091,11 @@
             return true;
         }
 
+        if (mContext.checkCallingOrSelfPermission(CALL_PRIVILEGED)
+                == PackageManager.PERMISSION_GRANTED) {
+            return true;
+        }
+
         // Accessing phone state is gated by a special permission.
         mContext.enforceCallingOrSelfPermission(CALL_PHONE, message);
 
@@ -3077,4 +3229,22 @@
             mContext.sendBroadcast(intent);
         }
     }
+
+    private void validateAccountIconUserBoundary(Icon icon) {
+        // Refer to Icon#getUriString for context. The URI string is invalid for icons of
+        // incompatible types.
+        if (icon != null && (icon.getType() == Icon.TYPE_URI
+                || icon.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP)) {
+            String encodedUser = icon.getUri().getEncodedUserInfo();
+            // If there is no encoded user, the URI is calling into the calling user space
+            if (encodedUser != null) {
+                int userId = Integer.parseInt(encodedUser);
+                if (userId != UserHandle.getUserId(Binder.getCallingUid())) {
+                    // If we are transcending the profile boundary, throw an error.
+                    throw new IllegalArgumentException("Attempting to register a phone account with"
+                            + " an image icon belonging to another user.");
+                }
+            }
+        }
+    }
 }
diff --git a/src/com/android/server/telecom/TelecomSystem.java b/src/com/android/server/telecom/TelecomSystem.java
index f9c2508..101cd2d 100644
--- a/src/com/android/server/telecom/TelecomSystem.java
+++ b/src/com/android/server/telecom/TelecomSystem.java
@@ -26,12 +26,14 @@
 import android.content.ServiceConnection;
 import android.content.pm.ResolveInfo;
 import android.net.Uri;
+import android.os.BugreportManager;
+import android.os.DropBoxManager;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.telecom.Log;
 import android.telecom.PhoneAccountHandle;
 import android.telephony.AnomalyReporter;
-import android.view.accessibility.AccessibilityManager;
+import android.telephony.TelephonyManager;
 import android.widget.Toast;
 
 import androidx.annotation.NonNull;
@@ -42,17 +44,25 @@
 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.BlockedNumbersAdapter;
+import com.android.server.telecom.callfiltering.CallFilterResultCallback;
+import com.android.server.telecom.callfiltering.IncomingCallFilterGraph;
+import com.android.server.telecom.callfiltering.IncomingCallFilterGraphProvider;
 import com.android.server.telecom.components.UserCallIntentProcessor;
 import com.android.server.telecom.components.UserCallIntentProcessorFactory;
+import com.android.server.telecom.flags.FeatureFlags;
 import com.android.server.telecom.ui.AudioProcessingNotification;
+import com.android.server.telecom.ui.CallStreamingNotification;
 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.ui.ToastFactory;
+import com.android.server.telecom.voip.TransactionManager;
 
 import java.io.FileNotFoundException;
 import java.io.InputStream;
 import java.util.List;
+import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
 
 /**
@@ -110,6 +120,11 @@
                 .addDataAuthority(DialerCodeReceiver.TELECOM_SECRET_CODE_MARK, null);
         DIALER_SECRET_CODE_FILTER
                 .addDataAuthority(DialerCodeReceiver.TELECOM_SECRET_CODE_MENU, null);
+
+        USER_SWITCHED_FILTER.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
+        USER_STARTING_FILTER.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
+        BOOT_COMPLETE_FILTER.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
+        DIALER_SECRET_CODE_FILTER.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
     }
 
     private static TelecomSystem INSTANCE = null;
@@ -210,9 +225,14 @@
             RoleManagerAdapter roleManagerAdapter,
             ContactsAsyncHelper.Factory contactsAsyncHelperFactory,
             DeviceIdleControllerAdapter deviceIdleControllerAdapter,
-            Ringer.AccessibilityManagerAdapter accessibilityManagerAdapter) {
+            Ringer.AccessibilityManagerAdapter accessibilityManagerAdapter,
+            Executor asyncTaskExecutor,
+            Executor asyncCallAudioTaskExecutor,
+            BlockedNumbersAdapter blockedNumbersAdapter,
+            FeatureFlags featureFlags) {
         mContext = context.getApplicationContext();
         LogUtils.initLogging(mContext);
+        android.telecom.Log.setLock(mLock);
         AnomalyReporter.initialize(mContext);
         DefaultDialerManagerAdapter defaultDialerAdapter =
                 new DefaultDialerCache.DefaultDialerManagerAdapterImpl();
@@ -235,13 +255,19 @@
                             return context.getContentResolver().openInputStream(uri);
                         }
                     });
+            CallAudioCommunicationDeviceTracker communicationDeviceTracker = new
+                    CallAudioCommunicationDeviceTracker(mContext);
             BluetoothDeviceManager bluetoothDeviceManager = new BluetoothDeviceManager(mContext,
-                    mContext.getSystemService(BluetoothManager.class).getAdapter());
+                    mContext.getSystemService(BluetoothManager.class).getAdapter(),
+                    communicationDeviceTracker, featureFlags);
             BluetoothRouteManager bluetoothRouteManager = new BluetoothRouteManager(mContext, mLock,
-                    bluetoothDeviceManager, new Timeouts.Adapter());
+                    bluetoothDeviceManager, new Timeouts.Adapter(),
+                    communicationDeviceTracker, featureFlags);
             BluetoothStateReceiver bluetoothStateReceiver = new BluetoothStateReceiver(
-                    bluetoothDeviceManager, bluetoothRouteManager);
+                    bluetoothDeviceManager, bluetoothRouteManager,
+                    communicationDeviceTracker, featureFlags);
             mContext.registerReceiver(bluetoothStateReceiver, BluetoothStateReceiver.INTENT_FILTER);
+            communicationDeviceTracker.setBluetoothRouteManager(bluetoothRouteManager);
 
             WiredHeadsetManager wiredHeadsetManager = new WiredHeadsetManager(mContext);
             SystemStateHelper systemStateHelper = new SystemStateHelper(mContext, mLock);
@@ -249,7 +275,8 @@
             mMissedCallNotifier = missedCallNotifierImplFactory
                     .makeMissedCallNotifierImpl(mContext, mPhoneAccountRegistrar,
                             defaultDialerCache,
-                            deviceIdleControllerAdapter);
+                            deviceIdleControllerAdapter,
+                            featureFlags);
             DisconnectedCallNotifier.Factory disconnectedCallNotifierFactory =
                     new DisconnectedCallNotifier.Default();
 
@@ -268,7 +295,7 @@
                         EmergencyCallHelper emergencyCallHelper) {
                     return new InCallController(context, lock, callsManager, systemStateProvider,
                             defaultDialerCache, timeoutsAdapter, emergencyCallHelper,
-                            new CarModeTracker(), clockProxy);
+                            new CarModeTracker(), clockProxy, featureFlags);
                 }
             };
 
@@ -331,9 +358,22 @@
                 }
             };
 
+            EmergencyCallDiagnosticLogger emergencyCallDiagnosticLogger =
+                    new EmergencyCallDiagnosticLogger(mContext.getSystemService(
+                            TelephonyManager.class), mContext.getSystemService(
+                            BugreportManager.class), timeoutsAdapter, mContext.getSystemService(
+                            DropBoxManager.class), asyncTaskExecutor, clockProxy);
+
             CallAnomalyWatchdog callAnomalyWatchdog = new CallAnomalyWatchdog(
                     Executors.newSingleThreadScheduledExecutor(),
-                    mLock, timeoutsAdapter, clockProxy);
+                    mLock, timeoutsAdapter, clockProxy, emergencyCallDiagnosticLogger);
+
+            TransactionManager transactionManager = TransactionManager.getInstance();
+
+            CallStreamingNotification callStreamingNotification =
+                    new CallStreamingNotification(mContext,
+                            packageName -> AppLabelProxy.Util.getAppLabel(
+                                    mContext.getPackageManager(), packageName), asyncTaskExecutor);
 
             mCallsManager = new CallsManager(
                     mContext,
@@ -367,8 +407,16 @@
                     toastFactory,
                     callEndpointControllerFactory,
                     callAnomalyWatchdog,
-                    Executors.newSingleThreadExecutor(),
-                    accessibilityManagerAdapter);
+                    accessibilityManagerAdapter,
+                    asyncTaskExecutor,
+                    asyncCallAudioTaskExecutor,
+                    blockedNumbersAdapter,
+                    transactionManager,
+                    emergencyCallDiagnosticLogger,
+                    communicationDeviceTracker,
+                    callStreamingNotification,
+                    featureFlags,
+                    IncomingCallFilterGraph::new);
 
             mIncomingCallNotifier = incomingCallNotifier;
             incomingCallNotifier.setCallsManagerProxy(new IncomingCallNotifier.CallsManagerProxy() {
@@ -412,7 +460,7 @@
             }
 
             mCallIntentProcessor = new CallIntentProcessor(mContext, mCallsManager,
-                    defaultDialerCache);
+                    defaultDialerCache, featureFlags);
             mTelecomBroadcastIntentProcessor = new TelecomBroadcastIntentProcessor(
                     mContext, mCallsManager);
 
@@ -436,6 +484,7 @@
                     defaultDialerCache,
                     new TelecomServiceImpl.SubscriptionManagerAdapterImpl(),
                     new TelecomServiceImpl.SettingsSecureAdapterImpl(),
+                    featureFlags,
                     mLock);
         } finally {
             Log.endSession();
diff --git a/src/com/android/server/telecom/Timeouts.java b/src/com/android/server/telecom/Timeouts.java
index d4713e8..c5fdd4c 100644
--- a/src/com/android/server/telecom/Timeouts.java
+++ b/src/com/android/server/telecom/Timeouts.java
@@ -20,8 +20,8 @@
 import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.telecom.CallDiagnosticService;
-import android.telecom.CallRedirectionService;
 import android.telecom.CallDiagnostics;
+import android.telecom.CallRedirectionService;
 import android.telephony.ims.ImsReasonInfo;
 
 import java.util.concurrent.TimeUnit;
@@ -112,12 +112,46 @@
         public long getNonVoipEmergencyCallIntermediateStateTimeoutMillis() {
             return Timeouts.getNonVoipEmergencyCallIntermediateStateTimeoutMillis();
         }
+
+        public long getEmergencyCallTimeBeforeUserDisconnectThresholdMillis(){
+            return Timeouts.getEmergencyCallTimeBeforeUserDisconnectThresholdMillis();
+        }
+
+        public long getEmergencyCallActiveTimeThresholdMillis(){
+            return Timeouts.getEmergencyCallActiveTimeThresholdMillis();
+        }
+
+        public int getDaysBackToSearchEmergencyDiagnosticEntries(){
+            return Timeouts.getDaysBackToSearchEmergencyDiagnosticEntries();
+
+        }
     }
 
     /** A prefix to use for all keys so to not clobber the global namespace. */
     private static final String PREFIX = "telecom.";
 
     /**
+     * threshold used to filter out ecalls that the user may have dialed by mistake
+     * It is used only when the disconnect cause is LOCAL by EmergencyDiagnosticLogger
+     */
+    private static final String EMERGENCY_CALL_TIME_BEFORE_USER_DISCONNECT_THRESHOLD_MILLIS =
+            "emergency_call_time_before_user_disconnect_threshold_millis";
+
+    /**
+     * Returns the threshold used to detect ecalls that transition to active but only for a very
+     * short duration. These short duration active calls can result in Diagnostic data collection.
+     */
+    private static final String EMERGENCY_CALL_ACTIVE_TIME_THRESHOLD_MILLIS =
+            "emergency_call_active_time_threshold_millis";
+
+    /**
+     * Time in Days that is used to filter out old dropbox entries for emergency call diagnostic
+     * data. Entries older than this are ignored
+     */
+    private static final String DAYS_BACK_TO_SEARCH_EMERGENCY_DROP_BOX_ENTRIES =
+            "days_back_to_search_emergency_drop_box_entries";
+
+    /**
      * A prefix to use for {@link DeviceConfig} for the transitory state timeout of
      * VoIP Call, in millis.
      */
@@ -335,6 +369,36 @@
                 TRANSITORY_STATE_VOIP_NORMAL_TIMEOUT_MILLIS, 5000L);
     }
 
+
+    /**
+     * Returns the threshold used to filter out ecalls that the user may have dialed by mistake
+     * It is used only when the disconnect cause is LOCAL by EmergencyDiagnosticLogger
+     * @return the threshold in milliseconds
+     */
+    public static long getEmergencyCallTimeBeforeUserDisconnectThresholdMillis() {
+        return DeviceConfig.getLong(DeviceConfig.NAMESPACE_TELEPHONY,
+                EMERGENCY_CALL_TIME_BEFORE_USER_DISCONNECT_THRESHOLD_MILLIS, 20000L);
+    }
+
+    /**
+     * Returns the threshold used to detect ecalls that transition to active but only for a very
+     * short duration. These short duration active calls can result in Diagnostic data collection.
+     * @return the threshold in milliseconds
+     */
+    public static long getEmergencyCallActiveTimeThresholdMillis() {
+        return DeviceConfig.getLong(DeviceConfig.NAMESPACE_TELEPHONY,
+                EMERGENCY_CALL_ACTIVE_TIME_THRESHOLD_MILLIS, 15000L);
+    }
+
+    /**
+     * Time in Days that is used to filter out old dropbox entries for emergency call diagnostic
+     * data. Entries older than this are ignored
+     */
+    public static int getDaysBackToSearchEmergencyDiagnosticEntries() {
+        return DeviceConfig.getInt(DeviceConfig.NAMESPACE_TELEPHONY,
+                DAYS_BACK_TO_SEARCH_EMERGENCY_DROP_BOX_ENTRIES, 30);
+    }
+
     /**
      * Returns the duration of time an emergency VoIP call can be in a transitory state before
      * Telecom will try to clean up the call.
@@ -352,7 +416,7 @@
      */
     public static long getNonVoipCallTransitoryStateTimeoutMillis() {
         return DeviceConfig.getLong(DeviceConfig.NAMESPACE_TELEPHONY,
-                TRANSITORY_STATE_NON_VOIP_NORMAL_TIMEOUT_MILLIS, 5000L);
+                TRANSITORY_STATE_NON_VOIP_NORMAL_TIMEOUT_MILLIS, 10000L);
     }
 
     /**
@@ -362,7 +426,7 @@
      */
     public static long getNonVoipEmergencyCallTransitoryStateTimeoutMillis() {
         return DeviceConfig.getLong(DeviceConfig.NAMESPACE_TELEPHONY,
-                TRANSITORY_STATE_NON_VOIP_EMERGENCY_TIMEOUT_MILLIS, 5000L);
+                TRANSITORY_STATE_NON_VOIP_EMERGENCY_TIMEOUT_MILLIS, 10000L);
     }
 
     /**
@@ -392,7 +456,7 @@
      */
     public static long getNonVoipCallIntermediateStateTimeoutMillis() {
         return DeviceConfig.getLong(DeviceConfig.NAMESPACE_TELEPHONY,
-                INTERMEDIATE_STATE_NON_VOIP_NORMAL_TIMEOUT_MILLIS, 60000L);
+                INTERMEDIATE_STATE_NON_VOIP_NORMAL_TIMEOUT_MILLIS, 120000L);
     }
 
     /**
diff --git a/src/com/android/server/telecom/TransactionalServiceRepository.java b/src/com/android/server/telecom/TransactionalServiceRepository.java
index f84b934..15278e1 100644
--- a/src/com/android/server/telecom/TransactionalServiceRepository.java
+++ b/src/com/android/server/telecom/TransactionalServiceRepository.java
@@ -16,6 +16,7 @@
 
 package com.android.server.telecom;
 
+import android.telecom.Log;
 import android.telecom.PhoneAccountHandle;
 
 import com.android.internal.telecom.ICallEventCallback;
@@ -28,8 +29,8 @@
  * more calls.
  */
 public class TransactionalServiceRepository {
-
-    private static final Map<PhoneAccountHandle, TransactionalServiceWrapper> lookupTable =
+    private static final String TAG = TransactionalServiceRepository.class.getSimpleName();
+    private static final Map<PhoneAccountHandle, TransactionalServiceWrapper> mServiceLookupTable =
             new HashMap<>();
 
     public TransactionalServiceRepository() {
@@ -38,12 +39,15 @@
     public TransactionalServiceWrapper addNewCallForTransactionalServiceWrapper
             (PhoneAccountHandle phoneAccountHandle, ICallEventCallback callEventCallback,
                     CallsManager callsManager, Call call) {
-
-        TransactionalServiceWrapper service = null;
+        TransactionalServiceWrapper service;
+        // Only create a new TransactionalServiceWrapper if this is the first call for a package.
+        // Otherwise, get the existing TSW and add the new call to the service.
         if (!hasExistingServiceWrapper(phoneAccountHandle)) {
+            Log.d(TAG, "creating a new TSW; handle=[%s]", phoneAccountHandle);
             service = new TransactionalServiceWrapper(callEventCallback,
                     callsManager, phoneAccountHandle, call, this);
         } else {
+            Log.d(TAG, "add a new call to an existing TSW; handle=[%s]", phoneAccountHandle);
             service = getTransactionalServiceWrapper(phoneAccountHandle);
             if (service == null) {
                 throw new IllegalStateException("service is null");
@@ -52,25 +56,25 @@
             }
         }
 
-        lookupTable.put(phoneAccountHandle, service);
+        mServiceLookupTable.put(phoneAccountHandle, service);
 
         return service;
     }
 
     public TransactionalServiceWrapper getTransactionalServiceWrapper(PhoneAccountHandle pah) {
-        return lookupTable.get(pah);
+        return mServiceLookupTable.get(pah);
     }
 
     public boolean hasExistingServiceWrapper(PhoneAccountHandle pah) {
-        return lookupTable.containsKey(pah);
+        return mServiceLookupTable.containsKey(pah);
     }
 
     public boolean removeServiceWrapper(PhoneAccountHandle pah) {
+        Log.i(TAG, "removeServiceWrapper: for phoneAccountHandle=[%s]", pah);
         if (!hasExistingServiceWrapper(pah)) {
             return false;
         }
-        lookupTable.remove(pah);
+        mServiceLookupTable.remove(pah);
         return true;
     }
-
 }
diff --git a/src/com/android/server/telecom/TransactionalServiceWrapper.java b/src/com/android/server/telecom/TransactionalServiceWrapper.java
index d97d192..02ccef7 100644
--- a/src/com/android/server/telecom/TransactionalServiceWrapper.java
+++ b/src/com/android/server/telecom/TransactionalServiceWrapper.java
@@ -38,31 +38,30 @@
 
 import com.android.internal.telecom.ICallControl;
 import com.android.internal.telecom.ICallEventCallback;
-import com.android.server.telecom.voip.AnswerCallTransaction;
 import com.android.server.telecom.voip.CallEventCallbackAckTransaction;
 import com.android.server.telecom.voip.EndpointChangeTransaction;
 import com.android.server.telecom.voip.HoldCallTransaction;
 import com.android.server.telecom.voip.EndCallTransaction;
-import com.android.server.telecom.voip.HoldActiveCallForNewCallTransaction;
+import com.android.server.telecom.voip.MaybeHoldCallForNewCallTransaction;
 import com.android.server.telecom.voip.ParallelTransaction;
-import com.android.server.telecom.voip.RequestFocusTransaction;
+import com.android.server.telecom.voip.RequestNewActiveCallTransaction;
 import com.android.server.telecom.voip.SerialTransaction;
 import com.android.server.telecom.voip.TransactionManager;
 import com.android.server.telecom.voip.VoipCallTransaction;
 import com.android.server.telecom.voip.VoipCallTransactionResult;
 
 import java.util.ArrayList;
-import java.util.Hashtable;
 import java.util.List;
 import java.util.Locale;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 
 /**
  * Implements {@link android.telecom.CallEventCallback} and {@link android.telecom.CallControl}
  * on a per-client basis which is tied to a {@link PhoneAccountHandle}
  */
 public class TransactionalServiceWrapper implements
-        ConnectionServiceFocusManager.ConnectionServiceFocus, IBinder.DeathRecipient {
+        ConnectionServiceFocusManager.ConnectionServiceFocus {
     private static final String TAG = TransactionalServiceWrapper.class.getSimpleName();
 
     // CallControl : Client (ex. voip app) --> Telecom
@@ -85,13 +84,25 @@
     private final TransactionalServiceRepository mRepository;
     private ConnectionServiceFocusManager.ConnectionServiceFocusListener mConnSvrFocusListener;
     // init when constructor is called
-    private final Hashtable<String, Call> mTrackedCalls = new Hashtable<>();
-    private final Object mLock;
+    private final ConcurrentHashMap<String, Call> mTrackedCalls = new ConcurrentHashMap<>();
+    private final TelecomSystem.SyncRoot mLock;
     private final String mPackageName;
     // needs to be non-final for testing
     private TransactionManager mTransactionManager;
     private CallStreamingController mStreamingController;
 
+
+    // Each TransactionalServiceWrapper should have their own Binder.DeathRecipient to clean up
+    // any calls in the event the application crashes or is force stopped.
+    private final IBinder.DeathRecipient mAppDeathListener = new IBinder.DeathRecipient() {
+        @Override
+        public void binderDied() {
+            Log.i(TAG, "binderDied: for package=[%s]; cleaning calls", mPackageName);
+            cleanupTransactionalServiceWrapper();
+            mICallEventCallback.asBinder().unlinkToDeath(this, 0);
+        }
+    };
+
     public TransactionalServiceWrapper(ICallEventCallback callEventCallback,
             CallsManager callsManager, PhoneAccountHandle phoneAccountHandle, Call call,
             TransactionalServiceRepository repo) {
@@ -105,7 +116,8 @@
         mPackageName = phoneAccountHandle.getComponentName().getPackageName();
         mTransactionManager = TransactionManager.getInstance();
         mStreamingController = mCallsManager.getCallStreamingController();
-        mLock = new Object();
+        mLock = mCallsManager.getLock();
+        setDeathRecipient(callEventCallback);
     }
 
     @VisibleForTesting
@@ -129,12 +141,6 @@
         }
     }
 
-    public Call getCallById(String callId) {
-        synchronized (mLock) {
-            return mTrackedCalls.get(callId);
-        }
-    }
-
     @VisibleForTesting
     public boolean untrackCall(Call call) {
         Call removedCall = null;
@@ -159,23 +165,12 @@
         return callCount;
     }
 
-    @Override
-    public void binderDied() {
-        // remove all tacked calls from CallsManager && frameworks side
-        for (String id : mTrackedCalls.keySet()) {
-            Call call = mTrackedCalls.get(id);
-            mCallsManager.markCallAsDisconnected(call, new DisconnectCause(DisconnectCause.ERROR));
-            mCallsManager.removeCall(call);
-            // remove calls from Frameworks side
-            if (mICallEventCallback != null) {
-                try {
-                    mICallEventCallback.removeCallFromTransactionalServiceWrapper(call.getId());
-                } catch (RemoteException e) {
-                    // pass
-                }
-            }
+    public void cleanupTransactionalServiceWrapper() {
+        for (Call call : mTrackedCalls.values()) {
+            mCallsManager.markCallAsDisconnected(call,
+                    new DisconnectCause(DisconnectCause.ERROR, "process died"));
+            mCallsManager.removeCall(call); // This will clear mTrackedCalls && ClientTWS
         }
-        mTrackedCalls.clear();
     }
 
     /***
@@ -247,11 +242,12 @@
             if (call != null) {
                 switch (action) {
                     case SET_ACTIVE:
-                        addTransactionsToManager(createSetActiveTransactions(call), callback);
+                        handleCallControlNewCallFocusTransactions(call, SET_ACTIVE,
+                                false /* isAnswer */, 0/*VideoState (ignored)*/, callback);
                         break;
                     case ANSWER:
-                        addTransactionsToManager(createSetAnswerTransactions(call,
-                                (int) objects[0]), callback);
+                        handleCallControlNewCallFocusTransactions(call, ANSWER,
+                                true /* isAnswer */, (int) objects[0] /*VideoState*/, callback);
                         break;
                     case DISCONNECT:
                         addTransactionsToManager(new EndCallTransaction(mCallsManager,
@@ -262,7 +258,8 @@
                                 new HoldCallTransaction(mCallsManager, call), callback);
                         break;
                     case START_STREAMING:
-                        addTransactionsToManager(createStartStreamingTransaction(call), callback);
+                        addTransactionsToManager(mStreamingController.getStartStreamingTransaction(mCallsManager,
+                                TransactionalServiceWrapper.this, call, mLock), callback);
                         break;
                 }
             } else {
@@ -278,6 +275,32 @@
             }
         }
 
+        // The client is request their VoIP call state go ACTIVE/ANSWERED.
+        // This request is originating from the VoIP application.
+        private void handleCallControlNewCallFocusTransactions(Call call, String action,
+                boolean isAnswer, int potentiallyNewVideoState, ResultReceiver callback) {
+            mTransactionManager.addTransaction(createSetActiveTransactions(call),
+                    new OutcomeReceiver<>() {
+                        @Override
+                        public void onResult(VoipCallTransactionResult result) {
+                            Log.i(TAG, String.format(Locale.US,
+                                    "%s: onResult: callId=[%s]", action, call.getId()));
+                            if (isAnswer) {
+                                call.setVideoState(potentiallyNewVideoState);
+                            }
+                            callback.send(TELECOM_TRANSACTION_SUCCESS, new Bundle());
+                        }
+
+                        @Override
+                        public void onError(CallException exception) {
+                            Bundle extras = new Bundle();
+                            extras.putParcelable(TRANSACTION_EXCEPTION_KEY, exception);
+                            callback.send(exception == null ? CallException.CODE_ERROR_UNKNOWN :
+                                    exception.getCode(), extras);
+                        }
+                    });
+        }
+
         @Override
         public void requestCallEndpointChange(CallEndpoint endpoint, ResultReceiver callback) {
             try {
@@ -299,20 +322,18 @@
                 Call call = mTrackedCalls.get(callId);
                 if (call != null) {
                     call.onConnectionEvent(event, extras);
-                }
-                else{
+                } else {
                     Log.i(TAG,
                             "sendEvent: was called but there is no call with id=[%s] cannot be "
                                     + "found. Most likely the call has been disconnected");
                 }
-            }
-            finally {
+            } finally {
                 Log.endSession();
             }
         }
     };
 
-    private void addTransactionsToManager(VoipCallTransaction transaction,
+    public void addTransactionsToManager(VoipCallTransaction transaction,
             ResultReceiver callback) {
         Log.d(TAG, "addTransactionsToManager");
 
@@ -348,7 +369,8 @@
         try {
             Log.startSession("TSW.oSA");
             Log.d(TAG, String.format(Locale.US, "onSetActive: callId=[%s]", call.getId()));
-            handleNewActiveCallCallbacks(call, ON_SET_ACTIVE, 0);
+            handleCallEventCallbackNewFocus(call, ON_SET_ACTIVE, false /*isAnswerRequest*/,
+                    0 /*VideoState*/);
         } finally {
             Log.endSession();
         }
@@ -358,42 +380,60 @@
         try {
             Log.startSession("TSW.oA");
             Log.d(TAG, String.format(Locale.US, "onAnswer: callId=[%s]", call.getId()));
-            handleNewActiveCallCallbacks(call, ON_ANSWER, videoState);
+            handleCallEventCallbackNewFocus(call, ON_ANSWER, true /*isAnswerRequest*/,
+                    videoState /*VideoState*/);
         } finally {
             Log.endSession();
         }
     }
 
-    // need to create multiple transactions for onSetActive and onAnswer which both seek to set
-    // the call to active
-    private void handleNewActiveCallCallbacks(Call call, String action, int videoState) {
+    // handle a CallEventCallback to set a call ACTIVE/ANSWERED. Must get ack from client since the
+    // request has come from another source (ex. Android Auto is requesting a call to go active)
+    private void handleCallEventCallbackNewFocus(Call call, String action, boolean isAnswerRequest,
+            int potentiallyNewVideoState) {
         // save CallsManager state before sending client state changes
         Call foregroundCallBeforeSwap = mCallsManager.getForegroundCall();
         boolean wasActive = foregroundCallBeforeSwap != null && foregroundCallBeforeSwap.isActive();
 
-        // create 3 serial transactions:
-        // -- hold active
-        // -- set newCall as active
-        // -- ack from client
         SerialTransaction serialTransactions = createSetActiveTransactions(call);
-        serialTransactions.appendTransaction(
-                new CallEventCallbackAckTransaction(mICallEventCallback,
-                        action, call.getId(), videoState));
+        // 3. get ack from client (that the requested call can go active)
+        if (isAnswerRequest) {
+            serialTransactions.appendTransaction(
+                    new CallEventCallbackAckTransaction(mICallEventCallback,
+                            action, call.getId(), potentiallyNewVideoState, mLock));
+        } else {
+            serialTransactions.appendTransaction(
+                    new CallEventCallbackAckTransaction(mICallEventCallback,
+                            action, call.getId(), mLock));
+        }
 
         // do CallsManager workload before asking client and
         //   reset CallsManager state if client does NOT ack
-        mTransactionManager.addTransaction(serialTransactions, new OutcomeReceiver<>() {
-            @Override
-            public void onResult(VoipCallTransactionResult result) {
-                Log.i(TAG, String.format(Locale.US,
-                        "%s: onResult: callId=[%s]", action, call.getId()));
-            }
+        mTransactionManager.addTransaction(serialTransactions,
+                new OutcomeReceiver<>() {
+                    @Override
+                    public void onResult(VoipCallTransactionResult result) {
+                        Log.i(TAG, String.format(Locale.US,
+                                "%s: onResult: callId=[%s]", action, call.getId()));
+                        if (isAnswerRequest) {
+                            call.setVideoState(potentiallyNewVideoState);
+                        }
+                    }
 
-            @Override
-            public void onError(CallException exception) {
-                maybeResetForegroundCall(foregroundCallBeforeSwap, wasActive);
-            }
-        });
+                    @Override
+                    public void onError(CallException exception) {
+                        if (isAnswerRequest) {
+                            // This also sends the signal to untrack from TSW and the client_TSW
+                            removeCallFromCallsManager(call,
+                                    new DisconnectCause(DisconnectCause.REJECTED,
+                                            "client rejected to answer the call;"
+                                                    + " force disconnecting"));
+                        } else {
+                            mCallsManager.markCallAsOnHold(call);
+                        }
+                        maybeResetForegroundCall(foregroundCallBeforeSwap, wasActive);
+                    }
+                });
     }
 
 
@@ -403,7 +443,7 @@
             Log.i(TAG, String.format(Locale.US, "onSetInactive: callId=[%s]", call.getId()));
             mTransactionManager.addTransaction(
                     new CallEventCallbackAckTransaction(mICallEventCallback,
-                            ON_SET_INACTIVE, call.getId()), new OutcomeReceiver<>() {
+                            ON_SET_INACTIVE, call.getId(), mLock), new OutcomeReceiver<>() {
                         @Override
                         public void onResult(VoipCallTransactionResult result) {
                             mCallsManager.markCallAsOnHold(call);
@@ -411,7 +451,8 @@
 
                         @Override
                         public void onError(CallException exception) {
-                            Log.i(TAG, "onSetInactive: onError: with e=[%e]", exception);
+                            Log.w(TAG, "onSetInactive: onError: e.code=[%d], e.msg=[%s]",
+                                    exception.getCode(), exception.getMessage());
                         }
                     });
         } finally {
@@ -426,7 +467,7 @@
 
             mTransactionManager.addTransaction(
                     new CallEventCallbackAckTransaction(mICallEventCallback, ON_DISCONNECT,
-                            call.getId(), cause), new OutcomeReceiver<>() {
+                            call.getId(), cause, mLock), new OutcomeReceiver<>() {
                         @Override
                         public void onResult(VoipCallTransactionResult result) {
                             removeCallFromCallsManager(call, cause);
@@ -451,15 +492,16 @@
 
             mTransactionManager.addTransaction(
                     new CallEventCallbackAckTransaction(mICallEventCallback, ON_STREAMING_STARTED,
-                            call.getId()), new OutcomeReceiver<>() {
+                            call.getId(), mLock), new OutcomeReceiver<>() {
                         @Override
                         public void onResult(VoipCallTransactionResult result) {
                         }
 
                         @Override
                         public void onError(CallException exception) {
-                            Log.i(TAG, "onCallStreamingStarted: onError: with e=[%e]",
-                                    exception);
+                            Log.w(TAG, "onCallStreamingStarted: onError: "
+                                            + "e.code=[%d], e.msg=[%s]",
+                                    exception.getCode(), exception.getMessage());
                             stopCallStreaming(call);
                         }
                     }
@@ -512,14 +554,14 @@
             try {
                 // remove the call from frameworks wrapper (client side)
                 mICallEventCallback.removeCallFromTransactionalServiceWrapper(call.getId());
-                // remove the call from this class/wrapper (server side)
-                untrackCall(call);
             } catch (RemoteException e) {
             }
+            // remove the call from this class/wrapper (server side)
+            untrackCall(call);
         }
     }
 
-    public void onEvent(Call call, String event, Bundle extras){
+    public void onEvent(Call call, String event, Bundle extras) {
         if (call != null) {
             try {
                 mICallEventCallback.onEvent(call.getId(), event, extras);
@@ -553,28 +595,21 @@
         // create list for multiple transactions
         List<VoipCallTransaction> transactions = new ArrayList<>();
 
-        // add t1. hold potential active call
-        transactions.add(new HoldActiveCallForNewCallTransaction(mCallsManager, call));
+        // potentially hold the current active call in order to set a new call (active/answered)
+        transactions.add(new MaybeHoldCallForNewCallTransaction(mCallsManager, call));
+        // And request a new focus call update
+        transactions.add(new RequestNewActiveCallTransaction(mCallsManager, call));
 
-        // add t2. send request to set the current call active
-        transactions.add(new RequestFocusTransaction(mCallsManager, call));
-
-        // send off to Transaction Manager to process
-        return new SerialTransaction(transactions);
+        return new SerialTransaction(transactions, mLock);
     }
 
-    private SerialTransaction createSetAnswerTransactions(Call call, int videoState) {
-        // create list for multiple transactions
-        List<VoipCallTransaction> transactions = new ArrayList<>();
-
-        // add t1. hold potential active call
-        transactions.add(new HoldActiveCallForNewCallTransaction(mCallsManager, call));
-
-        // add t2. answer current call
-        transactions.add(new AnswerCallTransaction(mCallsManager, call, videoState));
-
-        // send off to Transaction Manager to process
-        return new SerialTransaction(transactions);
+    private void setDeathRecipient(ICallEventCallback callEventCallback) {
+        try {
+            callEventCallback.asBinder().linkToDeath(mAppDeathListener, 0);
+        } catch (Exception e) {
+            Log.w(TAG, "setDeathRecipient: hit exception=[%s] trying to link binder to death",
+                    e.toString());
+        }
     }
 
     /***
@@ -615,46 +650,11 @@
      *********************************************************************************************
      */
 
-    private SerialTransaction createStartStreamingTransaction(Call call) {
-        // start streaming transaction flow:
-        //     make sure there's no ongoing streaming call --> bind to EXO
-        //                                                 `-> change audio mode
-        // create list for multiple transactions
-        List<VoipCallTransaction> transactions = new ArrayList<>();
-
-        // add t1. make sure no ongoing streaming call
-        transactions.add(new CallStreamingController.QueryCallStreamingTransaction(mCallsManager));
-
-        // create list for parallel transactions
-        List<VoipCallTransaction> subTransactions = new ArrayList<>();
-        // add t2.1 bind to call streaming service
-        subTransactions.add(mStreamingController.getCallStreamingServiceTransaction(
-                mCallsManager.getContext(), this, call));
-        // add t2.2 audio route operations
-        subTransactions.add(new CallStreamingController.AudioInterceptionTransaction(call, true));
-
-        // add t2
-        transactions.add(new ParallelTransaction(subTransactions));
-        // send off to Transaction Manager to process
-        return new SerialTransaction(transactions);
-    }
-
-    private VoipCallTransaction createStopStreamingTransaction(Call call) {
-        // TODO: implement this
-        // Stop streaming transaction flow:
-        List<VoipCallTransaction> transactions = new ArrayList<>();
-
-        // 1. unbind to call streaming service
-        transactions.add(mStreamingController.getUnbindStreamingServiceTransaction());
-        // 2. audio route operations
-        transactions.add(new CallStreamingController.AudioInterceptionTransaction(call, false));
-        return new ParallelTransaction(transactions);
-    }
-
-
     public void stopCallStreaming(Call call) {
+        Log.i(this, "stopCallStreaming; callid=%s", call.getId());
         if (call != null && call.isStreaming()) {
-            VoipCallTransaction stopStreamingTransaction = createStopStreamingTransaction(call);
+            VoipCallTransaction stopStreamingTransaction = mStreamingController
+                    .getStopStreamingTransaction(call, mLock);
             addTransactionsToManager(stopStreamingTransaction, new ResultReceiver(null));
         }
     }
diff --git a/src/com/android/server/telecom/TtyManager.java b/src/com/android/server/telecom/TtyManager.java
index 10e3348..53e917d 100644
--- a/src/com/android/server/telecom/TtyManager.java
+++ b/src/com/android/server/telecom/TtyManager.java
@@ -50,6 +50,7 @@
 
         IntentFilter intentFilter = new IntentFilter(
                 TelecomManager.ACTION_TTY_PREFERRED_MODE_CHANGED);
+        intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
         mContext.registerReceiver(mReceiver, intentFilter,
                 android.Manifest.permission.MODIFY_PHONE_STATE,
                 null, Context.RECEIVER_EXPORTED);
diff --git a/src/com/android/server/telecom/UserUtil.java b/src/com/android/server/telecom/UserUtil.java
index a304401..670ad34 100644
--- a/src/com/android/server/telecom/UserUtil.java
+++ b/src/com/android/server/telecom/UserUtil.java
@@ -16,10 +16,19 @@
 
 package com.android.server.telecom;
 
+import android.app.admin.DevicePolicyManager;
 import android.content.Context;
+import android.content.Intent;
 import android.content.pm.UserInfo;
+import android.net.Uri;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.telecom.Log;
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
+
+import com.android.server.telecom.components.ErrorDialogActivity;
+import com.android.server.telecom.flags.FeatureFlags;
 
 public final class UserUtil {
 
@@ -40,4 +49,90 @@
         UserInfo userInfo = getUserInfoFromUserHandle(context, userHandle);
         return userInfo != null && userInfo.profileGroupId != userInfo.id;
     }
+
+    public static void showErrorDialogForRestrictedOutgoingCall(Context context,
+            int stringId, String tag, String reason) {
+        final Intent intent = new Intent(context, ErrorDialogActivity.class);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.putExtra(ErrorDialogActivity.ERROR_MESSAGE_ID_EXTRA, stringId);
+        context.startActivityAsUser(intent, UserHandle.CURRENT);
+        Log.w(tag, "Rejecting non-emergency phone call because "
+                + reason);
+    }
+
+    public static boolean hasOutgoingCallsUserRestriction(Context context,
+            UserHandle userHandle, Uri handle, boolean isSelfManaged, String tag) {
+        // Set handle for conference calls. Refer to {@link Connection#ADHOC_CONFERENCE_ADDRESS}.
+        if (handle == null) {
+            handle = Uri.parse("tel:conf-factory");
+        }
+
+        if(!isSelfManaged) {
+            // Check DISALLOW_OUTGOING_CALLS restriction. Note: We are skipping this
+            // check in a managed profile user because this check can always be bypassed
+            // by copying and pasting the phone number into the personal dialer.
+            if (!UserUtil.isManagedProfile(context, userHandle)) {
+                // Only emergency calls are allowed for users with the DISALLOW_OUTGOING_CALLS
+                // restriction.
+                if (!TelephonyUtil.shouldProcessAsEmergency(context, handle)) {
+                    final UserManager userManager =
+                            (UserManager) context.getSystemService(Context.USER_SERVICE);
+                    if (userManager.hasBaseUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS,
+                            userHandle)) {
+                        String reason = "of DISALLOW_OUTGOING_CALLS restriction";
+                        showErrorDialogForRestrictedOutgoingCall(context,
+                                R.string.outgoing_call_not_allowed_user_restriction, tag, reason);
+                        return true;
+                    } else if (userManager.hasUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS,
+                            userHandle)) {
+                        final DevicePolicyManager dpm =
+                                context.getSystemService(DevicePolicyManager.class);
+                        if (dpm == null) {
+                            return true;
+                        }
+                        final Intent adminSupportIntent = dpm.createAdminSupportIntent(
+                                UserManager.DISALLOW_OUTGOING_CALLS);
+                        if (adminSupportIntent != null) {
+                            context.startActivityAsUser(adminSupportIntent, userHandle);
+                        }
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Gets the associated user for the given call. Note: this is applicable to all calls except
+     * outgoing calls as the associated user is already based off of the user placing the
+     * call.
+     *
+     * @param phoneAccountRegistrar
+     * @param currentUser Current user profile (this can either be the admin or a secondary/guest
+     *                    user). Note that work profile users fall under the admin user.
+     * @param targetPhoneAccount The phone account to retrieve the {@link UserHandle} from.
+     * @return current user if it isn't the admin or if the work profile is paused for the target
+     * phone account handle user, otherwise return the target phone account handle user. If the
+     * flag is disabled, return the legacy {@link UserHandle}.
+     */
+    public static UserHandle getAssociatedUserForCall(boolean isAssociatedUserFlagEnabled,
+            PhoneAccountRegistrar phoneAccountRegistrar, UserHandle currentUser,
+            PhoneAccountHandle targetPhoneAccount) {
+        if (!isAssociatedUserFlagEnabled) {
+            return targetPhoneAccount.getUserHandle();
+        }
+        // For multi-user phone accounts, associate the call with the profile receiving/placing
+        // the call. For SIM accounts (that are assigned to specific users), the user association
+        // will be placed on the target phone account handle user.
+        PhoneAccount account = phoneAccountRegistrar.getPhoneAccountUnchecked(targetPhoneAccount);
+        if (account != null) {
+            return account.hasCapabilities(PhoneAccount.CAPABILITY_MULTI_USER)
+                    ? currentUser
+                    : targetPhoneAccount.getUserHandle();
+        }
+        // If target phone account handle is null or account cannot be found,
+        // return the current user.
+        return currentUser;
+    }
 }
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java b/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
index 473e7b9..27e5a7d 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
@@ -28,10 +28,15 @@
 import android.media.AudioManager;
 import android.media.AudioDeviceInfo;
 import android.media.audio.common.AudioDevice;
+import android.os.Bundle;
 import android.telecom.Log;
+import android.util.ArraySet;
 import android.util.LocalLog;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.telecom.CallAudioCommunicationDeviceTracker;
+import com.android.server.telecom.flags.FeatureFlags;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -58,7 +63,8 @@
             public void onGroupStatusChanged(int groupId, int groupStatus) {}
             @Override
             public void onGroupNodeAdded(BluetoothDevice device, int groupId) {
-                Log.i(this, device.getAddress() + " group added " + groupId);
+                Log.i(this, (device == null ? "device is null" : device.getAddress())
+                        + " group added " + groupId);
                 if (device == null || groupId == BluetoothLeAudio.GROUP_ID_INVALID) {
                     Log.w(this, "invalid parameter");
                     return;
@@ -70,6 +76,8 @@
             }
             @Override
             public void onGroupNodeRemoved(BluetoothDevice device, int groupId) {
+                Log.i(this, (device == null ? "device is null" : device.getAddress())
+                        + " group removed " + groupId);
                 if (device == null || groupId == BluetoothLeAudio.GROUP_ID_INVALID) {
                     Log.w(this, "invalid parameter");
                     return;
@@ -85,7 +93,7 @@
             new BluetoothProfile.ServiceListener() {
                 @Override
                 public void onServiceConnected(int profile, BluetoothProfile proxy) {
-                    Log.startSession("BMSL.oSC");
+                    Log.startSession("BPSL.oSC");
                     try {
                         synchronized (mLock) {
                             String logString;
@@ -101,9 +109,13 @@
                                 logString = "Got BluetoothLeAudio: "
                                         + mBluetoothLeAudioService;
                                 if (!mLeAudioCallbackRegistered) {
-                                    mBluetoothLeAudioService.registerCallback(
-                                                mExecutor, mLeAudioCallbacks);
-                                    mLeAudioCallbackRegistered = true;
+                                    try {
+                                        mBluetoothLeAudioService.registerCallback(
+                                                    mExecutor, mLeAudioCallbacks);
+                                        mLeAudioCallbackRegistered = true;
+                                    } catch (IllegalStateException e) {
+                                        logString += ", but Bluetooth is down";
+                                    }
                                 }
                             } else {
                                 logString = "Connected to non-requested bluetooth service." +
@@ -119,7 +131,7 @@
 
                 @Override
                 public void onServiceDisconnected(int profile) {
-                    Log.startSession("BMSL.oSD");
+                    Log.startSession("BPSL.oSD");
                     try {
                         synchronized (mLock) {
                             LinkedHashMap<String, BluetoothDevice> lostServiceDevices;
@@ -174,6 +186,12 @@
             new LinkedHashMap<>();
     private final LinkedHashMap<BluetoothDevice, Integer> mGroupsByDevice =
             new LinkedHashMap<>();
+    private final ArrayList<LinkedHashMap<String, BluetoothDevice>>
+            mDevicesByAddressMaps = new ArrayList<LinkedHashMap<String, BluetoothDevice>>(); {
+        mDevicesByAddressMaps.add(mHfpDevicesByAddress);
+        mDevicesByAddressMaps.add(mHearingAidDevicesByAddress);
+        mDevicesByAddressMaps.add(mLeAudioDevicesByAddress);
+    }
     private int mGroupIdActive = BluetoothLeAudio.GROUP_ID_INVALID;
     private int mGroupIdPending = BluetoothLeAudio.GROUP_ID_INVALID;
     private final LocalLog mLocalLog = new LocalLog(20);
@@ -194,8 +212,12 @@
     private BluetoothAdapter mBluetoothAdapter;
     private AudioManager mAudioManager;
     private Executor mExecutor;
+    private CallAudioCommunicationDeviceTracker mCommunicationDeviceTracker;
+    private FeatureFlags mFeatureFlags;
 
-    public BluetoothDeviceManager(Context context, BluetoothAdapter bluetoothAdapter) {
+    public BluetoothDeviceManager(Context context, BluetoothAdapter bluetoothAdapter,
+            CallAudioCommunicationDeviceTracker communicationDeviceTracker,
+            FeatureFlags featureFlags) {
         if (bluetoothAdapter != null) {
             mBluetoothAdapter = bluetoothAdapter;
             bluetoothAdapter.getProfileProxy(context, mBluetoothProfileServiceListener,
@@ -206,6 +228,8 @@
                     BluetoothProfile.LE_AUDIO);
             mAudioManager = context.getSystemService(AudioManager.class);
             mExecutor = context.getMainExecutor();
+            mCommunicationDeviceTracker = communicationDeviceTracker;
+            mFeatureFlags = featureFlags;
         }
     }
 
@@ -234,18 +258,38 @@
     }
 
     public int getNumConnectedDevices() {
-        synchronized (mLock) {
-            return mHfpDevicesByAddress.size() +
-                    mHearingAidDevicesByAddress.size() +
-                    getLeAudioConnectedDevices().size();
-        }
+        return getConnectedDevices().size();
     }
 
     public Collection<BluetoothDevice> getConnectedDevices() {
         synchronized (mLock) {
-            ArrayList<BluetoothDevice> result = new ArrayList<>(mHfpDevicesByAddress.values());
+            ArraySet<BluetoothDevice> result = new ArraySet<>();
+
+            // Set storing the group ids of all dual mode audio devices to de-dupe them
+            Set<Integer> dualModeGroupIds = new ArraySet<>();
+            for (BluetoothDevice hfpDevice: mHfpDevicesByAddress.values()) {
+                result.add(hfpDevice);
+                if (mBluetoothLeAudioService == null) {
+                    continue;
+                }
+                int groupId = mBluetoothLeAudioService.getGroupId(hfpDevice);
+                if (groupId != BluetoothLeAudio.GROUP_ID_INVALID) {
+                    dualModeGroupIds.add(groupId);
+                }
+            }
+
             result.addAll(mHearingAidDevicesByAddress.values());
-            result.addAll(getLeAudioConnectedDevices());
+            if (mBluetoothLeAudioService == null) {
+                return Collections.unmodifiableCollection(result);
+            }
+            for (BluetoothDevice leAudioDevice: getLeAudioConnectedDevices()) {
+                // Exclude dual mode audio devices included from the HFP devices list
+                int groupId = mBluetoothLeAudioService.getGroupId(leAudioDevice);
+                if (groupId != BluetoothLeAudio.GROUP_ID_INVALID
+                        && !dualModeGroupIds.contains(groupId)) {
+                    result.add(leAudioDevice);
+                }
+            }
             return Collections.unmodifiableCollection(result);
         }
     }
@@ -253,9 +297,9 @@
     // Same as getConnectedDevices except it filters out the hearing aid devices that are linked
     // together by their hiSyncId.
     public Collection<BluetoothDevice> getUniqueConnectedDevices() {
-        ArrayList<BluetoothDevice> result;
+        ArraySet<BluetoothDevice> result;
         synchronized (mLock) {
-            result = new ArrayList<>(mHfpDevicesByAddress.values());
+            result = new ArraySet<>(mHfpDevicesByAddress.values());
         }
         Set<Long> seenHiSyncIds = new LinkedHashSet<>();
         // Add the left-most active device to the seen list so that we match up with the list
@@ -330,8 +374,10 @@
         }
     }
 
-    void onDeviceConnected(BluetoothDevice device, int deviceType) {
+    @VisibleForTesting
+    public void onDeviceConnected(BluetoothDevice device, int deviceType) {
         synchronized (mLock) {
+            clearDeviceFromDeviceMaps(device.getAddress());
             LinkedHashMap<String, BluetoothDevice> targetDeviceMap;
             if (deviceType == DEVICE_TYPE_LE_AUDIO) {
                 if (mBluetoothLeAudioService == null) {
@@ -367,12 +413,20 @@
                 return;
             }
             if (!targetDeviceMap.containsKey(device.getAddress())) {
+                Log.i(this, "Adding device with address: " + device + " and devicetype="
+                        + getDeviceTypeString(deviceType));
                 targetDeviceMap.put(device.getAddress(), device);
                 mBluetoothRouteManager.onDeviceAdded(device.getAddress());
             }
         }
     }
 
+    void clearDeviceFromDeviceMaps(String deviceAddress) {
+        for (LinkedHashMap<String, BluetoothDevice> deviceMap : mDevicesByAddressMaps) {
+            deviceMap.remove(deviceAddress);
+        }
+    }
+
     void onDeviceDisconnected(BluetoothDevice device, int deviceType) {
         mLocalLog.log("Device disconnected -- address: " + device.getAddress() + " deviceType: "
                 + deviceType);
@@ -391,6 +445,8 @@
                 return;
             }
             if (targetDeviceMap.containsKey(device.getAddress())) {
+                Log.i(this, "Removing device with address: " + device + " and devicetype="
+                        + getDeviceTypeString(deviceType));
                 targetDeviceMap.remove(device.getAddress());
                 mBluetoothRouteManager.onDeviceLost(device.getAddress());
             }
@@ -398,9 +454,14 @@
     }
 
     public void disconnectAudio() {
-        disconnectSco();
-        clearLeAudioCommunicationDevice();
-        clearHearingAidCommunicationDevice();
+        if (mFeatureFlags.callAudioCommunicationDeviceRefactor()) {
+            mCommunicationDeviceTracker.clearBtCommunicationDevice();
+            disconnectSco();
+        } else {
+            disconnectSco();
+            clearLeAudioCommunicationDevice();
+            clearHearingAidCommunicationDevice();
+        }
     }
 
     public void disconnectSco() {
@@ -568,50 +629,78 @@
     // Connect audio to the bluetooth device at address, checking to see whether it's
     // le audio, hearing aid or a HFP device, and using the proper BT API.
     public boolean connectAudio(String address, boolean switchingBtDevices) {
+        int callProfile = BluetoothProfile.LE_AUDIO;
+        Log.i(this, "Telecomm connecting audio to device: " + address);
+        BluetoothDevice device = null;
         if (mLeAudioDevicesByAddress.containsKey(address)) {
+            Log.i(this, "Telecomm found LE Audio device for address: " + address);
             if (mBluetoothLeAudioService == null) {
                 Log.w(this, "Attempting to turn on audio when the le audio service is null");
                 return false;
             }
-            BluetoothDevice device = mLeAudioDevicesByAddress.get(address);
-            if (mBluetoothAdapter.setActiveDevice(
-                    device, BluetoothAdapter.ACTIVE_DEVICE_ALL)) {
-
-                /* ACTION_ACTIVE_DEVICE_CHANGED intent will trigger setting communication device.
-                 * Only after receiving ACTION_ACTIVE_DEVICE_CHANGED it is known that device that
-                 * will be audio switched to is available to be choose as communication device */
-                if (!switchingBtDevices) {
-                    return setLeAudioCommunicationDevice();
-                }
-
-                return true;
-            }
-            return false;
+            device = mLeAudioDevicesByAddress.get(address);
+            callProfile = BluetoothProfile.LE_AUDIO;
         } else if (mHearingAidDevicesByAddress.containsKey(address)) {
+            Log.i(this, "Telecomm found hearing aid device for address: " + address);
             if (mBluetoothHearingAid == null) {
                 Log.w(this, "Attempting to turn on audio when the hearing aid service is null");
                 return false;
             }
-            if (mBluetoothAdapter.setActiveDevice(
-                    mHearingAidDevicesByAddress.get(address),
-                    BluetoothAdapter.ACTIVE_DEVICE_ALL)) {
-
-                /* ACTION_ACTIVE_DEVICE_CHANGED intent will trigger setting communication device.
-                 * Only after receiving ACTION_ACTIVE_DEVICE_CHANGED it is known that device that
-                 * will be audio switched to is available to be choose as communication device */
-                if (!switchingBtDevices) {
-                    return setHearingAidCommunicationDevice();
-                }
-
-                return true;
-            }
-            return false;
+            device = mHearingAidDevicesByAddress.get(address);
+            callProfile = BluetoothProfile.HEARING_AID;
         } else if (mHfpDevicesByAddress.containsKey(address)) {
-            BluetoothDevice device = mHfpDevicesByAddress.get(address);
+            Log.i(this, "Telecomm found HFP device for address: " + address);
             if (mBluetoothHeadset == null) {
                 Log.w(this, "Attempting to turn on audio when the headset service is null");
                 return false;
             }
+            device = mHfpDevicesByAddress.get(address);
+            callProfile = BluetoothProfile.HEADSET;
+        }
+
+        if (device == null) {
+            Log.w(this, "No active profiles for Bluetooth address=" + address);
+            return false;
+        }
+
+        Bundle preferredAudioProfiles = mBluetoothAdapter.getPreferredAudioProfiles(device);
+        if (preferredAudioProfiles != null && !preferredAudioProfiles.isEmpty()
+            && preferredAudioProfiles.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX) != 0) {
+            Log.i(this, "Preferred duplex profile for device=" + address + " is "
+                + preferredAudioProfiles.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX));
+            callProfile = preferredAudioProfiles.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX);
+        }
+
+        if (callProfile == BluetoothProfile.LE_AUDIO) {
+            if (mBluetoothAdapter.setActiveDevice(
+                    device, BluetoothAdapter.ACTIVE_DEVICE_ALL)) {
+                /* ACTION_ACTIVE_DEVICE_CHANGED intent will trigger setting communication device.
+                 * Only after receiving ACTION_ACTIVE_DEVICE_CHANGED it is known that device that
+                 * will be audio switched to is available to be choose as communication device */
+                if (!switchingBtDevices) {
+                    return mFeatureFlags.callAudioCommunicationDeviceRefactor() ?
+                            mCommunicationDeviceTracker.setCommunicationDevice(
+                                    AudioDeviceInfo.TYPE_BLE_HEADSET, device)
+                            : setLeAudioCommunicationDevice();
+                }
+                return true;
+            }
+            return false;
+        } else if (callProfile == BluetoothProfile.HEARING_AID) {
+            if (mBluetoothAdapter.setActiveDevice(device, BluetoothAdapter.ACTIVE_DEVICE_ALL)) {
+                /* ACTION_ACTIVE_DEVICE_CHANGED intent will trigger setting communication device.
+                 * Only after receiving ACTION_ACTIVE_DEVICE_CHANGED it is known that device that
+                 * will be audio switched to is available to be choose as communication device */
+                if (!switchingBtDevices) {
+                    return mFeatureFlags.callAudioCommunicationDeviceRefactor() ?
+                            mCommunicationDeviceTracker.setCommunicationDevice(
+                                    AudioDeviceInfo.TYPE_HEARING_AID, null)
+                            : setHearingAidCommunicationDevice();
+                }
+                return true;
+            }
+            return false;
+        } else if (callProfile == BluetoothProfile.HEADSET) {
             boolean success = mBluetoothAdapter.setActiveDevice(device,
                 BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL);
             if (!success) {
@@ -647,7 +736,9 @@
     }
 
     public boolean isInbandRingingEnabled() {
-        BluetoothDevice activeDevice = mBluetoothRouteManager.getBluetoothAudioConnectedDevice();
+        // Get the inband ringing enabled status of expected BT device to route call audio instead
+        // of using the address of currently connected device.
+        BluetoothDevice activeDevice = mBluetoothRouteManager.getMostRecentlyReportedActiveDevice();
         Log.i(this, "isInbandRingingEnabled: activeDevice: " + activeDevice);
         if (mBluetoothRouteManager.isCachedLeAudioDevice(activeDevice)) {
             if (mBluetoothLeAudioService == null) {
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java b/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
index 7966f73..235ba56 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
@@ -17,12 +17,14 @@
 package com.android.server.telecom.bluetooth;
 
 import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadset;
 import android.bluetooth.BluetoothHearingAid;
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothLeAudio;
 import android.content.Context;
+import android.media.AudioDeviceInfo;
 import android.os.Message;
 import android.telecom.Log;
 import android.telecom.Logging.Session;
@@ -33,8 +35,10 @@
 import com.android.internal.util.IState;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
+import com.android.server.telecom.CallAudioCommunicationDeviceTracker;
 import com.android.server.telecom.TelecomSystem;
 import com.android.server.telecom.Timeouts;
+import com.android.server.telecom.flags.FeatureFlags;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -43,6 +47,7 @@
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
@@ -78,6 +83,7 @@
         void onBluetoothActiveDevicePresent();
         void onBluetoothActiveDeviceGone();
         void onBluetoothAudioConnected();
+        void onBluetoothAudioConnecting();
         void onBluetoothAudioDisconnected();
         /**
          * This gets called when we get an unexpected state change from Bluetooth. Their stack does
@@ -131,7 +137,8 @@
         @Override
         public void enter() {
             BluetoothDevice erroneouslyConnectedDevice = getBluetoothAudioConnectedDevice();
-            if (erroneouslyConnectedDevice != null) {
+            if (erroneouslyConnectedDevice != null &&
+                !erroneouslyConnectedDevice.equals(mHearingAidActiveDeviceCache)) {
                 Log.w(LOG_TAG, "Entering AudioOff state but device %s appears to be connected. " +
                         "Switching to audio-on state for that device.", erroneouslyConnectedDevice);
                 // change this to just transition to the new audio on state
@@ -231,8 +238,7 @@
             sendMessageDelayed(CONNECTION_TIMEOUT, args,
                     mTimeoutsAdapter.getBluetoothPendingTimeoutMillis(
                             mContext.getContentResolver()));
-            // Pretend like audio is connected when communicating w/ CARSM.
-            mListener.onBluetoothAudioConnected();
+            mListener.onBluetoothAudioConnecting();
         }
 
         @Override
@@ -250,6 +256,27 @@
             SomeArgs args = (SomeArgs) msg.obj;
             String address = (String) args.arg2;
             boolean switchingBtDevices = !Objects.equals(mDeviceAddress, address);
+
+            if (switchingBtDevices == true) { // check if it is an hearing aid pair
+                BluetoothAdapter bluetoothAdapter = mDeviceManager.getBluetoothAdapter();
+                if (bluetoothAdapter != null) {
+                    List<BluetoothDevice> activeHearingAids =
+                      bluetoothAdapter.getActiveDevices(BluetoothProfile.HEARING_AID);
+                    for (BluetoothDevice hearingAid : activeHearingAids) {
+                        if (hearingAid != null) {
+                            String hearingAidAddress = hearingAid.getAddress();
+                            if (hearingAidAddress != null) {
+                                if (hearingAidAddress.equals(address) ||
+                                    hearingAidAddress.equals(mDeviceAddress)) {
+                                    switchingBtDevices = false;
+                                    break;
+                                }
+                            }
+                        }
+                    }
+
+                }
+            }
             try {
                 switch (msg.what) {
                     case NEW_DEVICE_CONNECTED:
@@ -391,8 +418,13 @@
                         String actualAddress = connectBtAudio(address,
                             true /* switchingBtDevices*/);
                         if (actualAddress != null) {
-                            transitionTo(getConnectingStateForAddress(address,
-                                    "AudioConnected/CONNECT_BT"));
+                            if (mFeatureFlags.useActualAddressToEnterConnectingState()) {
+                                transitionTo(getConnectingStateForAddress(actualAddress,
+                                        "AudioConnected/CONNECT_BT"));
+                            } else {
+                                transitionTo(getConnectingStateForAddress(address,
+                                        "AudioConnected/CONNECT_BT"));
+                            }
                         } else {
                             Log.w(LOG_TAG, "Tried to connect to %s but failed" +
                                     " to connect to any BT device.", (String) args.arg2);
@@ -469,15 +501,21 @@
     private BluetoothDevice mHearingAidActiveDeviceCache = null;
     private BluetoothDevice mLeAudioActiveDeviceCache = null;
     private BluetoothDevice mMostRecentlyReportedActiveDevice = null;
+    private CallAudioCommunicationDeviceTracker mCommunicationDeviceTracker;
+    private FeatureFlags mFeatureFlags;
 
     public BluetoothRouteManager(Context context, TelecomSystem.SyncRoot lock,
-            BluetoothDeviceManager deviceManager, Timeouts.Adapter timeoutsAdapter) {
+            BluetoothDeviceManager deviceManager, Timeouts.Adapter timeoutsAdapter,
+            CallAudioCommunicationDeviceTracker communicationDeviceTracker,
+            FeatureFlags featureFlags) {
         super(BluetoothRouteManager.class.getSimpleName());
         mContext = context;
         mLock = lock;
         mDeviceManager = deviceManager;
         mDeviceManager.setBluetoothRouteManager(this);
         mTimeoutsAdapter = timeoutsAdapter;
+        mCommunicationDeviceTracker = communicationDeviceTracker;
+        mFeatureFlags = featureFlags;
 
         mAudioOffState = new AudioOffState();
         addState(mAudioOffState);
@@ -621,12 +659,22 @@
         if (deviceType == BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO) {
             mLeAudioActiveDeviceCache = device;
             if (device == null) {
-                mDeviceManager.clearLeAudioCommunicationDevice();
+                if (mFeatureFlags.callAudioCommunicationDeviceRefactor()) {
+                    mCommunicationDeviceTracker.clearCommunicationDevice(
+                            AudioDeviceInfo.TYPE_BLE_HEADSET);
+                } else {
+                    mDeviceManager.clearLeAudioCommunicationDevice();
+                }
             }
         } else if (deviceType == BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID) {
             mHearingAidActiveDeviceCache = device;
             if (device == null) {
-                mDeviceManager.clearHearingAidCommunicationDevice();
+                if (mFeatureFlags.callAudioCommunicationDeviceRefactor()) {
+                    mCommunicationDeviceTracker.clearCommunicationDevice(
+                            AudioDeviceInfo.TYPE_HEARING_AID);
+                } else {
+                    mDeviceManager.clearHearingAidCommunicationDevice();
+                }
             }
         } else if (deviceType == BluetoothDeviceManager.DEVICE_TYPE_HEADSET) {
             mHfpActiveDeviceCache = device;
@@ -645,6 +693,10 @@
         }
     }
 
+    public BluetoothDevice getMostRecentlyReportedActiveDevice() {
+        return mMostRecentlyReportedActiveDevice;
+    }
+
     public boolean hasBtActiveDevice() {
         return mLeAudioActiveDeviceCache != null ||
                 mHearingAidActiveDeviceCache != null ||
@@ -663,6 +715,33 @@
         return mDeviceManager.getUniqueConnectedDevices();
     }
 
+    public boolean isWatch(BluetoothDevice device) {
+        if (device == null) {
+            Log.i(this, "isWatch: device is null. Returning false");
+            return false;
+        }
+
+        BluetoothClass deviceClass = device.getBluetoothClass();
+        if (deviceClass != null && deviceClass.getDeviceClass()
+                == BluetoothClass.Device.WEARABLE_WRIST_WATCH) {
+            Log.i(this, "isWatch: bluetooth class component is a WEARABLE_WRIST_WATCH.");
+            return true;
+        }
+
+        // Check metadata
+        byte[] deviceType = device.getMetadata(BluetoothDevice.METADATA_DEVICE_TYPE);
+        if (deviceType == null) {
+            return false;
+        }
+        String deviceTypeStr = new String(deviceType);
+        if (deviceTypeStr.equals(BluetoothDevice.DEVICE_TYPE_WATCH)) {
+            Log.i(this, "isWatch: bluetooth device type is DEVICE_TYPE_WATCH.");
+            return true;
+        }
+
+        return false;
+    }
+
     private String connectBtAudio(String address, boolean switchingBtDevices) {
         return connectBtAudio(address, 0, switchingBtDevices);
     }
@@ -692,10 +771,19 @@
                 ? address : getActiveDeviceAddress();
         if (actualAddress == null) {
             Log.i(this, "No device specified and BT stack has no active device."
-                    + " Using arbitrary device");
+                    + " Using arbitrary device - except watch");
             if (deviceList.size() > 0) {
-                actualAddress = deviceList.iterator().next().getAddress();
-            } else {
+                for (BluetoothDevice device : deviceList) {
+                    if (mFeatureFlags.ignoreAutoRouteToWatchDevice() && isWatch(device)) {
+                        Log.i(this, "Skipping a watch device: " + device);
+                        continue;
+                    }
+                    actualAddress = device.getAddress();
+                    break;
+                }
+            }
+
+            if (actualAddress == null) {
                 Log.i(this, "No devices available at all. Not connecting.");
                 return null;
             }
@@ -797,21 +885,37 @@
             }
         }
 
+        boolean isHearingAidSetForCommunication =
+                mFeatureFlags.callAudioCommunicationDeviceRefactor()
+                ? mCommunicationDeviceTracker.isAudioDeviceSetForType(
+                        AudioDeviceInfo.TYPE_HEARING_AID)
+                : mDeviceManager.isHearingAidSetAsCommunicationDevice();
         if (bluetoothHearingAid != null) {
-            if (mDeviceManager.isHearingAidSetAsCommunicationDevice()) {
-                for (BluetoothDevice device : bluetoothAdapter.getActiveDevices(
-                        BluetoothProfile.HEARING_AID)) {
-                    if (device != null) {
-                        hearingAidActiveDevice = device;
-                        activeDevices++;
-                        break;
+            if (isHearingAidSetForCommunication) {
+                List<BluetoothDevice> hearingAidsActiveDevices = bluetoothAdapter.getActiveDevices(
+                        BluetoothProfile.HEARING_AID);
+                if (hearingAidsActiveDevices.contains(mHearingAidActiveDeviceCache)) {
+                    hearingAidActiveDevice = mHearingAidActiveDeviceCache;
+                    activeDevices++;
+                } else {
+                    for (BluetoothDevice device : hearingAidsActiveDevices) {
+                        if (device != null) {
+                            hearingAidActiveDevice = device;
+                            activeDevices++;
+                            break;
+                        }
                     }
                 }
             }
         }
 
+        boolean isLeAudioSetForCommunication =
+                mFeatureFlags.callAudioCommunicationDeviceRefactor()
+                        ? mCommunicationDeviceTracker.isAudioDeviceSetForType(
+                        AudioDeviceInfo.TYPE_BLE_HEADSET)
+                        : mDeviceManager.isLeAudioCommunicationDevice();
         if (bluetoothLeAudio != null) {
-            if (mDeviceManager.isLeAudioCommunicationDevice()) {
+            if (isLeAudioSetForCommunication) {
                 for (BluetoothDevice device : bluetoothAdapter.getActiveDevices(
                         BluetoothProfile.LE_AUDIO)) {
                     if (device != null) {
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java b/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
index e5fe971..d2521ac 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
@@ -16,6 +16,7 @@
 
 package com.android.server.telecom.bluetooth;
 
+import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadset;
 import android.bluetooth.BluetoothHearingAid;
@@ -25,10 +26,14 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.media.AudioDeviceInfo;
+import android.os.Bundle;
 import android.telecom.Log;
 import android.telecom.Logging.Session;
 
 import com.android.internal.os.SomeArgs;
+import com.android.server.telecom.CallAudioCommunicationDeviceTracker;
+import com.android.server.telecom.flags.FeatureFlags;
 
 import static com.android.server.telecom.bluetooth.BluetoothRouteManager.BT_AUDIO_IS_ON;
 import static com.android.server.telecom.bluetooth.BluetoothRouteManager.BT_AUDIO_LOST;
@@ -46,6 +51,7 @@
         INTENT_FILTER.addAction(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED);
         INTENT_FILTER.addAction(BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED);
         INTENT_FILTER.addAction(BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED);
+        INTENT_FILTER.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
     }
 
     // If not in a call, BSR won't listen to the Bluetooth stack's HFP on/off messages, since
@@ -53,6 +59,8 @@
     private boolean mIsInCall = false;
     private final BluetoothRouteManager mBluetoothRouteManager;
     private final BluetoothDeviceManager mBluetoothDeviceManager;
+    private CallAudioCommunicationDeviceTracker mCommunicationDeviceTracker;
+    private FeatureFlags mFeatureFlags;
 
     public void onReceive(Context context, Intent intent) {
         Log.startSession("BSR.oR");
@@ -83,7 +91,7 @@
                 intent.getIntExtra(BluetoothHeadset.EXTRA_STATE,
                         BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
         BluetoothDevice device =
-                intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+                intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, BluetoothDevice.class);
         if (device == null) {
             Log.w(LOG_TAG, "Got null device from broadcast. " +
                     "Ignoring.");
@@ -114,7 +122,7 @@
         int bluetoothHeadsetState = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE,
                 BluetoothHeadset.STATE_DISCONNECTED);
         BluetoothDevice device =
-                intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+                intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, BluetoothDevice.class);
 
         if (device == null) {
             Log.w(LOG_TAG, "Got null device from broadcast. " +
@@ -148,7 +156,7 @@
 
     private void handleActiveDeviceChanged(Intent intent) {
         BluetoothDevice device =
-                intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+                intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, BluetoothDevice.class);
 
         int deviceType;
         if (BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED.equals(intent.getAction())) {
@@ -180,19 +188,49 @@
                 }
                 args.arg2 = device.getAddress();
 
+                boolean usePreferredAudioProfile = false;
+                BluetoothAdapter bluetoothAdapter = mBluetoothDeviceManager.getBluetoothAdapter();
+                int preferredDuplexProfile = BluetoothProfile.LE_AUDIO;
+                if (bluetoothAdapter != null) {
+                    Bundle preferredAudioProfiles = bluetoothAdapter.getPreferredAudioProfiles(
+                            device);
+                    if (preferredAudioProfiles != null && !preferredAudioProfiles.isEmpty()
+                            && preferredAudioProfiles.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX)
+                            != 0) {
+                        Log.i(this, "Preferred duplex profile for device=" + device + " is "
+                                + preferredAudioProfiles.getInt(
+                                BluetoothAdapter.AUDIO_MODE_DUPLEX));
+                        usePreferredAudioProfile = true;
+                        preferredDuplexProfile =
+                                preferredAudioProfiles.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX);
+                    }
+                }
+
                 if (deviceType == BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO) {
                     /* In Le Audio case, once device got Active, the Telecom needs to make sure it
                      * is set as communication device before we can say that BT_AUDIO_IS_ON
                      */
-                    if (!mBluetoothDeviceManager.setLeAudioCommunicationDevice()) {
+                    boolean isLeAudioSetForCommunication =
+                            mFeatureFlags.callAudioCommunicationDeviceRefactor()
+                                    ? mCommunicationDeviceTracker.setCommunicationDevice(
+                                    AudioDeviceInfo.TYPE_BLE_HEADSET, device)
+                                    : mBluetoothDeviceManager.setLeAudioCommunicationDevice();
+                    if ((!usePreferredAudioProfile
+                            || preferredDuplexProfile == BluetoothProfile.LE_AUDIO)
+                            && !isLeAudioSetForCommunication) {
                         Log.w(LOG_TAG,
                                 "Device %s cannot be use as LE audio communication device.",
                                 device);
                         return;
                     }
                 } else {
+                    boolean isHearingAidSetForCommunication =
+                            mFeatureFlags.callAudioCommunicationDeviceRefactor()
+                            ? mCommunicationDeviceTracker.setCommunicationDevice(
+                                    AudioDeviceInfo.TYPE_HEARING_AID, null)
+                            : mBluetoothDeviceManager.setHearingAidCommunicationDevice();
                     /* deviceType == BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID */
-                    if (!mBluetoothDeviceManager.setHearingAidCommunicationDevice()) {
+                    if (!isHearingAidSetForCommunication) {
                         Log.w(LOG_TAG,
                                 "Device %s cannot be use as hearing aid communication device.",
                                 device);
@@ -209,9 +247,13 @@
     }
 
     public BluetoothStateReceiver(BluetoothDeviceManager deviceManager,
-            BluetoothRouteManager routeManager) {
+            BluetoothRouteManager routeManager,
+            CallAudioCommunicationDeviceTracker communicationDeviceTracker,
+            FeatureFlags featureFlags) {
         mBluetoothDeviceManager = deviceManager;
         mBluetoothRouteManager = routeManager;
+        mCommunicationDeviceTracker = communicationDeviceTracker;
+        mFeatureFlags = featureFlags;
     }
 
     public void setIsInCall(boolean isInCall) {
diff --git a/src/com/android/server/telecom/callfiltering/BlockCheckerFilter.java b/src/com/android/server/telecom/callfiltering/BlockCheckerFilter.java
index 36f2077..64060c8 100644
--- a/src/com/android/server/telecom/callfiltering/BlockCheckerFilter.java
+++ b/src/com/android/server/telecom/callfiltering/BlockCheckerFilter.java
@@ -126,6 +126,7 @@
                     .setShouldReject(true)
                     .setShouldAddToCallLog(true)
                     .setShouldShowNotification(false)
+                    .setShouldSilence(true)
                     .setCallBlockReason(getBlockReason(blockStatus))
                     .setCallScreeningAppName(null)
                     .setCallScreeningComponentName(null)
diff --git a/src/com/android/server/telecom/callfiltering/BlockedNumbersAdapter.java b/src/com/android/server/telecom/callfiltering/BlockedNumbersAdapter.java
new file mode 100644
index 0000000..b8658d8
--- /dev/null
+++ b/src/com/android/server/telecom/callfiltering/BlockedNumbersAdapter.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 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;
+
+/**
+ * Adapter interface that wraps methods from
+ * {@link android.provider.BlockedNumberContract.SystemContract} and
+ * {@link com.android.server.telecom.settings.BlockedNumbersUtil} to make things testable.
+ */
+public interface BlockedNumbersAdapter {
+    boolean shouldShowEmergencyCallNotification (Context context);
+    void updateEmergencyCallNotification(Context context, boolean showNotification);
+}
diff --git a/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java b/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java
index f542fa2..f07c0aa 100644
--- a/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java
+++ b/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java
@@ -319,7 +319,7 @@
         CallScreeningServiceConnection connection = new CallScreeningServiceConnection(
                 resultFuture);
         if (!CallScreeningServiceHelper.bindCallScreeningService(mContext,
-                mCall.getUserHandleFromTargetPhoneAccount(), mPackageName, connection)) {
+                mCall.getAssociatedUser(), mPackageName, connection)) {
             Log.i(this, "Call screening service binding failed.");
             resultFuture.complete(mPriorStageResult);
         } else {
diff --git a/src/com/android/server/telecom/callfiltering/DndCallFilter.java b/src/com/android/server/telecom/callfiltering/DndCallFilter.java
index a100bad..f6ed646 100644
--- a/src/com/android/server/telecom/callfiltering/DndCallFilter.java
+++ b/src/com/android/server/telecom/callfiltering/DndCallFilter.java
@@ -55,9 +55,6 @@
         // query NotificationManager to determine if the call should ring or be suppressed
         boolean shouldSuppress = !mRinger.shouldRingForContact(mCall);
 
-        // store the shouldSuppress value in the call object which will be passed to InCallServices
-        mCall.setCallIsSuppressedByDoNotDisturb(shouldSuppress);
-
         // end timer
         Log.addEvent(mCall, LogUtils.Events.DND_PRE_CHECK_COMPLETED, shouldSuppress);
 
diff --git a/src/com/android/server/telecom/callfiltering/IncomingCallFilterGraphProvider.java b/src/com/android/server/telecom/callfiltering/IncomingCallFilterGraphProvider.java
new file mode 100644
index 0000000..1501280
--- /dev/null
+++ b/src/com/android/server/telecom/callfiltering/IncomingCallFilterGraphProvider.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 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 com.android.server.telecom.Call;
+import com.android.server.telecom.TelecomSystem;
+import com.android.server.telecom.Timeouts;
+
+/**
+ * Interface to provide a {@link IncomingCallFilterGraph}. This class serve for unit test purpose
+ * to mock an incoming call filter graph in test code.
+ */
+public interface IncomingCallFilterGraphProvider {
+
+
+    /**
+     * Provide a {@link  IncomingCallFilterGraph}
+     * @param call The call for the filters.
+     * @param listener Callback object to trigger when filtering is done.
+     * @param context An android context.
+     * @param timeoutsAdapter Adapter to provide timeout value for call filtering.
+     * @param lock Telecom lock.
+     * @return
+     */
+    IncomingCallFilterGraph createGraph(Call call, CallFilterResultCallback listener,
+            Context context,
+            Timeouts.Adapter timeoutsAdapter, TelecomSystem.SyncRoot lock);
+}
diff --git a/src/com/android/server/telecom/callredirection/CallRedirectionProcessor.java b/src/com/android/server/telecom/callredirection/CallRedirectionProcessor.java
index 039526b..05e73d5 100644
--- a/src/com/android/server/telecom/callredirection/CallRedirectionProcessor.java
+++ b/src/com/android/server/telecom/callredirection/CallRedirectionProcessor.java
@@ -162,6 +162,33 @@
                     Log.endSession();
                 }
             }
+
+            @Override
+            public void onNullBinding(ComponentName componentName) {
+                // Make sure we unbind the service if onBind returns null
+                Log.startSession("CRSC.oNB");
+                try {
+                    synchronized (mTelecomLock) {
+                        finishCallRedirection();
+                    }
+                } finally {
+                    Log.endSession();
+                }
+            }
+
+            @Override
+            public void onBindingDied(ComponentName componentName) {
+                // Make sure we unbind the service if binding died to avoid background stating
+                // activity leaks
+                Log.startSession("CRSC.oBD");
+                try {
+                    synchronized (mTelecomLock) {
+                        finishCallRedirection();
+                    }
+                } finally {
+                    Log.endSession();
+                }
+            }
         }
 
         private class CallRedirectionAdapter extends ICallRedirectionAdapter.Stub {
diff --git a/src/com/android/server/telecom/components/TelecomService.java b/src/com/android/server/telecom/components/TelecomService.java
index 3881154..9287d33 100644
--- a/src/com/android/server/telecom/components/TelecomService.java
+++ b/src/com/android/server/telecom/components/TelecomService.java
@@ -26,6 +26,7 @@
 import android.os.PowerManager;
 import android.os.ServiceManager;
 import android.os.SystemClock;
+import android.provider.BlockedNumberContract;
 import android.telecom.Log;
 
 import android.telecom.CallerInfoAsyncQuery;
@@ -44,6 +45,7 @@
 import com.android.server.telecom.ContactsAsyncHelper;
 import com.android.server.telecom.DefaultDialerCache;
 import com.android.server.telecom.DeviceIdleControllerAdapter;
+import com.android.server.telecom.flags.FeatureFlags;
 import com.android.server.telecom.HeadsetMediaButton;
 import com.android.server.telecom.HeadsetMediaButtonFactory;
 import com.android.server.telecom.InCallWakeLockControllerFactory;
@@ -59,10 +61,15 @@
 import com.android.server.telecom.TelecomSystem;
 import com.android.server.telecom.TelecomWakeLock;
 import com.android.server.telecom.Timeouts;
+import com.android.server.telecom.callfiltering.BlockedNumbersAdapter;
+import com.android.server.telecom.flags.FeatureFlagsImpl;
+import com.android.server.telecom.settings.BlockedNumbersUtil;
 import com.android.server.telecom.ui.IncomingCallNotifier;
 import com.android.server.telecom.ui.MissedCallNotifierImpl;
 import com.android.server.telecom.ui.NotificationChannelManager;
 
+import java.util.concurrent.Executors;
+
 /**
  * Implementation of the ITelecom interface.
  */
@@ -110,10 +117,11 @@
                                         Context context,
                                         PhoneAccountRegistrar phoneAccountRegistrar,
                                         DefaultDialerCache defaultDialerCache,
-                                        DeviceIdleControllerAdapter idleControllerAdapter) {
+                                        DeviceIdleControllerAdapter idleControllerAdapter,
+                                        FeatureFlags featureFlags) {
                                     return new MissedCallNotifierImpl(context,
                                             phoneAccountRegistrar, defaultDialerCache,
-                                            idleControllerAdapter);
+                                            idleControllerAdapter, featureFlags);
                                 }
                             },
                             new CallerInfoAsyncQueryFactory() {
@@ -156,7 +164,7 @@
                             new InCallWakeLockControllerFactory() {
                                 @Override
                                 public InCallWakeLockController create(Context context,
-                                                                       CallsManager callsManager) {
+                                        CallsManager callsManager) {
                                     return new InCallWakeLockController(
                                             new TelecomWakeLock(context,
                                                     PowerManager.FULL_WAKE_LOCK,
@@ -208,7 +216,25 @@
                                     return context.getSystemService(AccessibilityManager.class)
                                             .stopFlashNotificationSequence(context);
                                 }
-                            }));
+                            },
+                            Executors.newCachedThreadPool(),
+                            Executors.newSingleThreadExecutor(),
+                            new BlockedNumbersAdapter() {
+                                @Override
+                                public boolean shouldShowEmergencyCallNotification(Context
+                                        context) {
+                                    return BlockedNumberContract.SystemContract
+                                            .shouldShowEmergencyCallNotification(context);
+                                }
+
+                                @Override
+                                public void updateEmergencyCallNotification(Context context,
+                                        boolean showNotification) {
+                                    BlockedNumbersUtil.updateEmergencyCallNotification(context,
+                                            showNotification);
+                                }
+                            },
+                            new FeatureFlagsImpl()));
         }
     }
 
diff --git a/src/com/android/server/telecom/components/UserCallActivity.java b/src/com/android/server/telecom/components/UserCallActivity.java
index 1d85884..d7b2001 100644
--- a/src/com/android/server/telecom/components/UserCallActivity.java
+++ b/src/com/android/server/telecom/components/UserCallActivity.java
@@ -74,7 +74,8 @@
             // ActivityThread.ActivityClientRecord#intent directly.
             // Modifying directly may be a potential risk when relaunching this activity.
             new UserCallIntentProcessor(this, userHandle).processIntent(new Intent(intent),
-                    getCallingPackage(), true /* hasCallAppOp*/, false /* isLocalInvocation */);
+                    getCallingPackage(), false, true /* hasCallAppOp*/,
+                    false /* isLocalInvocation */);
         } finally {
             Log.endSession();
             wakelock.release();
diff --git a/src/com/android/server/telecom/components/UserCallIntentProcessor.java b/src/com/android/server/telecom/components/UserCallIntentProcessor.java
index cad7b4c..41232c2 100755
--- a/src/com/android/server/telecom/components/UserCallIntentProcessor.java
+++ b/src/com/android/server/telecom/components/UserCallIntentProcessor.java
@@ -69,6 +69,7 @@
      *
      * @param intent The intent.
      * @param callingPackageName The package name of the calling app.
+     * @param isSelfManaged      {@code true} if SelfManaged profile enabled.
      * @param canCallNonEmergency {@code true} if the caller is permitted to call non-emergency
      *                            numbers.
      * @param isLocalInvocation {@code true} if the caller is within the system service (i.e. the
@@ -79,19 +80,21 @@
      *                            service resides.
      */
     public void processIntent(Intent intent, String callingPackageName,
-            boolean canCallNonEmergency, boolean isLocalInvocation) {
+            boolean isSelfManaged, boolean canCallNonEmergency,
+            boolean isLocalInvocation) {
         String action = intent.getAction();
 
         if (Intent.ACTION_CALL.equals(action) ||
                 Intent.ACTION_CALL_PRIVILEGED.equals(action) ||
                 Intent.ACTION_CALL_EMERGENCY.equals(action)) {
-            processOutgoingCallIntent(intent, callingPackageName, canCallNonEmergency,
-                    isLocalInvocation);
+            processOutgoingCallIntent(intent, callingPackageName, isSelfManaged,
+                    canCallNonEmergency, isLocalInvocation);
         }
     }
 
     private void processOutgoingCallIntent(Intent intent, String callingPackageName,
-            boolean canCallNonEmergency, boolean isLocalInvocation) {
+            boolean isSelfManaged, boolean canCallNonEmergency,
+            boolean isLocalInvocation) {
         Uri handle = intent.getData();
         if (handle == null) return;
         String scheme = handle.getScheme();
@@ -102,44 +105,17 @@
             handle = Uri.fromParts(PhoneAccount.SCHEME_SIP, uriString, null);
         }
 
-        // Check DISALLOW_OUTGOING_CALLS restriction. Note: We are skipping this check in a managed
-        // profile user because this check can always be bypassed by copying and pasting the phone
-        // number into the personal dialer.
-        if (!UserUtil.isManagedProfile(mContext, mUserHandle)) {
-            // Only emergency calls are allowed for users with the DISALLOW_OUTGOING_CALLS
-            // restriction.
-            if (!TelephonyUtil.shouldProcessAsEmergency(mContext, handle)) {
-                final UserManager userManager = (UserManager) mContext.getSystemService(
-                        Context.USER_SERVICE);
-                if (userManager.hasBaseUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS,
-                        mUserHandle)) {
-                    showErrorDialogForRestrictedOutgoingCall(mContext,
-                            R.string.outgoing_call_not_allowed_user_restriction);
-                    Log.w(this, "Rejecting non-emergency phone call due to DISALLOW_OUTGOING_CALLS "
-                            + "restriction");
-                    return;
-                } else if (userManager.hasUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS,
-                        mUserHandle)) {
-                    final DevicePolicyManager dpm =
-                            mContext.getSystemService(DevicePolicyManager.class);
-                    if (dpm == null) {
-                        return;
-                    }
-                    final Intent adminSupportIntent = dpm.createAdminSupportIntent(
-                            UserManager.DISALLOW_OUTGOING_CALLS);
-                    if (adminSupportIntent != null) {
-                        mContext.startActivity(adminSupportIntent);
-                    }
-                    return;
-                }
-            }
-        }
+       if (UserUtil.hasOutgoingCallsUserRestriction(mContext, mUserHandle,
+               handle, isSelfManaged, UserCallIntentProcessor.class.getCanonicalName())) {
+           return;
+       }
 
-        if (!canCallNonEmergency && !TelephonyUtil.shouldProcessAsEmergency(mContext, handle)) {
-            showErrorDialogForRestrictedOutgoingCall(mContext,
-                    R.string.outgoing_call_not_allowed_no_permission);
-            Log.w(this, "Rejecting non-emergency phone call because "
-                    + android.Manifest.permission.CALL_PHONE + " permission is not granted.");
+        if (!isSelfManaged && !canCallNonEmergency &&
+                !TelephonyUtil.shouldProcessAsEmergency(mContext, handle)) {
+            String reason = android.Manifest.permission.CALL_PHONE + " permission is not granted.";
+            UserUtil.showErrorDialogForRestrictedOutgoingCall(mContext,
+                    R.string.outgoing_call_not_allowed_no_permission,
+                    this.getClass().getCanonicalName(), reason);
             return;
         }
 
@@ -181,11 +157,4 @@
         }
         return true;
     }
-
-    private static void showErrorDialogForRestrictedOutgoingCall(Context context, int stringId) {
-        final Intent intent = new Intent(context, ErrorDialogActivity.class);
-        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        intent.putExtra(ErrorDialogActivity.ERROR_MESSAGE_ID_EXTRA, stringId);
-        context.startActivityAsUser(intent, UserHandle.CURRENT);
-    }
 }
diff --git a/src/com/android/server/telecom/settings/BlockedNumbersActivity.java b/src/com/android/server/telecom/settings/BlockedNumbersActivity.java
index 6e2eb97..5fa5f06 100644
--- a/src/com/android/server/telecom/settings/BlockedNumbersActivity.java
+++ b/src/com/android/server/telecom/settings/BlockedNumbersActivity.java
@@ -40,7 +40,6 @@
 import android.text.Editable;
 import android.text.TextUtils;
 import android.text.TextWatcher;
-import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.MenuItem;
 import android.view.View;
@@ -155,8 +154,11 @@
                 updateButterBar();
             }
         };
-        registerReceiver(mBlockingStatusReceiver, new IntentFilter(
-                BlockedNumberContract.SystemContract.ACTION_BLOCK_SUPPRESSION_STATE_CHANGED));
+        IntentFilter blockStatusIntentFilter = new IntentFilter(
+                BlockedNumberContract.SystemContract.ACTION_BLOCK_SUPPRESSION_STATE_CHANGED);
+        blockStatusIntentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
+        registerReceiver(mBlockingStatusReceiver, blockStatusIntentFilter,
+                Context.RECEIVER_EXPORTED);
 
         getLoaderManager().initLoader(0, null, this);
     }
diff --git a/src/com/android/server/telecom/settings/EnhancedCallBlockingFragment.java b/src/com/android/server/telecom/settings/EnhancedCallBlockingFragment.java
index c0bb56a..b1a1b0e 100644
--- a/src/com/android/server/telecom/settings/EnhancedCallBlockingFragment.java
+++ b/src/com/android/server/telecom/settings/EnhancedCallBlockingFragment.java
@@ -45,6 +45,7 @@
     private static final String BLOCK_UNAVAILABLE_NUMBERS_KEY =
             "block_unavailable_calls_setting";
     private boolean mIsCombiningRestrictedAndUnknownOption = false;
+    private boolean mIsCombiningUnavailableAndUnknownOption = false;
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
@@ -94,11 +95,17 @@
                         R.bool.combine_options_to_block_restricted_and_unknown_callers);
         if (mIsCombiningRestrictedAndUnknownOption) {
             Preference restricted_pref = findPreference(BLOCK_RESTRICTED_NUMBERS_KEY);
-            Preference unavailable_pref = findPreference(BLOCK_UNAVAILABLE_NUMBERS_KEY);
             screen.removePreference(restricted_pref);
-            screen.removePreference(unavailable_pref);
             Log.i(this, "onCreate: removed block restricted preference.");
         }
+
+        mIsCombiningUnavailableAndUnknownOption = getResources().getBoolean(
+                R.bool.combine_options_to_block_unavailable_and_unknown_callers);
+        if (mIsCombiningUnavailableAndUnknownOption) {
+            Preference unavailable_pref = findPreference(BLOCK_UNAVAILABLE_NUMBERS_KEY);
+            screen.removePreference(unavailable_pref);
+            Log.i(this, "onCreate: removed block unavailable preference.");
+        }
     }
 
     /**
@@ -136,14 +143,20 @@
 
     @Override
     public boolean onPreferenceChange(Preference preference, Object objValue) {
-        if (mIsCombiningRestrictedAndUnknownOption
-                && preference.getKey().equals(BLOCK_UNKNOWN_NUMBERS_KEY)) {
-            Log.i(this, "onPreferenceChange: changing %s and %s to %b",
-                    preference.getKey(), BLOCK_RESTRICTED_NUMBERS_KEY, (boolean) objValue);
-            BlockedNumbersUtil.setEnhancedBlockSetting(getActivity(), BLOCK_RESTRICTED_NUMBERS_KEY,
-                    (boolean) objValue);
-            BlockedNumbersUtil.setEnhancedBlockSetting(getActivity(),
-                    BLOCK_UNAVAILABLE_NUMBERS_KEY, (boolean) objValue);
+        if (preference.getKey().equals(BLOCK_UNKNOWN_NUMBERS_KEY)) {
+            if (mIsCombiningRestrictedAndUnknownOption) {
+                Log.i(this, "onPreferenceChange: changing %s and %s to %b",
+                        preference.getKey(), BLOCK_RESTRICTED_NUMBERS_KEY, (boolean) objValue);
+                BlockedNumbersUtil.setEnhancedBlockSetting(getActivity(),
+                        BLOCK_RESTRICTED_NUMBERS_KEY, (boolean) objValue);
+            }
+
+            if (mIsCombiningUnavailableAndUnknownOption) {
+                Log.i(this, "onPreferenceChange: changing %s and %s to %b",
+                        preference.getKey(), BLOCK_UNAVAILABLE_NUMBERS_KEY, (boolean) objValue);
+                BlockedNumbersUtil.setEnhancedBlockSetting(getActivity(),
+                        BLOCK_UNAVAILABLE_NUMBERS_KEY, (boolean) objValue);
+            }
         }
         BlockedNumbersUtil.setEnhancedBlockSetting(getActivity(), preference.getKey(),
                 (boolean) objValue);
diff --git a/src/com/android/server/telecom/ui/AudioProcessingNotification.java b/src/com/android/server/telecom/ui/AudioProcessingNotification.java
index e38178e..952bee8 100644
--- a/src/com/android/server/telecom/ui/AudioProcessingNotification.java
+++ b/src/com/android/server/telecom/ui/AudioProcessingNotification.java
@@ -54,7 +54,7 @@
         } else if (oldState == CallState.AUDIO_PROCESSING
                 && newState != CallState.AUDIO_PROCESSING) {
             cancelAudioProcessingNotification(
-                    call.getUserHandleFromTargetPhoneAccount());
+                    call.getAssociatedUser());
         }
     }
 
@@ -69,7 +69,7 @@
     public void onCallRemoved(Call call) {
         if (call == mCallInAudioProcessing) {
             cancelAudioProcessingNotification(
-                    call.getUserHandleFromTargetPhoneAccount());
+                    call.getAssociatedUser());
         }
     }
 
@@ -80,7 +80,7 @@
      */
     private void showAudioProcessingNotification(Call call) {
         Log.i(this, "showAudioProcessingNotification for user = %s",
-                call.getUserHandleFromTargetPhoneAccount());
+                call.getAssociatedUser());
         mCallInAudioProcessing = call;
 
         Notification.Builder builder = new Notification.Builder(mContext,
@@ -97,7 +97,7 @@
         Notification notification = builder.build();
 
         mNotificationManager.notifyAsUser(NOTIFICATION_TAG, AUDIO_PROCESSING_NOTIFICATION_ID,
-                notification, mCallInAudioProcessing.getUserHandleFromTargetPhoneAccount());
+                notification, mCallInAudioProcessing.getAssociatedUser());
     }
 
     /** Cancels the audio processing notification. */
diff --git a/src/com/android/server/telecom/ui/CallStreamingNotification.java b/src/com/android/server/telecom/ui/CallStreamingNotification.java
new file mode 100644
index 0000000..8414047
--- /dev/null
+++ b/src/com/android/server/telecom/ui/CallStreamingNotification.java
@@ -0,0 +1,324 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.telecom.ui;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Person;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.os.UserHandle;
+import android.telecom.Log;
+import android.telecom.PhoneAccount;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.TextUtils;
+import android.text.style.ForegroundColorSpan;
+import androidx.core.graphics.drawable.RoundedBitmapDrawable;
+import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.telecom.AppLabelProxy;
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallsManagerListenerBase;
+import com.android.server.telecom.R;
+import com.android.server.telecom.TelecomBroadcastIntentProcessor;
+import com.android.server.telecom.components.TelecomBroadcastReceiver;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Class responsible for tracking if there is a call which is being streamed and posting a
+ * notification which informs the user that a call is streaming.  The user has two possible actions:
+ * disconnect the call, bring the call back to the current device (stop streaming).
+ */
+public class CallStreamingNotification extends CallsManagerListenerBase implements Call.Listener {
+    // URI scheme used for data related to the notification actions.
+    public static final String CALL_ID_SCHEME = "callid";
+    // The default streaming notification ID.
+    private static final int STREAMING_NOTIFICATION_ID = 90210;
+    // Tag for streaming notification.
+    private static final String NOTIFICATION_TAG =
+            CallStreamingNotification.class.getSimpleName();
+
+    private final Context mContext;
+    private final NotificationManager mNotificationManager;
+    // Used to get the app name for the notification.
+    private final AppLabelProxy mAppLabelProxy;
+    // An executor that can be used to fire off async tasks that do not block Telecom in any manner.
+    private final Executor mAsyncTaskExecutor;
+    // The call which is streaming.
+    private Call mStreamingCall;
+    // Lock for notification post/remove -- these happen outside the Telecom sync lock.
+    private final Object mNotificationLock = new Object();
+
+    // Whether the notification is showing.
+    @GuardedBy("mNotificationLock")
+    private boolean mIsNotificationShowing = false;
+    @GuardedBy("mNotificationLock")
+    private UserHandle mNotificationUserHandle;
+
+    public CallStreamingNotification(@NonNull Context context,
+            @NonNull AppLabelProxy appLabelProxy,
+            @NonNull Executor asyncTaskExecutor) {
+        mContext = context;
+        mNotificationManager = context.getSystemService(NotificationManager.class);
+        mAppLabelProxy = appLabelProxy;
+        mAsyncTaskExecutor = asyncTaskExecutor;
+    }
+
+    @Override
+    public void onCallAdded(Call call) {
+        if (call.isStreaming()) {
+            trackStreamingCall(call);
+            enqueueStreamingNotification(call);
+        }
+    }
+
+    @Override
+    public void onCallRemoved(Call call) {
+        if (call == mStreamingCall) {
+            trackStreamingCall(null);
+            dequeueStreamingNotification();
+        }
+    }
+
+    /**
+     * Handles streaming state changes for a call.
+     * @param call the call
+     * @param isStreaming whether it is streaming or not
+     */
+    @Override
+    public void onCallStreamingStateChanged(Call call, boolean isStreaming) {
+        Log.i(this, "onCallStreamingStateChanged: call=%s, isStreaming=%b", call.getId(),
+                isStreaming);
+
+        if (isStreaming) {
+            trackStreamingCall(call);
+            enqueueStreamingNotification(call);
+        } else {
+            trackStreamingCall(null);
+            dequeueStreamingNotification();
+        }
+    }
+
+    /**
+     * Change the streaming call we are tracking.
+     * @param call the call.
+     */
+    private void trackStreamingCall(Call call) {
+        if (mStreamingCall != null) {
+            mStreamingCall.removeListener(this);
+        }
+        mStreamingCall = call;
+        if (mStreamingCall != null) {
+            mStreamingCall.addListener(this);
+        }
+    }
+
+    /**
+     * Enqueue an async task to post/repost the streaming notification.
+     * Note: This happens INSIDE the telecom lock.
+     * @param call the call to post notification for.
+     */
+    private void enqueueStreamingNotification(Call call) {
+        final Bitmap contactPhotoBitmap = call.getPhotoIcon();
+        mAsyncTaskExecutor.execute(() -> {
+            Icon contactPhotoIcon = null;
+            try {
+                contactPhotoIcon = Icon.createWithResource(mContext.getResources(),
+                        R.drawable.person_circle);
+            } catch (Exception e) {
+                // All loads of things can do wrong when working with bitmaps and images, so to
+                // ensure Telecom doesn't crash, lets try/catch to be sure.
+                Log.e(this, e, "enqueueStreamingNotification: Couldn't build avatar icon");
+            }
+            showStreamingNotification(call.getId(),
+                    call.getAssociatedUser(), call.getCallerDisplayName(),
+                    call.getHandle(), contactPhotoIcon,
+                    call.getTargetPhoneAccount().getComponentName().getPackageName(),
+                    call.getConnectTimeMillis());
+        });
+    }
+
+    /**
+     * Dequeues the call streaming notification.
+     * Note: This is yo be called within the Telecom sync lock to launch the task to remove the call
+     * streaming notification.
+     */
+    private void dequeueStreamingNotification() {
+        mAsyncTaskExecutor.execute(() -> hideStreamingNotification());
+    }
+
+    /**
+     * Show the call streaming notification.  This is intended to run outside the Telecom sync lock.
+     *
+     * @param callId the call ID we're streaming.
+     * @param userHandle the userhandle for the call.
+     * @param callerName the name of the caller/callee associated with the call
+     * @param callerAddress the address associated with the caller/callee
+     * @param photoIcon the contact photo icon if available
+     * @param appPackageName the package name for the app to post the notification for
+     * @param connectTimeMillis when the call connected (for chronometer in the notification)
+     */
+    private void showStreamingNotification(final String callId, final UserHandle userHandle,
+            String callerName, Uri callerAddress, Icon photoIcon, String appPackageName,
+            long connectTimeMillis) {
+        Log.i(this, "showStreamingNotification; callid=%s, hasPhoto=%b", callId, photoIcon != null);
+
+        // Use the caller name for the label if available, default to app name if none.
+        if (TextUtils.isEmpty(callerName)) {
+            // App did not provide a caller name, so default to app's name.
+            callerName = mAppLabelProxy.getAppLabel(appPackageName).toString();
+        }
+
+        // Action to hangup; this can use the default hangup action from the call style
+        // notification.
+        Intent hangupIntent = new Intent(TelecomBroadcastIntentProcessor.ACTION_HANGUP_CALL,
+                Uri.fromParts(CALL_ID_SCHEME, callId, null),
+                mContext, TelecomBroadcastReceiver.class);
+        PendingIntent hangupPendingIntent = PendingIntent.getBroadcast(mContext, 0, hangupIntent,
+                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+
+        // Action to switch here.
+        Intent switchHereIntent = new Intent(TelecomBroadcastIntentProcessor.ACTION_STOP_STREAMING,
+                Uri.fromParts(CALL_ID_SCHEME, callId, null),
+                mContext, TelecomBroadcastReceiver.class);
+        PendingIntent switchHerePendingIntent = PendingIntent.getBroadcast(mContext, 0,
+                switchHereIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+
+        // Apply a span to the string to colorize it using the "answer" color.
+        Spannable spannable = new SpannableString(
+                mContext.getString(R.string.call_streaming_notification_action_switch_here));
+        spannable.setSpan(new ForegroundColorSpan(
+                com.android.internal.R.color.call_notification_answer_color), 0, spannable.length(),
+                Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
+
+        // Use the "phone link" icon per mock.
+        Icon switchHereIcon = Icon.createWithResource(mContext, R.drawable.gm_phonelink);
+        Notification.Action.Builder switchHereBuilder = new Notification.Action.Builder(
+                switchHereIcon,
+                spannable,
+                switchHerePendingIntent);
+        Notification.Action switchHereAction = switchHereBuilder.build();
+
+        // Notifications use a "person" entity to identify caller/callee.
+        Person.Builder personBuilder = new Person.Builder()
+                .setName(callerName);
+
+        // Some apps use phone numbers to identify; these are something the notification framework
+        // can lookup in contacts to provide more data
+        if (callerAddress != null && PhoneAccount.SCHEME_TEL.equals(callerAddress)) {
+            personBuilder.setUri(callerAddress.toString());
+        }
+        if (photoIcon != null) {
+            personBuilder.setIcon(photoIcon);
+        }
+        Person person = personBuilder.build();
+
+        // Call Style notification requires a full screen intent, so we'll just link in a null
+        // pending intent
+        Intent nullIntent = new Intent();
+        PendingIntent nullPendingIntent = PendingIntent.getBroadcast(mContext, 0, nullIntent,
+                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+
+        Notification.Builder builder = new Notification.Builder(mContext,
+                NotificationChannelManager.CHANNEL_ID_CALL_STREAMING)
+                // Use call style to get the general look and feel for the notification; it provides
+                // a hangup action with the right action already so we can leverage that.  The
+                // "switch here" action will be a custom action defined later.
+                .setStyle(Notification.CallStyle.forOngoingCall(person, hangupPendingIntent))
+                .setSmallIcon(R.drawable.ic_phone)
+                .setContentText(mContext.getString(
+                        R.string.call_streaming_notification_body))
+                // Report call time
+                .setWhen(connectTimeMillis)
+                .setShowWhen(true)
+                .setUsesChronometer(true)
+                // Set the full screen intent; this is just tricking notification manager into
+                // letting us use this style.  Sssh.
+                .setFullScreenIntent(nullPendingIntent, true)
+                .setColorized(true)
+                .addAction(switchHereAction);
+        Notification notification = builder.build();
+
+        synchronized(mNotificationLock) {
+            mIsNotificationShowing = true;
+            mNotificationUserHandle = userHandle;
+            try {
+                mNotificationManager.notifyAsUser(NOTIFICATION_TAG, STREAMING_NOTIFICATION_ID,
+                        notification, userHandle);
+            } catch (Exception e) {
+                // We don't want to crash Telecom if something changes with the requirements for the
+                // notification.
+                Log.e(this, e, "Notification post failed.");
+            }
+        }
+    }
+
+    /**
+     * Removes the posted streaming notification.  Intended to run outside the telecom lock.
+     */
+    private void hideStreamingNotification() {
+        Log.i(this, "hideStreamingNotification");
+        synchronized(mNotificationLock) {
+            if (mIsNotificationShowing) {
+                mIsNotificationShowing = false;
+                mNotificationManager.cancelAsUser(NOTIFICATION_TAG,
+                        STREAMING_NOTIFICATION_ID, mNotificationUserHandle);
+            }
+        }
+    }
+
+    public static Bitmap drawableToBitmap(@Nullable Drawable drawable, int width, int height) {
+        if (drawable == null) {
+            return null;
+        }
+
+        Bitmap bitmap;
+        if (drawable instanceof BitmapDrawable) {
+            bitmap = ((BitmapDrawable) drawable).getBitmap();
+        } else {
+            if (width > 0 || height > 0) {
+                bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+            } else if (drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) {
+                // Needed for drawables that are just a colour.
+                bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
+            } else {
+                bitmap =
+                        Bitmap.createBitmap(
+                                drawable.getIntrinsicWidth(),
+                                drawable.getIntrinsicHeight(),
+                                Bitmap.Config.ARGB_8888);
+            }
+
+            Canvas canvas = new Canvas(bitmap);
+            drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+            drawable.draw(canvas);
+        }
+        return bitmap;
+    }
+}
diff --git a/src/com/android/server/telecom/ui/DisconnectedCallNotifier.java b/src/com/android/server/telecom/ui/DisconnectedCallNotifier.java
index 66f9fe4..1604285 100644
--- a/src/com/android/server/telecom/ui/DisconnectedCallNotifier.java
+++ b/src/com/android/server/telecom/ui/DisconnectedCallNotifier.java
@@ -143,8 +143,7 @@
                 DisconnectCause.REASON_EMERGENCY_CALL_PLACED.equals(cause.getReason())) {
             // Clear any existing notification.
             clearNotification(mCallsManager.getCurrentUserHandle());
-            UserHandle userHandle = call.getTargetPhoneAccount() != null ?
-                    call.getTargetPhoneAccount().getUserHandle() : call.getInitiatingUser();
+            UserHandle userHandle = call.getAssociatedUser();
             // As a last resort, use the current user to display the notification.
             if (userHandle == null) userHandle = mCallsManager.getCurrentUserHandle();
             mPendingCallNotification = new CallInfo(userHandle, call.getHandle(),
diff --git a/src/com/android/server/telecom/ui/IncomingCallNotifier.java b/src/com/android/server/telecom/ui/IncomingCallNotifier.java
index 3b188d4..d419163 100644
--- a/src/com/android/server/telecom/ui/IncomingCallNotifier.java
+++ b/src/com/android/server/telecom/ui/IncomingCallNotifier.java
@@ -168,19 +168,19 @@
             } else if (hadIncomingCall && !hasIncomingCall) {
                 previousIncomingCall.removeListener(mCallListener);
                 hideIncomingCallNotification(
-                        previousIncomingCall.getUserHandleFromTargetPhoneAccount());
+                        previousIncomingCall.getAssociatedUser());
             }
         }
     }
 
     private void showIncomingCallNotification(Call call) {
         Log.i(this, "showIncomingCallNotification showCall = %s for user = %s",
-                call, call.getUserHandleFromTargetPhoneAccount());
+                call, call.getAssociatedUser());
 
         Notification.Builder builder = getNotificationBuilder(call,
                 mCallsManagerProxy.getActiveCall());
         mNotificationManager.notifyAsUser(NOTIFICATION_TAG, NOTIFICATION_INCOMING_CALL,
-                builder.build(), call.getUserHandleFromTargetPhoneAccount());
+                builder.build(), call.getAssociatedUser());
     }
 
     private void hideIncomingCallNotification(UserHandle userHandle) {
diff --git a/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java b/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java
index 6b97f97..25ce0ca 100644
--- a/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java
+++ b/src/com/android/server/telecom/ui/MissedCallNotifierImpl.java
@@ -20,6 +20,7 @@
 import static android.app.admin.DevicePolicyResources.Strings.Telecomm.NOTIFICATION_MISSED_WORK_CALL_TITLE;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.BroadcastOptions;
 import android.app.Notification;
 import android.app.NotificationManager;
@@ -42,6 +43,7 @@
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.UserHandle;
+import android.provider.CallLog;
 import android.provider.CallLog.Calls;
 import android.telecom.CallerInfo;
 import android.telecom.Log;
@@ -62,6 +64,7 @@
 import com.android.server.telecom.Constants;
 import com.android.server.telecom.DefaultDialerCache;
 import com.android.server.telecom.DeviceIdleControllerAdapter;
+import com.android.server.telecom.flags.FeatureFlags;
 import com.android.server.telecom.MissedCallNotifier;
 import com.android.server.telecom.PhoneAccountRegistrar;
 import com.android.server.telecom.R;
@@ -87,7 +90,8 @@
         MissedCallNotifier makeMissedCallNotifierImpl(Context context,
                 PhoneAccountRegistrar phoneAccountRegistrar,
                 DefaultDialerCache defaultDialerCache,
-                DeviceIdleControllerAdapter deviceIdleControllerAdapter);
+                DeviceIdleControllerAdapter deviceIdleControllerAdapter,
+                FeatureFlags featureFlags);
     }
 
     public interface NotificationBuilderFactory {
@@ -141,19 +145,22 @@
     private final Map<UserHandle, Integer> mMissedCallCounts;
 
     private Set<UserHandle> mUsersToLoadAfterBootComplete = new ArraySet<>();
+    private FeatureFlags mFeatureFlags;
 
     public MissedCallNotifierImpl(Context context, PhoneAccountRegistrar phoneAccountRegistrar,
             DefaultDialerCache defaultDialerCache,
-            DeviceIdleControllerAdapter deviceIdleControllerAdapter) {
+            DeviceIdleControllerAdapter deviceIdleControllerAdapter,
+            FeatureFlags featureFlags) {
         this(context, phoneAccountRegistrar, defaultDialerCache,
-                new DefaultNotificationBuilderFactory(), deviceIdleControllerAdapter);
+                new DefaultNotificationBuilderFactory(), deviceIdleControllerAdapter, featureFlags);
     }
 
     public MissedCallNotifierImpl(Context context,
             PhoneAccountRegistrar phoneAccountRegistrar,
             DefaultDialerCache defaultDialerCache,
             NotificationBuilderFactory notificationBuilderFactory,
-            DeviceIdleControllerAdapter deviceIdleControllerAdapter) {
+            DeviceIdleControllerAdapter deviceIdleControllerAdapter,
+            FeatureFlags featureFlags) {
         mContext = context;
         mPhoneAccountRegistrar = phoneAccountRegistrar;
         mNotificationManager =
@@ -163,6 +170,7 @@
 
         mNotificationBuilderFactory = notificationBuilderFactory;
         mMissedCallCounts = new ArrayMap<>();
+        mFeatureFlags = featureFlags;
     }
 
     /** Clears missed call notification and marks the call log's missed calls as read. */
@@ -261,17 +269,17 @@
     }
 
     private void sendNotificationThroughDefaultDialer(String dialerPackage, CallInfo callInfo,
-            UserHandle userHandle, int missedCallCount) {
+            UserHandle userHandle, int missedCallCount, @Nullable Uri uri) {
         Intent intent = getShowMissedCallIntentForDefaultDialer(dialerPackage)
             .setFlags(Intent.FLAG_RECEIVER_FOREGROUND)
             .putExtra(TelecomManager.EXTRA_CLEAR_MISSED_CALLS_INTENT,
                     createClearMissedCallsPendingIntent(userHandle))
             .putExtra(TelecomManager.EXTRA_NOTIFICATION_COUNT, missedCallCount)
+            .putExtra(TelecomManager.EXTRA_CALL_LOG_URI, uri)
             .putExtra(TelecomManager.EXTRA_NOTIFICATION_PHONE_NUMBER,
                     callInfo == null ? null : callInfo.getPhoneNumber())
             .putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
                     callInfo == null ? null : callInfo.getPhoneAccountHandle());
-
         if (missedCallCount == 1 && callInfo != null) {
             final Uri handleUri = callInfo.getHandle();
             String handle = handleUri == null ? null : handleUri.getSchemeSpecificPart();
@@ -295,7 +303,7 @@
      * @param callInfo The missed call.
      */
     @Override
-    public void showMissedCallNotification(@NonNull CallInfo callInfo) {
+    public void showMissedCallNotification(@NonNull CallInfo callInfo, @Nullable Uri uri) {
         final PhoneAccountHandle phoneAccountHandle = callInfo.getPhoneAccountHandle();
         final PhoneAccount phoneAccount =
                 mPhoneAccountRegistrar.getPhoneAccountUnchecked(phoneAccountHandle);
@@ -306,10 +314,11 @@
         } else {
             userHandle = phoneAccountHandle.getUserHandle();
         }
-        showMissedCallNotification(callInfo, userHandle);
+        showMissedCallNotification(callInfo, userHandle, uri);
     }
 
-    private void showMissedCallNotification(@NonNull CallInfo callInfo, UserHandle userHandle) {
+    private void showMissedCallNotification(@NonNull CallInfo callInfo, UserHandle userHandle,
+            @Nullable Uri uri) {
         int missedCallCounts;
         synchronized (mMissedCallCountsLock) {
             Integer currentCount = mMissedCallCounts.get(userHandle);
@@ -324,7 +333,7 @@
         String dialerPackage = getDefaultDialerPackage(userHandle);
         if (shouldManageNotificationThroughDefaultDialer(dialerPackage, userHandle)) {
             sendNotificationThroughDefaultDialer(dialerPackage, callInfo, userHandle,
-                    missedCallCounts);
+                    missedCallCounts, uri);
             return;
         }
 
@@ -446,7 +455,7 @@
         String dialerPackage = getDefaultDialerPackage(userHandle);
         if (shouldManageNotificationThroughDefaultDialer(dialerPackage, userHandle)) {
             sendNotificationThroughDefaultDialer(dialerPackage, null, userHandle,
-                    0 /* missedCallCount */);
+                    /* missedCallCount= */ 0, /* uri= */ null);
             return;
         }
 
@@ -631,6 +640,13 @@
                         while (cursor.moveToNext()) {
                             // Get data about the missed call from the cursor
                             final String handleString = cursor.getString(CALL_LOG_COLUMN_NUMBER);
+                            final Uri uri;
+                            if (mFeatureFlags.addCallUriForMissedCalls()){
+                                uri = Calls.CONTENT_URI.buildUpon().appendPath(
+                                        Long.toString(cursor.getInt(CALL_LOG_COLUMN_ID))).build();
+                            }else{
+                                uri = null;
+                            }
                             final int presentation =
                                     cursor.getInt(CALL_LOG_COLUMN_NUMBER_PRESENTATION);
                             final long date = cursor.getLong(CALL_LOG_COLUMN_DATE);
@@ -663,7 +679,8 @@
                                                 // null, just show the notification.
                                                 CallInfo callInfo = callInfoFactory.makeCallInfo(
                                                         info, null, handle, date);
-                                                showMissedCallNotification(callInfo, userHandle);
+                                                showMissedCallNotification(callInfo, userHandle,
+                                                        /* uri= */ uri);
                                             }
                                         }
 
@@ -678,7 +695,8 @@
                                             }
                                             CallInfo callInfo = callInfoFactory.makeCallInfo(
                                                     info, null, handle, date);
-                                            showMissedCallNotification(callInfo, userHandle);
+                                            showMissedCallNotification(callInfo, userHandle,
+                                                    /* uri= */ uri);
                                         }
                                     }
                             );
diff --git a/src/com/android/server/telecom/ui/NotificationChannelManager.java b/src/com/android/server/telecom/ui/NotificationChannelManager.java
index 58794a6..b3cb2c3 100644
--- a/src/com/android/server/telecom/ui/NotificationChannelManager.java
+++ b/src/com/android/server/telecom/ui/NotificationChannelManager.java
@@ -40,6 +40,7 @@
     public static final String CHANNEL_ID_AUDIO_PROCESSING = "TelecomBackgroundAudioProcessing";
     public static final String CHANNEL_ID_DISCONNECTED_CALLS = "TelecomDisconnectedCalls";
     public static final String CHANNEL_ID_IN_CALL_SERVICE_CRASH = "TelecomInCallServiceCrash";
+    public static final String CHANNEL_ID_CALL_STREAMING = "TelecomCallStreaming";
 
     private BroadcastReceiver mLocaleChangeReceiver = new BroadcastReceiver() {
         @Override
@@ -50,8 +51,9 @@
     };
 
     public void createChannels(Context context) {
-        context.registerReceiver(mLocaleChangeReceiver,
-                new IntentFilter(Intent.ACTION_LOCALE_CHANGED));
+        IntentFilter localeChangedfilter = new IntentFilter(Intent.ACTION_LOCALE_CHANGED);
+        localeChangedfilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
+        context.registerReceiver(mLocaleChangeReceiver, localeChangedfilter);
 
         createOrUpdateAll(context);
     }
@@ -63,6 +65,7 @@
         createOrUpdateChannel(context, CHANNEL_ID_AUDIO_PROCESSING);
         createOrUpdateChannel(context, CHANNEL_ID_DISCONNECTED_CALLS);
         createOrUpdateChannel(context, CHANNEL_ID_IN_CALL_SERVICE_CRASH);
+        createOrUpdateChannel(context, CHANNEL_ID_CALL_STREAMING);
     }
 
     private void createOrUpdateChannel(Context context, String channelId) {
@@ -127,6 +130,14 @@
                 lights = true;
                 vibration = true;
                 sound = null;
+            case CHANNEL_ID_CALL_STREAMING:
+                name = context.getText(R.string.notification_channel_call_streaming);
+                importance = NotificationManager.IMPORTANCE_DEFAULT;
+                canShowBadge = false;
+                lights = false;
+                vibration = false;
+                sound = null;
+                break;
         }
 
         NotificationChannel channel = new NotificationChannel(channelId, name, importance);
diff --git a/src/com/android/server/telecom/voip/AnswerCallTransaction.java b/src/com/android/server/telecom/voip/AnswerCallTransaction.java
deleted file mode 100644
index 1eb34c4..0000000
--- a/src/com/android/server/telecom/voip/AnswerCallTransaction.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2023 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.voip;
-
-import android.os.OutcomeReceiver;
-import android.telecom.CallException;
-import android.util.Log;
-
-import com.android.server.telecom.Call;
-import com.android.server.telecom.CallState;
-import com.android.server.telecom.CallsManager;
-
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CompletionStage;
-
-/**
- * This transaction should be created for new incoming calls that request to go from
- * CallState.Ringing to CallState.Answered.  Before changing the CallState, the focus manager must
- * be updated. Once the focus manager updates, the call state will be set.  If there is an issue
- * answering the call, the transaction will fail.
- */
-public class AnswerCallTransaction extends VoipCallTransaction {
-
-    private static final String TAG = AnswerCallTransaction.class.getSimpleName();
-    private final CallsManager mCallsManager;
-    private final Call mCall;
-    private final int mVideoState;
-
-    public AnswerCallTransaction(CallsManager callsManager, Call call, int videoState) {
-        mCallsManager = callsManager;
-        mCall = call;
-        mVideoState = videoState;
-    }
-
-    @Override
-    public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
-        Log.d(TAG, "processTransaction");
-        CompletableFuture<VoipCallTransactionResult> future = new CompletableFuture<>();
-
-        mCall.setVideoState(mVideoState);
-
-        mCallsManager.transactionRequestNewFocusCall(mCall, CallState.ANSWERED,
-                new OutcomeReceiver<>() {
-            @Override
-            public void onResult(Boolean result) {
-                Log.d(TAG, "processTransaction: onResult");
-                future.complete(new VoipCallTransactionResult(
-                        VoipCallTransactionResult.RESULT_SUCCEED, null));
-            }
-
-            @Override
-            public void onError(CallException exception) {
-                Log.d(TAG, "processTransaction: onError");
-                future.complete(new VoipCallTransactionResult(
-                        exception.getCode(), exception.getMessage()));
-            }
-        });
-
-        return future;
-    }
-}
\ No newline at end of file
diff --git a/src/com/android/server/telecom/voip/CallEventCallbackAckTransaction.java b/src/com/android/server/telecom/voip/CallEventCallbackAckTransaction.java
index f47e4c5..93d9836 100644
--- a/src/com/android/server/telecom/voip/CallEventCallbackAckTransaction.java
+++ b/src/com/android/server/telecom/voip/CallEventCallbackAckTransaction.java
@@ -22,10 +22,12 @@
 import android.os.Bundle;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
+import android.telecom.CallAttributes;
 import android.telecom.DisconnectCause;
 import android.util.Log;
 
 import com.android.internal.telecom.ICallEventCallback;
+import com.android.server.telecom.TelecomSystem;
 import com.android.server.telecom.TransactionalServiceWrapper;
 
 import java.util.concurrent.CompletableFuture;
@@ -43,7 +45,7 @@
     private final String mAction;
     private final String mCallId;
     // optional values
-    private int mVideoState = 0;
+    private int mVideoState = CallAttributes.AUDIO_CALL;
     private DisconnectCause mDisconnectCause = null;
 
     private final VoipCallTransactionResult TRANSACTION_FAILED = new VoipCallTransactionResult(
@@ -66,7 +68,8 @@
     }
 
     public CallEventCallbackAckTransaction(ICallEventCallback service, String action,
-            String callId) {
+            String callId, TelecomSystem.SyncRoot lock) {
+        super(lock);
         mICallEventCallback = service;
         mAction = action;
         mCallId = callId;
@@ -74,7 +77,8 @@
 
 
     public CallEventCallbackAckTransaction(ICallEventCallback service, String action, String callId,
-            int videoState) {
+            int videoState, TelecomSystem.SyncRoot lock) {
+        super(lock);
         mICallEventCallback = service;
         mAction = action;
         mCallId = callId;
@@ -82,7 +86,8 @@
     }
 
     public CallEventCallbackAckTransaction(ICallEventCallback service, String action, String callId,
-            DisconnectCause cause) {
+            DisconnectCause cause, TelecomSystem.SyncRoot lock) {
+        super(lock);
         mICallEventCallback = service;
         mAction = action;
         mCallId = callId;
@@ -123,6 +128,8 @@
             boolean success = latch.await(VoipCallTransaction.TIMEOUT_LIMIT, TimeUnit.MILLISECONDS);
             if (!success) {
                 // client send onError and failed to complete transaction
+                Log.i(TAG, String.format("CallEventCallbackAckTransaction:"
+                        + " client failed to complete the [%s] transaction", mAction));
                 return CompletableFuture.completedFuture(TRANSACTION_FAILED);
             } else {
                 // success
diff --git a/src/com/android/server/telecom/voip/EndCallTransaction.java b/src/com/android/server/telecom/voip/EndCallTransaction.java
index 7a5006a..0cb7458 100644
--- a/src/com/android/server/telecom/voip/EndCallTransaction.java
+++ b/src/com/android/server/telecom/voip/EndCallTransaction.java
@@ -36,6 +36,7 @@
     private DisconnectCause mCause;
 
     public EndCallTransaction(CallsManager callsManager, DisconnectCause cause, Call call) {
+        super(callsManager.getLock());
         mCallsManager = callsManager;
         mCause = cause;
         mCall = call;
diff --git a/src/com/android/server/telecom/voip/EndpointChangeTransaction.java b/src/com/android/server/telecom/voip/EndpointChangeTransaction.java
index 88630c8..e037a79 100644
--- a/src/com/android/server/telecom/voip/EndpointChangeTransaction.java
+++ b/src/com/android/server/telecom/voip/EndpointChangeTransaction.java
@@ -32,6 +32,7 @@
     private final CallsManager mCallsManager;
 
     public EndpointChangeTransaction(CallEndpoint endpoint, CallsManager callsManager) {
+        super(callsManager.getLock());
         mCallEndpoint = endpoint;
         mCallsManager = callsManager;
     }
diff --git a/src/com/android/server/telecom/voip/HoldCallTransaction.java b/src/com/android/server/telecom/voip/HoldCallTransaction.java
index bf0805e..6c4e8b7 100644
--- a/src/com/android/server/telecom/voip/HoldCallTransaction.java
+++ b/src/com/android/server/telecom/voip/HoldCallTransaction.java
@@ -32,6 +32,7 @@
     private final Call mCall;
 
     public HoldCallTransaction(CallsManager callsManager, Call call) {
+        super(callsManager.getLock());
         mCallsManager = callsManager;
         mCall = call;
     }
diff --git a/src/com/android/server/telecom/voip/IncomingCallTransaction.java b/src/com/android/server/telecom/voip/IncomingCallTransaction.java
index 7bb9736..d35030c 100644
--- a/src/com/android/server/telecom/voip/IncomingCallTransaction.java
+++ b/src/com/android/server/telecom/voip/IncomingCallTransaction.java
@@ -17,10 +17,10 @@
 package com.android.server.telecom.voip;
 
 import static android.telecom.CallAttributes.CALL_CAPABILITIES_KEY;
+import static android.telecom.CallAttributes.DISPLAY_NAME_KEY;
 
 import android.os.Bundle;
 import android.telecom.CallAttributes;
-import android.telecom.CallControl;
 import android.telecom.CallException;
 import android.telecom.TelecomManager;
 import android.util.Log;
@@ -37,14 +37,22 @@
     private final String mCallId;
     private final CallAttributes mCallAttributes;
     private final CallsManager mCallsManager;
+    private final Bundle mExtras;
 
     public IncomingCallTransaction(String callId, CallAttributes callAttributes,
-            CallsManager callsManager) {
+            CallsManager callsManager, Bundle extras) {
+        super(callsManager.getLock());
+        mExtras = extras;
         mCallId = callId;
         mCallAttributes = callAttributes;
         mCallsManager = callsManager;
     }
 
+    public IncomingCallTransaction(String callId, CallAttributes callAttributes,
+            CallsManager callsManager) {
+        this(callId, callAttributes, callsManager, new Bundle());
+    }
+
     @Override
     public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
         Log.d(TAG, "processTransaction");
@@ -69,11 +77,13 @@
         }
     }
 
-    private Bundle generateExtras(CallAttributes callAttributes){
-        Bundle extras = new Bundle();
-        extras.putString(TelecomManager.TRANSACTION_CALL_ID_KEY, mCallId);
-        extras.putInt(CALL_CAPABILITIES_KEY, callAttributes.getCallCapabilities());
-        extras.putInt(TelecomManager.EXTRA_INCOMING_VIDEO_STATE, callAttributes.getCallType());
-        return extras;
+    private Bundle generateExtras(CallAttributes callAttributes) {
+        mExtras.putString(TelecomManager.TRANSACTION_CALL_ID_KEY, mCallId);
+        mExtras.putInt(CALL_CAPABILITIES_KEY, callAttributes.getCallCapabilities());
+        mExtras.putInt(TelecomManager.EXTRA_INCOMING_VIDEO_STATE, callAttributes.getCallType());
+        mExtras.putCharSequence(DISPLAY_NAME_KEY, callAttributes.getDisplayName());
+        mExtras.putParcelable(TelecomManager.EXTRA_INCOMING_CALL_ADDRESS,
+                callAttributes.getAddress());
+        return mExtras;
     }
 }
diff --git a/src/com/android/server/telecom/voip/HoldActiveCallForNewCallTransaction.java b/src/com/android/server/telecom/voip/MaybeHoldCallForNewCallTransaction.java
similarity index 87%
rename from src/com/android/server/telecom/voip/HoldActiveCallForNewCallTransaction.java
rename to src/com/android/server/telecom/voip/MaybeHoldCallForNewCallTransaction.java
index 5d269b1..a245c1c 100644
--- a/src/com/android/server/telecom/voip/HoldActiveCallForNewCallTransaction.java
+++ b/src/com/android/server/telecom/voip/MaybeHoldCallForNewCallTransaction.java
@@ -26,13 +26,14 @@
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CompletionStage;
 
-public class HoldActiveCallForNewCallTransaction extends VoipCallTransaction {
+public class MaybeHoldCallForNewCallTransaction extends VoipCallTransaction {
 
-    private static final String TAG = HoldActiveCallForNewCallTransaction.class.getSimpleName();
+    private static final String TAG = MaybeHoldCallForNewCallTransaction.class.getSimpleName();
     private final CallsManager mCallsManager;
     private final Call mCall;
 
-    public HoldActiveCallForNewCallTransaction(CallsManager callsManager, Call call) {
+    public MaybeHoldCallForNewCallTransaction(CallsManager callsManager, Call call) {
+        super(callsManager.getLock());
         mCallsManager = callsManager;
         mCall = call;
     }
diff --git a/src/com/android/server/telecom/voip/OutgoingCallTransaction.java b/src/com/android/server/telecom/voip/OutgoingCallTransaction.java
index 169fc48..b2625e6 100644
--- a/src/com/android/server/telecom/voip/OutgoingCallTransaction.java
+++ b/src/com/android/server/telecom/voip/OutgoingCallTransaction.java
@@ -18,6 +18,7 @@
 
 import static android.Manifest.permission.CALL_PRIVILEGED;
 import static android.telecom.CallAttributes.CALL_CAPABILITIES_KEY;
+import static android.telecom.CallAttributes.DISPLAY_NAME_KEY;
 import static android.telecom.CallException.CODE_CALL_NOT_PERMITTED_AT_PRESENT_TIME;
 
 import android.content.Context;
@@ -43,14 +44,22 @@
     private final String mCallingPackage;
     private final CallAttributes mCallAttributes;
     private final CallsManager mCallsManager;
+    private final Bundle mExtras;
+
+    public OutgoingCallTransaction(String callId, Context context, CallAttributes callAttributes,
+            CallsManager callsManager, Bundle extras) {
+        super(callsManager.getLock());
+        mCallId = callId;
+        mContext = context;
+        mCallAttributes = callAttributes;
+        mCallsManager = callsManager;
+        mExtras = extras;
+        mCallingPackage = mContext.getOpPackageName();
+    }
 
     public OutgoingCallTransaction(String callId, Context context, CallAttributes callAttributes,
             CallsManager callsManager) {
-        mCallId = callId;
-        mContext = context;
-        mCallingPackage = mContext.getOpPackageName();
-        mCallAttributes = callAttributes;
-        mCallsManager = callsManager;
+        this(callId, context, callAttributes, callsManager, new Bundle());
     }
 
     @Override
@@ -112,13 +121,13 @@
         }
     }
 
-    private Bundle generateExtras(CallAttributes callAttributes){
-        Bundle extras = new Bundle();
-        extras.setDefusable(true);
-        extras.putString(TelecomManager.TRANSACTION_CALL_ID_KEY, mCallId);
-        extras.putInt(CALL_CAPABILITIES_KEY, callAttributes.getCallCapabilities());
-        extras.putInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
+    private Bundle generateExtras(CallAttributes callAttributes) {
+        mExtras.setDefusable(true);
+        mExtras.putString(TelecomManager.TRANSACTION_CALL_ID_KEY, mCallId);
+        mExtras.putInt(CALL_CAPABILITIES_KEY, callAttributes.getCallCapabilities());
+        mExtras.putInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
                 callAttributes.getCallType());
-        return extras;
+        mExtras.putCharSequence(DISPLAY_NAME_KEY, callAttributes.getDisplayName());
+        return mExtras;
     }
 }
diff --git a/src/com/android/server/telecom/voip/ParallelTransaction.java b/src/com/android/server/telecom/voip/ParallelTransaction.java
index c2d532c..6176087 100644
--- a/src/com/android/server/telecom/voip/ParallelTransaction.java
+++ b/src/com/android/server/telecom/voip/ParallelTransaction.java
@@ -16,29 +16,38 @@
 
 package com.android.server.telecom.voip;
 
+import com.android.server.telecom.LoggedHandlerExecutor;
+import com.android.server.telecom.TelecomSystem;
+
 import java.util.List;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * A VoipCallTransaction implementation that its sub transactions will be executed in parallel
  */
 public class ParallelTransaction extends VoipCallTransaction {
-    public ParallelTransaction(List<VoipCallTransaction> subTransactions) {
-        super(subTransactions);
+    public ParallelTransaction(List<VoipCallTransaction> subTransactions,
+            TelecomSystem.SyncRoot lock) {
+        super(subTransactions, lock);
     }
 
     @Override
     public void start() {
         // post timeout work
-        mHandler.postDelayed(() -> {
+        CompletableFuture<Void> future = new CompletableFuture<>();
+        mHandler.postDelayed(() -> future.complete(null), TIMEOUT_LIMIT);
+        future.thenApplyAsync((x) -> {
             if (mCompleted.getAndSet(true)) {
-                return;
+                return null;
             }
             if (mCompleteListener != null) {
                 mCompleteListener.onTransactionTimeout(mTransactionName);
             }
             finish();
-        }, TIMEOUT_LIMIT);
+            return null;
+        }, new LoggedHandlerExecutor(mHandler, mTransactionName + "@" + hashCode()
+                + ".s", mLock));
 
         if (mSubTransactions != null && mSubTransactions.size() > 0) {
             TransactionManager.TransactionCompleteListener subTransactionListener =
@@ -49,16 +58,21 @@
                         public void onTransactionCompleted(VoipCallTransactionResult result,
                                 String transactionName) {
                             if (result.getResult() != VoipCallTransactionResult.RESULT_SUCCEED) {
-                                mHandler.post(() -> {
-                                    VoipCallTransactionResult mainResult =
-                                            new VoipCallTransactionResult(
-                                                    VoipCallTransactionResult.RESULT_FAILED,
-                                                    String.format("sub transaction %s failed",
-                                                            transactionName));
-                                    mCompleteListener.onTransactionCompleted(mainResult,
-                                            mTransactionName);
-                                    finish();
-                                });
+                                CompletableFuture.completedFuture(null).thenApplyAsync(
+                                        (x) -> {
+                                            VoipCallTransactionResult mainResult =
+                                                    new VoipCallTransactionResult(
+                                                            VoipCallTransactionResult.RESULT_FAILED,
+                                                            String.format(
+                                                                    "sub transaction %s failed",
+                                                                    transactionName));
+                                            mCompleteListener.onTransactionCompleted(mainResult,
+                                                    mTransactionName);
+                                            finish();
+                                            return null;
+                                        }, new LoggedHandlerExecutor(mHandler,
+                                                mTransactionName + "@" + hashCode()
+                                                        + ".oTC", mLock));
                             } else {
                                 if (mCount.decrementAndGet() == 0) {
                                     scheduleTransaction();
@@ -68,15 +82,20 @@
 
                         @Override
                         public void onTransactionTimeout(String transactionName) {
-                            mHandler.post(() -> {
-                                VoipCallTransactionResult mainResult = new VoipCallTransactionResult(
-                                        VoipCallTransactionResult.RESULT_FAILED,
-                                        String.format("sub transaction %s timed out",
-                                                transactionName));
-                                mCompleteListener.onTransactionCompleted(mainResult,
-                                        mTransactionName);
-                                finish();
-                            });
+                            CompletableFuture.completedFuture(null).thenApplyAsync(
+                                    (x) -> {
+                                        VoipCallTransactionResult mainResult =
+                                                new VoipCallTransactionResult(
+                                                VoipCallTransactionResult.RESULT_FAILED,
+                                                String.format("sub transaction %s timed out",
+                                                        transactionName));
+                                        mCompleteListener.onTransactionCompleted(mainResult,
+                                                mTransactionName);
+                                        finish();
+                                        return null;
+                                    }, new LoggedHandlerExecutor(mHandler,
+                                            mTransactionName + "@" + hashCode()
+                                                    + ".oTT", mLock));
                         }
                     };
             for (VoipCallTransaction transaction : mSubTransactions) {
diff --git a/src/com/android/server/telecom/voip/RequestFocusTransaction.java b/src/com/android/server/telecom/voip/RequestFocusTransaction.java
deleted file mode 100644
index 5dedbda..0000000
--- a/src/com/android/server/telecom/voip/RequestFocusTransaction.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2022 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.voip;
-
-import android.os.OutcomeReceiver;
-import android.telecom.CallException;
-import android.util.Log;
-
-import com.android.server.telecom.Call;
-import com.android.server.telecom.CallState;
-import com.android.server.telecom.CallsManager;
-
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CompletionStage;
-
-public class RequestFocusTransaction extends VoipCallTransaction {
-
-    private static final String TAG = RequestFocusTransaction.class.getSimpleName();
-    private final CallsManager mCallsManager;
-    private final Call mCall;
-
-    public RequestFocusTransaction(CallsManager callsManager, Call call) {
-        mCallsManager = callsManager;
-        mCall = call;
-    }
-
-    @Override
-    public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
-        Log.d(TAG, "processTransaction");
-        CompletableFuture<VoipCallTransactionResult> future = new CompletableFuture<>();
-
-        mCallsManager.transactionRequestNewFocusCall(mCall, CallState.ACTIVE,
-                new OutcomeReceiver<>() {
-            @Override
-            public void onResult(Boolean result) {
-                Log.d(TAG, "processTransaction: onResult");
-                future.complete(new VoipCallTransactionResult(
-                        VoipCallTransactionResult.RESULT_SUCCEED, null));
-            }
-
-            @Override
-            public void onError(CallException exception) {
-                Log.d(TAG, "processTransaction: onError");
-                future.complete(new VoipCallTransactionResult(
-                        exception.getCode(), exception.getMessage()));
-            }
-        });
-
-        return future;
-    }
-}
\ No newline at end of file
diff --git a/src/com/android/server/telecom/voip/RequestNewActiveCallTransaction.java b/src/com/android/server/telecom/voip/RequestNewActiveCallTransaction.java
new file mode 100644
index 0000000..f586cc3
--- /dev/null
+++ b/src/com/android/server/telecom/voip/RequestNewActiveCallTransaction.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2022 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.voip;
+
+import android.os.OutcomeReceiver;
+import android.telecom.CallAttributes;
+import android.telecom.CallException;
+import android.util.Log;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallState;
+import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.ConnectionServiceFocusManager;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+
+/**
+ * This transaction should be created when a requesting call would like to go from a valid inactive
+ * state (ex. HELD, RINGING, DIALING) to ACTIVE.
+ *
+ * This class performs some pre-checks to spot a failure in requesting a new call focus and sends
+ * the official request to transition the requested call to ACTIVE.
+ *
+ * Note:
+ * - This Transaction is used for CallControl and CallEventCallbacks, do not put logic in the
+ * onResult/onError that pertains to one direction.
+ * - MaybeHoldCallForNewCallTransaction was performed before this so any potential active calls
+ * should be held now.
+ */
+public class RequestNewActiveCallTransaction extends VoipCallTransaction {
+
+    private static final String TAG = RequestNewActiveCallTransaction.class.getSimpleName();
+    private final CallsManager mCallsManager;
+    private final Call mCall;
+
+    public RequestNewActiveCallTransaction(CallsManager callsManager, Call call) {
+        super(callsManager.getLock());
+        mCallsManager = callsManager;
+        mCall = call;
+    }
+
+    @Override
+    public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
+        Log.d(TAG, "processTransaction");
+        CompletableFuture<VoipCallTransactionResult> future = new CompletableFuture<>();
+        int currentCallState = mCall.getState();
+
+        // certain calls cannot go active/answered (ex. disconnect calls, etc.)
+        if (!canBecomeNewCallFocus(currentCallState)) {
+            future.complete(new VoipCallTransactionResult(
+                    CallException.CODE_CALL_CANNOT_BE_SET_TO_ACTIVE,
+                    "CallState cannot be set to active or answered due to current call"
+                            + " state being in invalid state"));
+            return future;
+        }
+
+        if (mCallsManager.getActiveCall() != null) {
+            future.complete(new VoipCallTransactionResult(
+                    CallException.CODE_CALL_CANNOT_BE_SET_TO_ACTIVE,
+                    "Already an active call. Request hold on current active call."));
+            return future;
+        }
+
+        mCallsManager.requestNewCallFocusAndVerify(mCall, new OutcomeReceiver<>() {
+                    @Override
+                    public void onResult(Boolean result) {
+                        Log.d(TAG, "processTransaction: onResult");
+                        future.complete(new VoipCallTransactionResult(
+                                VoipCallTransactionResult.RESULT_SUCCEED, null));
+                    }
+
+                    @Override
+                    public void onError(CallException exception) {
+                        Log.d(TAG, "processTransaction: onError");
+                        future.complete(new VoipCallTransactionResult(
+                                exception.getCode(), exception.getMessage()));
+                    }
+                });
+
+        return future;
+    }
+
+    private boolean isPriorityCallingState(int currentCallState) {
+        return ConnectionServiceFocusManager.PRIORITY_FOCUS_CALL_STATE.contains(currentCallState);
+    }
+
+    private boolean canBecomeNewCallFocus(int currentCallState) {
+        return isPriorityCallingState(currentCallState) || currentCallState == CallState.ON_HOLD;
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/server/telecom/voip/SerialTransaction.java b/src/com/android/server/telecom/voip/SerialTransaction.java
index 87bc9ec..b35b471 100644
--- a/src/com/android/server/telecom/voip/SerialTransaction.java
+++ b/src/com/android/server/telecom/voip/SerialTransaction.java
@@ -16,14 +16,19 @@
 
 package com.android.server.telecom.voip;
 
+import com.android.server.telecom.LoggedHandlerExecutor;
+import com.android.server.telecom.TelecomSystem;
+
 import java.util.List;
+import java.util.concurrent.CompletableFuture;
 
 /**
  * A VoipCallTransaction implementation that its sub transactions will be executed in serial
  */
 public class SerialTransaction extends VoipCallTransaction {
-    public SerialTransaction(List<VoipCallTransaction> subTransactions) {
-        super(subTransactions);
+    public SerialTransaction(List<VoipCallTransaction> subTransactions,
+            TelecomSystem.SyncRoot lock) {
+        super(subTransactions, lock);
     }
 
     public void appendTransaction(VoipCallTransaction transaction){
@@ -33,15 +38,19 @@
     @Override
     public void start() {
         // post timeout work
-        mHandler.postDelayed(() -> {
+        CompletableFuture<Void> future = new CompletableFuture<>();
+        mHandler.postDelayed(() -> future.complete(null), TIMEOUT_LIMIT);
+        future.thenApplyAsync((x) -> {
             if (mCompleted.getAndSet(true)) {
-                return;
+                return null;
             }
             if (mCompleteListener != null) {
                 mCompleteListener.onTransactionTimeout(mTransactionName);
             }
             finish();
-        }, TIMEOUT_LIMIT);
+            return null;
+        }, new LoggedHandlerExecutor(mHandler, mTransactionName + "@" + hashCode()
+                + ".s", mLock));
 
         if (mSubTransactions != null && mSubTransactions.size() > 0) {
             TransactionManager.TransactionCompleteListener subTransactionListener =
@@ -51,16 +60,22 @@
                         public void onTransactionCompleted(VoipCallTransactionResult result,
                                 String transactionName) {
                             if (result.getResult() != VoipCallTransactionResult.RESULT_SUCCEED) {
-                                mHandler.post(() -> {
-                                    VoipCallTransactionResult mainResult =
-                                            new VoipCallTransactionResult(
-                                                    VoipCallTransactionResult.RESULT_FAILED,
-                                                    String.format("sub transaction %s failed",
-                                                            transactionName));
-                                    mCompleteListener.onTransactionCompleted(mainResult,
-                                            mTransactionName);
-                                    finish();
-                                });
+                                handleTransactionFailure();
+                                CompletableFuture.completedFuture(null).thenApplyAsync(
+                                        (x) -> {
+                                            VoipCallTransactionResult mainResult =
+                                                    new VoipCallTransactionResult(
+                                                            VoipCallTransactionResult.RESULT_FAILED,
+                                                            String.format(
+                                                                    "sub transaction %s failed",
+                                                                    transactionName));
+                                            mCompleteListener.onTransactionCompleted(mainResult,
+                                                    mTransactionName);
+                                            finish();
+                                            return null;
+                                        }, new LoggedHandlerExecutor(mHandler,
+                                                mTransactionName + "@" + hashCode()
+                                                        + ".oTC", mLock));
                             } else {
                                 if (mSubTransactions.size() > 0) {
                                     VoipCallTransaction transaction = mSubTransactions.remove(0);
@@ -74,15 +89,21 @@
 
                         @Override
                         public void onTransactionTimeout(String transactionName) {
-                            mHandler.post(() -> {
-                                VoipCallTransactionResult mainResult = new VoipCallTransactionResult(
-                                        VoipCallTransactionResult.RESULT_FAILED,
-                                        String.format("sub transaction %s timed out",
-                                                transactionName));
-                                mCompleteListener.onTransactionCompleted(mainResult,
-                                        mTransactionName);
-                                finish();
-                            });
+                            handleTransactionFailure();
+                            CompletableFuture.completedFuture(null).thenApplyAsync(
+                                    (x) -> {
+                                        VoipCallTransactionResult mainResult =
+                                                new VoipCallTransactionResult(
+                                                VoipCallTransactionResult.RESULT_FAILED,
+                                                String.format("sub transaction %s timed out",
+                                                        transactionName));
+                                        mCompleteListener.onTransactionCompleted(mainResult,
+                                                mTransactionName);
+                                        finish();
+                                        return null;
+                                    }, new LoggedHandlerExecutor(mHandler,
+                                            mTransactionName + "@" + hashCode()
+                                                    + ".oTT", mLock));
                         }
                     };
             VoipCallTransaction transaction = mSubTransactions.remove(0);
@@ -92,4 +113,6 @@
             scheduleTransaction();
         }
     }
+
+    public void handleTransactionFailure() {}
 }
diff --git a/src/com/android/server/telecom/voip/TransactionManager.java b/src/com/android/server/telecom/voip/TransactionManager.java
index a0955c8..228bdde 100644
--- a/src/com/android/server/telecom/voip/TransactionManager.java
+++ b/src/com/android/server/telecom/voip/TransactionManager.java
@@ -16,6 +16,8 @@
 
 package com.android.server.telecom.voip;
 
+import static android.telecom.CallException.CODE_OPERATION_TIMED_OUT;
+
 import android.os.OutcomeReceiver;
 import android.telecom.TelecomManager;
 import android.telecom.CallException;
@@ -24,6 +26,8 @@
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Queue;
 
 public class TransactionManager {
@@ -61,30 +65,32 @@
             OutcomeReceiver<VoipCallTransactionResult, CallException> receiver) {
         synchronized (sLock) {
             mTransactions.add(transaction);
-            transaction.setCompleteListener(new TransactionCompleteListener() {
-                @Override
-                public void onTransactionCompleted(VoipCallTransactionResult result,
-                        String transactionName){
-                    Log.i(TAG, String.format("transaction completed: with result=[%d]",
-                            result.getResult()));
-                    if (result.getResult() == TelecomManager.TELECOM_TRANSACTION_SUCCESS) {
-                        receiver.onResult(result);
-                    } else {
-                        receiver.onError(
-                                new CallException(result.getMessage(),
-                                        result.getResult()));
-                    }
-                    finishTransaction();
-                }
-
-                @Override
-                public void onTransactionTimeout(String transactionName){
-                    receiver.onResult(new VoipCallTransactionResult(
-                            VoipCallTransactionResult.RESULT_FAILED, transactionName + " timeout"));
-                    finishTransaction();
-                }
-            });
         }
+        transaction.setCompleteListener(new TransactionCompleteListener() {
+            @Override
+            public void onTransactionCompleted(VoipCallTransactionResult result,
+                    String transactionName){
+                Log.i(TAG, String.format("transaction %s completed: with result=[%d]",
+                        transactionName, result.getResult()));
+                if (result.getResult() == TelecomManager.TELECOM_TRANSACTION_SUCCESS) {
+                    receiver.onResult(result);
+                } else {
+                    receiver.onError(
+                            new CallException(result.getMessage(),
+                                    result.getResult()));
+                }
+                finishTransaction();
+            }
+
+            @Override
+            public void onTransactionTimeout(String transactionName){
+                Log.i(TAG, String.format("transaction %s timeout", transactionName));
+                receiver.onError(new CallException(transactionName + " timeout",
+                        CODE_OPERATION_TIMED_OUT));
+                finishTransaction();
+            }
+        });
+
         startTransactions();
     }
 
@@ -100,8 +106,8 @@
                 return;
             }
             mCurrentTransaction = mTransactions.poll();
-            mCurrentTransaction.start();
         }
+        mCurrentTransaction.start();
     }
 
     private void finishTransaction() {
@@ -113,10 +119,12 @@
 
     @VisibleForTesting
     public void clear() {
+        List<VoipCallTransaction> pendingTransactions;
         synchronized (sLock) {
-            for (VoipCallTransaction transaction : mTransactions) {
-                transaction.finish();
-            }
+            pendingTransactions = new ArrayList<>(mTransactions);
+        }
+        for (VoipCallTransaction transaction : pendingTransactions) {
+            transaction.finish();
         }
     }
 }
diff --git a/src/com/android/server/telecom/voip/VerifyCallStateChangeTransaction.java b/src/com/android/server/telecom/voip/VerifyCallStateChangeTransaction.java
new file mode 100644
index 0000000..b17dedd
--- /dev/null
+++ b/src/com/android/server/telecom/voip/VerifyCallStateChangeTransaction.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2023 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.voip;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallsManager;
+
+import android.telecom.DisconnectCause;
+import android.telecom.Log;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * VerifyCallStateChangeTransaction is a transaction that verifies a CallState change and has
+ * the ability to disconnect if the CallState is not changed within the timeout window.
+ * <p>
+ * Note: This transaction has a timeout of 2 seconds.
+ */
+public class VerifyCallStateChangeTransaction extends VoipCallTransaction {
+    private static final String TAG = VerifyCallStateChangeTransaction.class.getSimpleName();
+    public static final int FAILURE_CODE = 0;
+    public static final int SUCCESS_CODE = 1;
+    public static final int TIMEOUT_SECONDS = 2;
+    private final Call mCall;
+    private final CallsManager mCallsManager;
+    private final int mTargetCallState;
+    private final boolean mShouldDisconnectUponFailure;
+    private final CompletableFuture<Integer> mCallStateOrTimeoutResult = new CompletableFuture<>();
+    private final CompletableFuture<VoipCallTransactionResult> mTransactionResult =
+            new CompletableFuture<>();
+
+    @VisibleForTesting
+    public Call.CallStateListener mCallStateListenerImpl = new Call.CallStateListener() {
+        @Override
+        public void onCallStateChanged(int newCallState) {
+            Log.d(TAG, "newState=[%d], expectedState=[%d]", newCallState, mTargetCallState);
+            if (newCallState == mTargetCallState) {
+                mCallStateOrTimeoutResult.complete(SUCCESS_CODE);
+            }
+            // NOTE:: keep listening to the call state until the timeout is reached. It's possible
+            // another call state is reached in between...
+        }
+    };
+
+    public VerifyCallStateChangeTransaction(CallsManager callsManager, Call call,
+            int targetCallState, boolean shouldDisconnectUponFailure) {
+        super(callsManager.getLock());
+        mCallsManager = callsManager;
+        mCall = call;
+        mTargetCallState = targetCallState;
+        mShouldDisconnectUponFailure = shouldDisconnectUponFailure;
+    }
+
+    @Override
+    public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
+        Log.d(TAG, "processTransaction:");
+        // It's possible the Call is already in the expected call state
+        if (isNewCallStateTargetCallState()) {
+            mTransactionResult.complete(
+                    new VoipCallTransactionResult(VoipCallTransactionResult.RESULT_SUCCEED,
+                            TAG));
+            return mTransactionResult;
+        }
+        initCallStateListenerOnTimeout();
+        // At this point, the mCallStateOrTimeoutResult has been completed. There are 2 scenarios:
+        // (1) newCallState == targetCallState --> the transaction is successful
+        // (2) timeout is reached --> evaluate the current call state and complete the t accordingly
+        // also need to do cleanup for the transaction
+        evaluateCallStateUponChangeOrTimeout();
+
+        return mTransactionResult;
+    }
+
+    private boolean isNewCallStateTargetCallState() {
+        return mCall.getState() == mTargetCallState;
+    }
+
+    private void initCallStateListenerOnTimeout() {
+        mCall.addCallStateListener(mCallStateListenerImpl);
+        mCallStateOrTimeoutResult.completeOnTimeout(FAILURE_CODE, TIMEOUT_SECONDS,
+                TimeUnit.SECONDS);
+    }
+
+    private void evaluateCallStateUponChangeOrTimeout() {
+        mCallStateOrTimeoutResult.thenAcceptAsync((result) -> {
+            Log.i(TAG, "processTransaction: thenAcceptAsync: result=[%s]", result);
+            mCall.removeCallStateListener(mCallStateListenerImpl);
+            if (isNewCallStateTargetCallState()) {
+                mTransactionResult.complete(
+                        new VoipCallTransactionResult(VoipCallTransactionResult.RESULT_SUCCEED,
+                                TAG));
+            } else {
+                maybeDisconnectCall();
+                mTransactionResult.complete(
+                        new VoipCallTransactionResult(VoipCallTransactionResult.RESULT_FAILED,
+                                TAG));
+            }
+        }).exceptionally(exception -> {
+            Log.i(TAG, "hit exception=[%s] while completing future", exception);
+            mTransactionResult.complete(
+                    new VoipCallTransactionResult(VoipCallTransactionResult.RESULT_FAILED,
+                            TAG));
+            return null;
+        });
+    }
+
+    private void maybeDisconnectCall() {
+        if (mShouldDisconnectUponFailure) {
+            mCallsManager.markCallAsDisconnected(mCall,
+                    new DisconnectCause(DisconnectCause.ERROR,
+                            "did not hold in timeout window"));
+            mCallsManager.markCallAsRemoved(mCall);
+        }
+    }
+
+    @VisibleForTesting
+    public CompletableFuture<Integer> getCallStateOrTimeoutResult() {
+        return mCallStateOrTimeoutResult;
+    }
+
+    @VisibleForTesting
+    public CompletableFuture<VoipCallTransactionResult> getTransactionResult() {
+        return mTransactionResult;
+    }
+
+    @VisibleForTesting
+    public Call.CallStateListener getCallStateListenerImpl() {
+        return mCallStateListenerImpl;
+    }
+}
diff --git a/src/com/android/server/telecom/voip/VoipCallMonitor.java b/src/com/android/server/telecom/voip/VoipCallMonitor.java
new file mode 100644
index 0000000..8f6ad51
--- /dev/null
+++ b/src/com/android/server/telecom/voip/VoipCallMonitor.java
@@ -0,0 +1,374 @@
+/*
+ * Copyright (C) 2022 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.voip;
+
+import static android.app.ForegroundServiceDelegationOptions.DELEGATION_SERVICE_PHONE_CALL;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL;
+
+import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
+import android.app.ForegroundServiceDelegationOptions;
+import android.app.Notification;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.ServiceConnection;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.StatusBarNotification;
+import android.telecom.Log;
+import android.telecom.PhoneAccountHandle;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalServices;
+import com.android.server.telecom.Call;
+
+import com.android.server.telecom.CallsManagerListenerBase;
+import com.android.server.telecom.LogUtils;
+import com.android.server.telecom.LoggedHandlerExecutor;
+import com.android.server.telecom.TelecomSystem;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+
+public class VoipCallMonitor extends CallsManagerListenerBase {
+
+    private final List<Call> mNotificationPendingCalls;
+    // Same notification may be passed as different object in onNotificationPosted and
+    // onNotificationRemoved. Use its string as key to cache ongoing notifications.
+    private final Map<NotificationInfo, Call> mNotificationInfoToCallMap;
+    private final Map<PhoneAccountHandle, Set<Call>> mAccountHandleToCallMap;
+    private ActivityManagerInternal mActivityManagerInternal;
+    private final Map<PhoneAccountHandle, ServiceConnection> mServices;
+    private NotificationListenerService mNotificationListener;
+    private final Object mLock = new Object();
+    private final HandlerThread mHandlerThread;
+    private final Handler mHandler;
+    private final Context mContext;
+    private List<NotificationInfo> mCachedNotifications;
+    private TelecomSystem.SyncRoot mSyncRoot;
+
+    public VoipCallMonitor(Context context, TelecomSystem.SyncRoot lock) {
+        mSyncRoot = lock;
+        mContext = context;
+        mHandlerThread = new HandlerThread(this.getClass().getSimpleName());
+        mHandlerThread.start();
+        mHandler = new Handler(mHandlerThread.getLooper());
+        mNotificationPendingCalls = new ArrayList<>();
+        mCachedNotifications = new ArrayList<>();
+        mNotificationInfoToCallMap = new HashMap<>();
+        mServices = new HashMap<>();
+        mAccountHandleToCallMap = new HashMap<>();
+        mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
+
+        mNotificationListener = new NotificationListenerService() {
+            @Override
+            public void onNotificationPosted(StatusBarNotification sbn) {
+                synchronized (mLock) {
+                    if (sbn.getNotification().isStyle(Notification.CallStyle.class)) {
+                        NotificationInfo info = new NotificationInfo(sbn.getPackageName(),
+                                sbn.getUser());
+                        boolean sbnMatched = false;
+                        for (Call call : mNotificationPendingCalls) {
+                            if (info.matchesCall(call)) {
+                                Log.i(this, "onNotificationPosted: found a pending "
+                                                + "callId=[%s] for the call notification w/ "
+                                                + "id=[%s]",
+                                        call.getId(), sbn.getId());
+                                mNotificationPendingCalls.remove(call);
+                                mNotificationInfoToCallMap.put(info, call);
+                                sbnMatched = true;
+                                break;
+                            }
+                        }
+                        if (!sbnMatched &&
+                                !mCachedNotifications.contains(info) /* don't re-add if update */) {
+                            Log.i(this, "onNotificationPosted: could not find a"
+                                            + "call for the call notification w/ id=[%s]",
+                                    sbn.getId());
+                            // notification may post before we started to monitor the call, cache
+                            // this notification and try to match it later with new added call.
+                            mCachedNotifications.add(info);
+                        }
+                    }
+                }
+            }
+
+            @Override
+            public void onNotificationRemoved(StatusBarNotification sbn) {
+                synchronized (mLock) {
+                    NotificationInfo info = new NotificationInfo(sbn.getPackageName(),
+                            sbn.getUser());
+                    mCachedNotifications.remove(info);
+                    if (mNotificationInfoToCallMap.isEmpty()) {
+                        return;
+                    }
+                    Call call = mNotificationInfoToCallMap.getOrDefault(info, null);
+                    if (call != null) {
+                        // TODO: fix potential bug for multiple calls of same voip app.
+                        mNotificationInfoToCallMap.remove(info, call);
+                        stopFGSDelegation(call);
+                    }
+                }
+            }
+        };
+
+    }
+
+    public void startMonitor() {
+        try {
+            mNotificationListener.registerAsSystemService(mContext,
+                    new ComponentName(this.getClass().getPackageName(),
+                            this.getClass().getCanonicalName()), ActivityManager.getCurrentUser());
+        } catch (RemoteException e) {
+            Log.e(this, e, "Cannot register notification listener");
+        }
+    }
+
+    public void stopMonitor() {
+        try {
+            mNotificationListener.unregisterAsSystemService();
+        } catch (RemoteException e) {
+            Log.e(this, e, "Cannot unregister notification listener");
+        }
+    }
+
+    @Override
+    public void onCallAdded(Call call) {
+        if (!call.isTransactionalCall()) {
+            return;
+        }
+
+        synchronized (mLock) {
+            PhoneAccountHandle phoneAccountHandle = call.getTargetPhoneAccount();
+            Set<Call> callList = mAccountHandleToCallMap.computeIfAbsent(phoneAccountHandle,
+                    k -> new HashSet<>());
+            callList.add(call);
+            CompletableFuture.completedFuture(null).thenComposeAsync(
+                    (x) -> {
+                        startFGSDelegation(call.getCallingPackageIdentity().mCallingPackagePid,
+                                call.getCallingPackageIdentity().mCallingPackageUid, call);
+                        return null;
+                    }, new LoggedHandlerExecutor(mHandler, "VCM.oCA", mSyncRoot));
+        }
+    }
+
+    @Override
+    public void onCallRemoved(Call call) {
+        if (!call.isTransactionalCall()) {
+            return;
+        }
+
+        synchronized (mLock) {
+            stopMonitorWorks(call);
+            PhoneAccountHandle phoneAccountHandle = call.getTargetPhoneAccount();
+            Set<Call> callList = mAccountHandleToCallMap.computeIfAbsent(phoneAccountHandle,
+                    k -> new HashSet<>());
+            callList.remove(call);
+
+            if (callList.isEmpty()) {
+                stopFGSDelegation(call);
+            }
+        }
+    }
+
+    private void startFGSDelegation(int pid, int uid, Call call) {
+        Log.i(this, "startFGSDelegation for call %s", call.getId());
+        if (mActivityManagerInternal != null) {
+            PhoneAccountHandle handle = call.getTargetPhoneAccount();
+            ForegroundServiceDelegationOptions options = new ForegroundServiceDelegationOptions(pid,
+                    uid, handle.getComponentName().getPackageName(), null /* clientAppThread */,
+                    false /* isSticky */, String.valueOf(handle.hashCode()),
+                    FOREGROUND_SERVICE_TYPE_PHONE_CALL |
+                    FOREGROUND_SERVICE_TYPE_MICROPHONE |
+                    FOREGROUND_SERVICE_TYPE_CAMERA |
+                    FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE /* foregroundServiceTypes */,
+                    DELEGATION_SERVICE_PHONE_CALL /* delegationService */);
+            ServiceConnection fgsConnection = new ServiceConnection() {
+                @Override
+                public void onServiceConnected(ComponentName name, IBinder service) {
+                    mServices.put(handle, this);
+                    startMonitorWorks(call);
+                }
+
+                @Override
+                public void onServiceDisconnected(ComponentName name) {
+                    mServices.remove(handle);
+                }
+            };
+            try {
+                if (mActivityManagerInternal
+                        .startForegroundServiceDelegate(options, fgsConnection)) {
+                    Log.addEvent(call, LogUtils.Events.GAINED_FGS_DELEGATION);
+                } else {
+                    Log.addEvent(call, LogUtils.Events.GAIN_FGS_DELEGATION_FAILED);
+                }
+            } catch (Exception e) {
+                Log.i(this, "startForegroundServiceDelegate failed due to: " + e);
+            }
+        }
+    }
+
+    @VisibleForTesting
+    public void stopFGSDelegation(Call call) {
+        synchronized (mLock) {
+            Log.i(this, "stopFGSDelegation of call %s", call);
+            PhoneAccountHandle handle = call.getTargetPhoneAccount();
+            Set<Call> calls = mAccountHandleToCallMap.get(handle);
+
+            // Every call for the package that is losing foreground service delegation should be
+            // removed from tracking maps/contains in this class
+            if (calls != null) {
+                for (Call c : calls) {
+                    stopMonitorWorks(c); // remove the call from tacking in this class
+                }
+            }
+
+            mAccountHandleToCallMap.remove(handle);
+
+            if (mActivityManagerInternal != null) {
+                ServiceConnection fgsConnection = mServices.get(handle);
+                if (fgsConnection != null) {
+                    mActivityManagerInternal.stopForegroundServiceDelegate(fgsConnection);
+                    Log.addEvent(call, LogUtils.Events.LOST_FGS_DELEGATION);
+                }
+            }
+        }
+    }
+
+    private void startMonitorWorks(Call call) {
+        startMonitorNotification(call);
+    }
+
+    private void stopMonitorWorks(Call call) {
+        stopMonitorNotification(call);
+    }
+
+    private void startMonitorNotification(Call call) {
+        synchronized (mLock) {
+            boolean sbnMatched = false;
+            for (NotificationInfo info : mCachedNotifications) {
+                if (info.matchesCall(call)) {
+                    Log.i(this, "startMonitorNotification: found a cached call "
+                            + "notification for call=[%s]", call);
+                    mCachedNotifications.remove(info);
+                    mNotificationInfoToCallMap.put(info, call);
+                    sbnMatched = true;
+                    break;
+                }
+            }
+            if (!sbnMatched) {
+                // Only continue to
+                Log.i(this, "startMonitorNotification: could not find a call"
+                        + " notification for the call=[%s];", call);
+                mNotificationPendingCalls.add(call);
+                CompletableFuture<Void> future = new CompletableFuture<>();
+                mHandler.postDelayed(() -> future.complete(null), 5000L);
+                future.thenComposeAsync(
+                        (x) -> {
+                            if (mNotificationPendingCalls.contains(call)) {
+                                Log.i(this, "Notification for voip-call %s haven't "
+                                        + "posted in time, stop delegation.", call.getId());
+                                stopFGSDelegation(call);
+                                mNotificationPendingCalls.remove(call);
+                                return null;
+                            }
+                            return null;
+                        }, new LoggedHandlerExecutor(mHandler, "VCM.sMN", mSyncRoot));
+            }
+        }
+    }
+
+    private void stopMonitorNotification(Call call) {
+        mNotificationPendingCalls.remove(call);
+    }
+
+    @VisibleForTesting
+    public void setActivityManagerInternal(ActivityManagerInternal ami) {
+        mActivityManagerInternal = ami;
+    }
+
+    private static class NotificationInfo extends Object {
+        private String mPackageName;
+        private UserHandle mUserHandle;
+
+        NotificationInfo(String packageName, UserHandle userHandle) {
+            mPackageName = packageName;
+            mUserHandle = userHandle;
+        }
+
+        boolean matchesCall(Call call) {
+            PhoneAccountHandle accountHandle = call.getTargetPhoneAccount();
+            return mPackageName != null && mPackageName.equals(
+                    accountHandle.getComponentName().getPackageName())
+                    && mUserHandle != null && mUserHandle.equals(accountHandle.getUserHandle());
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (!(obj instanceof NotificationInfo)) {
+                return false;
+            }
+            NotificationInfo that = (NotificationInfo) obj;
+            return Objects.equals(this.mPackageName, that.mPackageName)
+                    && Objects.equals(this.mUserHandle, that.mUserHandle);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mPackageName, mUserHandle);
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder();
+            sb.append("{ NotificationInfo: [mPackageName: ")
+                    .append(mPackageName)
+                    .append("], [mUserHandle=")
+                    .append(mUserHandle)
+                    .append("]  }");
+            return sb.toString();
+        }
+    }
+
+    @VisibleForTesting
+    public void postNotification(StatusBarNotification statusBarNotification) {
+        mNotificationListener.onNotificationPosted(statusBarNotification);
+    }
+
+    @VisibleForTesting
+    public void removeNotification(StatusBarNotification statusBarNotification) {
+        mNotificationListener.onNotificationRemoved(statusBarNotification);
+    }
+
+    @VisibleForTesting
+    public Set<Call> getCallsForHandle(PhoneAccountHandle handle){
+        return mAccountHandleToCallMap.get(handle);
+    }
+}
diff --git a/src/com/android/server/telecom/voip/VoipCallTransaction.java b/src/com/android/server/telecom/voip/VoipCallTransaction.java
index 413a1cd..a952eb1 100644
--- a/src/com/android/server/telecom/voip/VoipCallTransaction.java
+++ b/src/com/android/server/telecom/voip/VoipCallTransaction.java
@@ -18,8 +18,10 @@
 
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.telecom.Log;
 
 import com.android.server.telecom.LoggedHandlerExecutor;
+import com.android.server.telecom.TelecomSystem;
 
 import java.util.List;
 import java.util.concurrent.CompletableFuture;
@@ -36,48 +38,57 @@
     protected Handler mHandler;
     protected TransactionManager.TransactionCompleteListener mCompleteListener;
     protected List<VoipCallTransaction> mSubTransactions;
+    protected TelecomSystem.SyncRoot mLock;
 
     public VoipCallTransaction(
-            List<VoipCallTransaction> subTransactions) {
+            List<VoipCallTransaction> subTransactions, TelecomSystem.SyncRoot lock) {
         mSubTransactions = subTransactions;
         mHandlerThread = new HandlerThread(this.toString());
         mHandlerThread.start();
         mHandler = new Handler(mHandlerThread.getLooper());
+        mLock = lock;
     }
 
-    public VoipCallTransaction() {
-        this(null /** mSubTransactions */);
+    public VoipCallTransaction(TelecomSystem.SyncRoot lock) {
+        this(null /** mSubTransactions */, lock);
     }
 
     public void start() {
         // post timeout work
-        mHandler.postDelayed(() -> {
+        CompletableFuture<Void> future = new CompletableFuture<>();
+        mHandler.postDelayed(() -> future.complete(null), TIMEOUT_LIMIT);
+        future.thenApplyAsync((x) -> {
             if (mCompleted.getAndSet(true)) {
-                return;
+                return null;
             }
             if (mCompleteListener != null) {
                 mCompleteListener.onTransactionTimeout(mTransactionName);
             }
             finish();
-        }, TIMEOUT_LIMIT);
+            return null;
+        }, new LoggedHandlerExecutor(mHandler, mTransactionName + "@" + hashCode()
+                + ".s", mLock));
 
         scheduleTransaction();
     }
 
     protected void scheduleTransaction() {
+        LoggedHandlerExecutor executor = new LoggedHandlerExecutor(mHandler,
+                mTransactionName + "@" + hashCode() + ".pT", mLock);
         CompletableFuture<Void> future = CompletableFuture.completedFuture(null);
-        future.thenComposeAsync(this::processTransaction,
-                        new LoggedHandlerExecutor(mHandler, mTransactionName + "@"
-                                + hashCode() + ".pT", null))
-                .thenApplyAsync(
-                        (Function<VoipCallTransactionResult, Void>) result -> {
-                            mCompleted.set(true);
-                            if (mCompleteListener != null) {
-                                mCompleteListener.onTransactionCompleted(result, mTransactionName);
-                            }
-                            finish();
-                            return null;
-                        });
+        future.thenComposeAsync(this::processTransaction, executor)
+                .thenApplyAsync((Function<VoipCallTransactionResult, Void>) result -> {
+                    mCompleted.set(true);
+                    if (mCompleteListener != null) {
+                        mCompleteListener.onTransactionCompleted(result, mTransactionName);
+                    }
+                    finish();
+                    return null;
+                    }, executor)
+                .exceptionallyAsync((throwable -> {
+                    Log.e(this, throwable, "Error while executing transaction.");
+                    return null;
+                }), executor);
     }
 
     public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
diff --git a/testapps/AndroidManifest.xml b/testapps/AndroidManifest.xml
index dd8258a..645a42b 100644
--- a/testapps/AndroidManifest.xml
+++ b/testapps/AndroidManifest.xml
@@ -259,6 +259,15 @@
           </intent-filter>
         </service>
 
+        <service android:name="com.android.server.telecom.testapps.OtherSelfManagedConnectionService"
+                 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"/>
+            </intent-filter>
+        </service>
+
         <receiver android:exported="false"
              android:process="com.android.server.telecom.testapps.SelfMangingCallingApp"
              android:name="com.android.server.telecom.testapps.SelfManagedCallNotificationReceiver"/>
diff --git a/testapps/res/layout/self_managed_sample_main.xml b/testapps/res/layout/self_managed_sample_main.xml
index d26d629..98b879a 100644
--- a/testapps/res/layout/self_managed_sample_main.xml
+++ b/testapps/res/layout/self_managed_sample_main.xml
@@ -55,6 +55,12 @@
                 android:layout_height="wrap_content"
                 android:background="@color/test_call_b_color"
                 android:text="2"/>
+            <RadioButton
+                android:id="@+id/useAcct3Button"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:background="@color/test_call_c_color"
+                android:text="3"/>
         </RadioGroup>
         <TextView
             android:id="@+id/hasFocus"
diff --git a/testapps/res/values/colors.xml b/testapps/res/values/colors.xml
index 3939e78..9447ac8 100644
--- a/testapps/res/values/colors.xml
+++ b/testapps/res/values/colors.xml
@@ -17,4 +17,5 @@
 <resources>
     <color name="test_call_a_color">#f2eebf</color>
     <color name="test_call_b_color">#afc5e6</color>
+    <color name="test_call_c_color">#c5afe6</color>
 </resources>
diff --git a/testapps/src/com/android/server/telecom/testapps/OtherSelfManagedConnectionService.java b/testapps/src/com/android/server/telecom/testapps/OtherSelfManagedConnectionService.java
new file mode 100644
index 0000000..7bb9830
--- /dev/null
+++ b/testapps/src/com/android/server/telecom/testapps/OtherSelfManagedConnectionService.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2023 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;
+
+public class OtherSelfManagedConnectionService extends SelfManagedConnectionService {
+}
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallList.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallList.java
index d4661ff..273b060 100644
--- a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallList.java
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallList.java
@@ -46,20 +46,27 @@
 
     public static String SELF_MANAGED_ACCOUNT_1 = "1";
     public static String SELF_MANAGED_ACCOUNT_2 = "2";
+    public static String SELF_MANAGED_ACCOUNT_1A = "1A";
     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 SELF_MANAGED_NAME_1A = "SM Call";
+    public static String SELF_MANAGED_NAME_3 = "Sep Process";
     public static String CUSTOM_URI_SCHEME = "custom";
 
     private static SelfManagedCallList sInstance;
     private static ComponentName COMPONENT_NAME = new ComponentName(
             SelfManagedCallList.class.getPackage().getName(),
             SelfManagedConnectionService.class.getName());
+    private static ComponentName OTHER_COMPONENT_NAME = new ComponentName(
+            SelfManagedCallList.class.getPackage().getName(),
+            OtherSelfManagedConnectionService.class.getName());
     private static Uri SELF_MANAGED_ADDRESS_1 = Uri.fromParts(PhoneAccount.SCHEME_TEL, "555-1212",
             "");
     private static Uri SELF_MANAGED_ADDRESS_2 = Uri.fromParts(PhoneAccount.SCHEME_SIP,
             "me@test.org", "");
+    private static Uri SELF_MANAGED_ADDRESS_3 = Uri.fromParts(PhoneAccount.SCHEME_SIP,
+            "hilda@test.org", "");
     private static Map<String, PhoneAccountHandle> mPhoneAccounts = new ArrayMap();
 
     public static SelfManagedCallList getInstance() {
@@ -101,20 +108,29 @@
                 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 */);
+        registerPhoneAccount(context, SELF_MANAGED_ACCOUNT_1A, SELF_MANAGED_ADDRESS_1,
+                SELF_MANAGED_NAME_1A, true /* areCallsLogged */);
+        registerPhoneAccount(context, SELF_MANAGED_ACCOUNT_1A, SELF_MANAGED_ADDRESS_1,
+                SELF_MANAGED_NAME_1A, true /* areCallsLogged */);
+        registerPhoneAccount(context, OTHER_COMPONENT_NAME, SELF_MANAGED_ACCOUNT_3,
+                SELF_MANAGED_ADDRESS_3, SELF_MANAGED_NAME_3, false /* areCallsLogged */);
     }
 
     public void registerPhoneAccount(Context context, String id, Uri address, String name,
-                                     boolean areCallsLogged) {
-        PhoneAccountHandle handle = new PhoneAccountHandle(COMPONENT_NAME, id);
+            boolean areCallsLogged) {
+        registerPhoneAccount(context, COMPONENT_NAME, id, address, name, areCallsLogged);
+    }
+
+    public void registerPhoneAccount(Context context, ComponentName componentName, String id,
+            Uri address, String name, boolean areCallsLogged) {
+        PhoneAccountHandle handle = new PhoneAccountHandle(componentName, id);
         mPhoneAccounts.put(id, handle);
         Bundle extras = new Bundle();
         extras.putBoolean(PhoneAccount.EXTRA_SUPPORTS_HANDOVER_TO, true);
         if (areCallsLogged) {
             extras.putBoolean(PhoneAccount.EXTRA_LOG_SELF_MANAGED_CALLS, true);
         }
-        if (id.equals(SELF_MANAGED_ACCOUNT_3)) {
+        if (id.equals(SELF_MANAGED_ACCOUNT_1A)) {
             extras.putBoolean(PhoneAccount.EXTRA_ADD_SELF_MANAGED_CALLS_TO_INCALLSERVICE, true);
         }
         PhoneAccount.Builder builder = PhoneAccount.builder(handle, name)
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallListAdapter.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallListAdapter.java
index 75ceb62..475f255 100644
--- a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallListAdapter.java
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallListAdapter.java
@@ -166,8 +166,10 @@
                 SelfManagedConnection.EXTRA_PHONE_ACCOUNT_HANDLE);
         if (phoneAccountHandle.getId().equals(SelfManagedCallList.SELF_MANAGED_ACCOUNT_1)) {
             result.setBackgroundColor(result.getContext().getColor(R.color.test_call_a_color));
-        } else {
+        } else if (phoneAccountHandle.getId().equals(SelfManagedCallList.SELF_MANAGED_ACCOUNT_2)) {
             result.setBackgroundColor(result.getContext().getColor(R.color.test_call_b_color));
+        } else {
+            result.setBackgroundColor(result.getContext().getColor(R.color.test_call_c_color));
         }
 
         CallAudioState audioState = connection.getCallAudioState();
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java
index 44410d2..5cdaf3d 100644
--- a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java
@@ -43,8 +43,6 @@
 import android.widget.TextView;
 import android.widget.Toast;
 
-import com.android.server.telecom.testapps.R;
-
 import java.util.Objects;
 
 /**
@@ -66,6 +64,7 @@
     private Button mDisableCarMode;
     private RadioButton mUseAcct1Button;
     private RadioButton mUseAcct2Button;
+    private RadioButton mUseAcct3Button;
     private CheckBox mHoldableCheckbox;
     private CheckBox mVideoCallCheckbox;
     private EditText mNumber;
@@ -165,6 +164,7 @@
         }));
         mUseAcct1Button = findViewById(R.id.useAcct1Button);
         mUseAcct2Button = findViewById(R.id.useAcct2Button);
+        mUseAcct3Button = findViewById(R.id.useAcct3Button);
         mHasFocus = findViewById(R.id.hasFocus);
         mVideoCallCheckbox = findViewById(R.id.videoCall);
         mHoldableCheckbox = findViewById(R.id.holdable);
@@ -183,6 +183,8 @@
             return mCallList.getPhoneAccountHandle(SelfManagedCallList.SELF_MANAGED_ACCOUNT_1);
         } else if (mUseAcct2Button.isChecked()) {
             return mCallList.getPhoneAccountHandle(SelfManagedCallList.SELF_MANAGED_ACCOUNT_2);
+        } else if (mUseAcct3Button.isChecked()) {
+            return mCallList.getPhoneAccountHandle(SelfManagedCallList.SELF_MANAGED_ACCOUNT_3);
         }
         return null;
     }
@@ -214,8 +216,7 @@
 
     private void placeSelfManagedOutgoingCall() {
         TelecomManager tm = TelecomManager.from(this);
-        PhoneAccountHandle phoneAccountHandle = mCallList.getPhoneAccountHandle(
-                SelfManagedCallList.SELF_MANAGED_ACCOUNT_3);
+        PhoneAccountHandle phoneAccountHandle = getSelectedPhoneAccountHandle();
 
         if (mCheckIfPermittedBeforeCalling.isChecked()) {
             Toast.makeText(this, R.string.outgoingCallNotPermitted, Toast.LENGTH_SHORT).show();
@@ -264,7 +265,7 @@
     private void placeSelfManagedIncomingCall() {
         TelecomManager tm = TelecomManager.from(this);
         PhoneAccountHandle phoneAccountHandle = mCallList.getPhoneAccountHandle(
-                SelfManagedCallList.SELF_MANAGED_ACCOUNT_3);
+                SelfManagedCallList.SELF_MANAGED_ACCOUNT_1A);
 
         if (mCheckIfPermittedBeforeCalling.isChecked()) {
             if (!tm.isIncomingCallPermitted(phoneAccountHandle)) {
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedConnectionService.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedConnectionService.java
index 6670095..3ef8fbb 100644
--- a/testapps/src/com/android/server/telecom/testapps/SelfManagedConnectionService.java
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedConnectionService.java
@@ -142,11 +142,6 @@
         connection.setVideoState(request.getVideoState());
         Log.i(this, "createSelfManagedConnection %s", connection);
         mCallList.addConnection(connection);
-        try {
-            Thread.sleep(8000);
-        } catch (InterruptedException e) {
-            e.printStackTrace();
-        }
         return connection;
     }
 }
diff --git a/testapps/streamingtest/Android.bp b/testapps/streamingtest/Android.bp
new file mode 100644
index 0000000..bd0a582
--- /dev/null
+++ b/testapps/streamingtest/Android.bp
@@ -0,0 +1,31 @@
+//
+// Copyright (C) 2022 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: "streamingTestApp",
+    static_libs: [
+        "androidx.legacy_legacy-support-v4",
+        "guava",
+    ],
+    srcs: ["src/**/*.java"],
+    platform_apis: true,
+    certificate: "platform",
+    privileged: true,
+}
diff --git a/testapps/streamingtest/AndroidManifest.xml b/testapps/streamingtest/AndroidManifest.xml
new file mode 100644
index 0000000..47e4abc
--- /dev/null
+++ b/testapps/streamingtest/AndroidManifest.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 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.streamingtest">
+
+    <uses-sdk android:minSdkVersion="28"
+              android:targetSdkVersion="33"/>
+
+    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
+    <uses-permission android:name="android.permission.CALL_AUDIO_INTERCEPTION"/>
+
+    <application android:label="Streaming Test App">
+        <uses-library android:name="android.test.runner"/>
+
+        <service android:name="com.android.server.telecom.streamingtest.StreamingService"
+                 android:exported="true"
+                 android:permission="android.permission.BIND_CALL_STREAMING_SERVICE">
+            <intent-filter>
+                <action android:name="android.telecom.CallStreamingService"/>
+            </intent-filter>
+        </service>
+    </application>
+</manifest>
diff --git a/testapps/streamingtest/src/com/android/server/telecom/streamingtest/StreamingService.java b/testapps/streamingtest/src/com/android/server/telecom/streamingtest/StreamingService.java
new file mode 100644
index 0000000..c76b349
--- /dev/null
+++ b/testapps/streamingtest/src/com/android/server/telecom/streamingtest/StreamingService.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2023 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.streamingtest;
+
+import android.annotation.NonNull;
+import android.content.Intent;
+import android.telecom.CallStreamingService;
+import android.telecom.StreamingCall;
+import android.telecom.Log;
+
+public class StreamingService extends CallStreamingService {
+    @Override
+    public void onCallStreamingStarted(@NonNull StreamingCall call) {
+        Log.i(this, "onCallStreamingStarted: call %s", call);
+    }
+
+    @Override
+    public void onCallStreamingStopped() {
+        Log.i(this, "onCallStreamingStopped");
+    }
+
+    @Override
+    public void onCallStreamingStateChanged(int state) {
+        Log.i(this, "onCallStreamingStateChanged; state=%d", state);
+    }
+
+    @Override
+    public boolean onUnbind(Intent intent) {
+        Log.i(this, "onUnbind");
+        return false;
+    }
+}
diff --git a/testapps/transactionalVoipApp/AndroidManifest.xml b/testapps/transactionalVoipApp/AndroidManifest.xml
index d0aa50b..e4968db 100644
--- a/testapps/transactionalVoipApp/AndroidManifest.xml
+++ b/testapps/transactionalVoipApp/AndroidManifest.xml
@@ -15,13 +15,20 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-     coreApp="true"
-     package="com.android.server.telecom.transactionalVoipApp">
+          coreApp="true"
+          package="com.android.server.telecom.transactionalVoipApp">
 
     <uses-sdk android:minSdkVersion="28"
-         android:targetSdkVersion="33"/>
+              android:targetSdkVersion="33"/>
 
-    <uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
+    <uses-permission android:name="android.permission.MANAGE_OWN_CALLS"/>
+    <!-- Needed to test media/audio -->
+    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
+    <!-- Needed for foreground services -->
+    <uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT"/>
+    <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_PHONE_CALL"/>
 
     <application android:label="Transactional Voip">
         <uses-library android:name="android.test.runner"/>
@@ -30,10 +37,26 @@
                   android:exported="true"
                   android:label="Transactional Voip">
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
 
+        <activity android:name="com.android.server.telecom.transactionalVoipApp.InCallActivity"
+                  android:exported="true"
+                  android:launchMode="singleInstance"
+                  android:label="InCall VoIP Activity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+
+        <service
+            android:name=".BackgroundIncomingCallService"
+            android:foregroundServiceType="phoneCall"
+            android:exported="false"
+        />
+
     </application>
 </manifest>
diff --git a/testapps/transactionalVoipApp/res/layout/in_call_activity.xml b/testapps/transactionalVoipApp/res/layout/in_call_activity.xml
new file mode 100644
index 0000000..a92a99b
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/layout/in_call_activity.xml
@@ -0,0 +1,110 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 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/getCallIdTextView"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/get_call_id"
+    />
+
+    <Button
+        android:id="@+id/updateCallStyleNotification"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/update_notification"
+    />
+
+    <Button
+        android:id="@+id/answer_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/answer"/>
+
+    <Button
+        android:id="@+id/set_call_active_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/set_call_active"/>
+
+    <Button
+        android:id="@+id/set_call_inactive_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/set_call_inactive"/>
+
+    <Button
+        android:id="@+id/disconnect_call_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/disconnect_call"/>
+
+    <Button
+        android:id="@+id/start_stream_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/start_stream"/>
+
+    <Button
+        android:id="@+id/crash_app"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/crash_app"/>
+
+    <LinearLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+
+        <TextView
+            android:id="@+id/current_endpoint"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+        />
+
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal">
+
+            <Button
+                android:id="@+id/request_earpiece"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/request_earpiece_endpoint"/>
+
+            <Button
+                android:id="@+id/request_speaker"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/request_speaker_endpoint"/>
+
+            <Button
+                android:id="@+id/request_bluetooth"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/request_bluetooth_endpoint"/>
+        </LinearLayout>
+
+    </LinearLayout>
+</LinearLayout>
diff --git a/testapps/transactionalVoipApp/res/layout/main_activity.xml b/testapps/transactionalVoipApp/res/layout/main_activity.xml
index 86d8e20..28f0744 100644
--- a/testapps/transactionalVoipApp/res/layout/main_activity.xml
+++ b/testapps/transactionalVoipApp/res/layout/main_activity.xml
@@ -38,53 +38,31 @@
             android:layout_height="wrap_content"
             android:text="@string/register_phone_account"/>
 
-        <ToggleButton
-            android:id="@+id/callDirectionButton"
+        <Button
+            android:id="@+id/startForegroundService"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:textOff="@string/direction_outgoing"
-            android:textOn="@string/direction_incoming"
+            android:text="@string/start_foreground_service"
         />
 
         <LinearLayout
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:orientation="horizontal">
-            <Button
-                android:id="@+id/add_call_1_button"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:text="@string/add_call_1"/>
 
             <Button
-                android:id="@+id/disconnect_call_1_button"
+                android:id="@+id/startOutgoingCall"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
-                android:text="@string/disconnect_call_1"/>
-        </LinearLayout>
-
-        <LinearLayout
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:orientation="horizontal">
+                android:text="@string/start_outgoing"
+            />
 
             <Button
-                android:id="@+id/add_call_2_button"
+                android:id="@+id/startIncomingCall"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
-                android:text="@string/add_call_2"/>
-
-            <Button
-                android:id="@+id/set_call_2_active_button"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:text="@string/set_call_active"/>
-
-            <Button
-                android:id="@+id/disconnect_call_2_button"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:text="@string/disconnect_call_2"/>
+                android:text="@string/start_incoming"
+            />
         </LinearLayout>
     </LinearLayout>
 </LinearLayout>
diff --git a/testapps/transactionalVoipApp/res/raw/sample_audio.ogg b/testapps/transactionalVoipApp/res/raw/sample_audio.ogg
new file mode 100644
index 0000000..0129b46
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/raw/sample_audio.ogg
Binary files differ
diff --git a/testapps/transactionalVoipApp/res/raw/sample_audio2.ogg b/testapps/transactionalVoipApp/res/raw/sample_audio2.ogg
new file mode 100644
index 0000000..a0b39b4
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/raw/sample_audio2.ogg
Binary files differ
diff --git a/testapps/transactionalVoipApp/res/values-af/strings.xml b/testapps/transactionalVoipApp/res/values-af/strings.xml
index 3e12b43..bf7ad33 100644
--- a/testapps/transactionalVoipApp/res/values-af/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-af/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"Transactional API-toetsaktiwiteit"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"Transaksionele inoproepaktiwiteit"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"Registreer foonrekening"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"uitgaande"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"inkomend"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"voeg oproep 1 by"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"beëindig oproep 1"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"voeg oproep 2 by"</string>
-    <string name="set_call_active" msgid="248748409907478011">"stel oproep 2 as aktief"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"beëindig oproep 2"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"Begin voorgronddiens (simuleer masjienvertaling + app op agtergrond)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"Begin uitgaande oproep"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"Begin inkomende oproep"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"oproep-id is nie gestel nie"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+    <string name="answer" msgid="5423590397665409939">"antwoord"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"ontkoppel"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Oorstuk"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"Luidspreker"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+    <string name="start_stream" msgid="3567634786280097431">"begin stroom"</string>
+    <string name="crash_app" msgid="2548690390730057704">"gooi uitsondering"</string>
+    <string name="update_notification" msgid="8677916482672588779">"dateer kennisgewing aan voortdurende oproepstyl op"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-am/strings.xml b/testapps/transactionalVoipApp/res/values-am/strings.xml
index 9aba40a..d71c287 100644
--- a/testapps/transactionalVoipApp/res/values-am/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-am/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"የግብይት ኤፒአይ ሙከራ እንቅስቃሴ"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"በጥሪ እንቅስቃሴ ውስጥ ግብይታዊ"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"የስልክ መለያ መዝግብ"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"ወጪ"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"መጪ"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"ጥሪ 1ን አክል"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"የጥሪ 1ን ግንኙነት አቋርጥ"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"ጥሪ 2ን አክል"</string>
-    <string name="set_call_active" msgid="248748409907478011">"ጥሪ 2ን ወደ ንቁ አቀናብር"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"የጥሪ 2ን ግንኙነት አቋርጥ"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"FGS ይጀምሩ (በዳራው ውስጥ MT + መተግበሪያን ያስመስላል)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"ወጪ ጥሪን ይጀምሩ"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"ገቢ ጥሪን ይጀምሩ"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"የደዋይ መታወቂያ አልተቀናበረም"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"ወደ ገቢር ተቀናብሯል"</string>
+    <string name="answer" msgid="5423590397665409939">"መልስ"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"ወደ ገቢር ያልሆነ ተቀናብሯል"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"ግንኙነትን ያቋርጡ"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"ማዳመጫ"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"ድምፅ ማውጫ"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"ብሉቱዝ"</string>
+    <string name="start_stream" msgid="3567634786280097431">"ዥረት ይጀምሩ"</string>
+    <string name="crash_app" msgid="2548690390730057704">"ለየት ያለ ነገርን ይጣሉ"</string>
+    <string name="update_notification" msgid="8677916482672588779">"በመካሄድ ላይ ላለ ጥሪ ቅጥ ማሳወቂያ ያዘምኑ"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-ar/strings.xml b/testapps/transactionalVoipApp/res/values-ar/strings.xml
index c76746b..d2c1464 100644
--- a/testapps/transactionalVoipApp/res/values-ar/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-ar/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"نشاط اختبار واجهة برمجة التطبيقات من خلال المعاملات"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"نشاط المعاملات أثناء المكالمة"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"تسجيل حساب الهاتف"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"الصادرة"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"الواردة"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"إضافة المكالمة 1"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"قطع المكالمة 1"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"إضافة المكالمة 2"</string>
-    <string name="set_call_active" msgid="248748409907478011">"ضبط حالة المكالمة 2 على نشطة"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"قطع المكالمة 2"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"‏بدء FGS (محاكاة الترجمة الآلية + التطبيق في الخلفية)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"بدء مكالمة صادرة"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"بدء مكالمة واردة"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"لم يتم ضبط رقم تعريف المكالمة"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+    <string name="answer" msgid="5423590397665409939">"الإجابة"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"إلغاء الربط"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"سماعة الأذن"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"مكبّر الصوت"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"البلوتوث"</string>
+    <string name="start_stream" msgid="3567634786280097431">"بدء البث"</string>
+    <string name="crash_app" msgid="2548690390730057704">"طرح استثناء"</string>
+    <string name="update_notification" msgid="8677916482672588779">"إشعار التعديل إلى نمط المكالمات الجارية"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-as/strings.xml b/testapps/transactionalVoipApp/res/values-as/strings.xml
index 66fae5f..c48ac0e 100644
--- a/testapps/transactionalVoipApp/res/values-as/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-as/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"লেনদেন সম্বন্ধীয় API পৰীক্ষণৰ কাৰ্যকলাপ"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"কলত হোৱা লেনদেন সম্বন্ধীয় কাৰ্যকলাপ"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"ফ\'নৰ একাউণ্ট পঞ্জীয়ন কৰক"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"বহিৰ্গামী"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"অন্তৰ্গামী"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"কল ১ যোগ কৰক"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"কল ১ৰ সংযোগ বিচ্ছিন্ন কৰক"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"কল ২ যোগ কৰক"</string>
-    <string name="set_call_active" msgid="248748409907478011">"কল ২ক সক্ৰিয় হিচাপে ছেট কৰক"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"কল ২ৰ সংযোগ বিচ্ছিন্ন কৰক"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"FGS আৰম্ভ কৰক (নেপথ্যত MT + এপ্ ছিমুলে’ট কৰক)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"বহিৰ্গামী কল আৰম্ভ কৰক"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"অন্তৰ্গামী কল আৰম্ভ কৰক"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"কলৰ আইডিটো ছেট কৰা হোৱা নাই"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"সক্ৰিয় হিচাপে ছেট কৰক"</string>
+    <string name="answer" msgid="5423590397665409939">"উত্তৰ দিয়ক"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"নিষ্ক্ৰিয় হিচাপে ছেট কৰক"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"সংযোগ বিচ্ছিন্ন কৰক"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"ইয়েৰপিচ"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"স্পীকাৰ"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"ব্লুটুথ"</string>
+    <string name="start_stream" msgid="3567634786280097431">"ষ্ট্ৰীম কৰিবলৈ আৰম্ভ কৰক"</string>
+    <string name="crash_app" msgid="2548690390730057704">"থ্ৰ’ এক্সচেপশ্বন"</string>
+    <string name="update_notification" msgid="8677916482672588779">"চলিত কলৰ শৈলী সম্পৰ্কে আপডে’ট দিয়া জাননী"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-az/strings.xml b/testapps/transactionalVoipApp/res/values-az/strings.xml
index 1bdb545..75d8278 100644
--- a/testapps/transactionalVoipApp/res/values-az/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-az/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"Tranzaksiya ilə bağlı API test Fəaliyyəti"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"Tranzaksiya üzrə Zəngdaxili Fəaliyyət"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"Telefon Hesabını Qeydiyyatdan Keçirin"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"gedən"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"gələn"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"1-ci zəngi əlavə edin"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"1-ci zəngi bitirin"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"2-ci zəngi əlavə edin"</string>
-    <string name="set_call_active" msgid="248748409907478011">"2-ci zəngi aktivləşdirin"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"2-ci zəngi bitirin"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"FGS-ni başladın (arxa fonda MT + tətbiqini simulyasiya edin)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"Gedən zəng başladın"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"Gələn zəng başladın"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"zəng ID-si təyin olunmayıb"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"Aktiv kimi təyin edin"</string>
+    <string name="answer" msgid="5423590397665409939">"cavab"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"Qeyri-aktiv kimi təyin edin"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"əlaqəni kəsin"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Qulaqlıq"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"Dinamik"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+    <string name="start_stream" msgid="3567634786280097431">"yayıma başlayın"</string>
+    <string name="crash_app" msgid="2548690390730057704">"istisna yaradın"</string>
+    <string name="update_notification" msgid="8677916482672588779">"bildirişi davam edən zəng üslubuna yeniləyin"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-b+sr+Latn/strings.xml b/testapps/transactionalVoipApp/res/values-b+sr+Latn/strings.xml
index ad4605d..f824910 100644
--- a/testapps/transactionalVoipApp/res/values-b+sr+Latn/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-b+sr+Latn/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"Aktivnost testiranja transakcionog API-ja"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"Aktivnost poziva u vezi sa transakcijama"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"Registruj nalog telefona"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"odlazni"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"dolazni"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"dodaj 1. poziv"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"prekini 1. poziv"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"dodaj 2. poziv"</string>
-    <string name="set_call_active" msgid="248748409907478011">"podesi 2. poziv kao aktivan"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"prekini 2. poziv"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"Pokreni FGS (simulirajte MT + aplikaciju u pozadini)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"Započnite odlazni poziv"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"Započnite dolazni poziv"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"ID poziva nije podešen"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+    <string name="answer" msgid="5423590397665409939">"odgovori"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"prekini vezu"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Slušalica"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"Zvučnik"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+    <string name="start_stream" msgid="3567634786280097431">"počnite da strimujete"</string>
+    <string name="crash_app" msgid="2548690390730057704">"izbaciti izuzetak"</string>
+    <string name="update_notification" msgid="8677916482672588779">"ažurirajte obaveštenje na stil aktuelnog poziva"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-be/strings.xml b/testapps/transactionalVoipApp/res/values-be/strings.xml
index ecb1464..36d558e 100644
--- a/testapps/transactionalVoipApp/res/values-be/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-be/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"Праверачныя дзеянні API трансакцый"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"Дзеянні падчас выклікаў"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"Зарэгістраваць уліковы запіс тэлефона"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"выходны"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"уваходны"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"дадаць выклік 1"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"завяршыць выклік 1"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"дадаць выклік 2"</string>
-    <string name="set_call_active" msgid="248748409907478011">"зрабіць выклік 2 актыўным"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"завяршыць выклік 2"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"Запусціць FGS (сімуляцыя MT + праграма ў фонавым рэжыме)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"Пачаць выходны выклік"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"Пачаць уваходны выклік"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"ідэнтыфікатар выкліку не зададзены"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+    <string name="answer" msgid="5423590397665409939">"адказаць"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"завяршыць выклік"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Навушнік"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"Дынамік"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+    <string name="start_stream" msgid="3567634786280097431">"пачаць перадачу плынню"</string>
+    <string name="crash_app" msgid="2548690390730057704">"адправіць паведамленне аб выключэнні"</string>
+    <string name="update_notification" msgid="8677916482672588779">"стыль паведамлення аб абнаўленні для бягучага званка"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-bg/strings.xml b/testapps/transactionalVoipApp/res/values-bg/strings.xml
index c822ad5..2210400 100644
--- a/testapps/transactionalVoipApp/res/values-bg/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-bg/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"Активност за тестване на API за транзакции"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"Транзакционална активност в обаждане"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"Регистриране на профила на телефона"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"изходящо"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"входящо"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"добавяне на обаждане 1"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"прекратяване на обаждане 1"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"добавяне на обаждане 2"</string>
-    <string name="set_call_active" msgid="248748409907478011">"задаване на обаждане 2 като активно"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"прекратяване на обаждане 2"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"Стартиране на FGS (симулиране на MT + приложението на заден план)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"Започване на изходящо обаждане"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"Започване на входящо обаждане"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"идентификаторът на обаждането не е зададен"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+    <string name="answer" msgid="5423590397665409939">"отговаряне"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"прекратяване на връзката"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Слушалка"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"Високоговорител"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+    <string name="start_stream" msgid="3567634786280097431">"започване на поточно предаване"</string>
+    <string name="crash_app" msgid="2548690390730057704">"генериране на изключение"</string>
+    <string name="update_notification" msgid="8677916482672588779">"актуализиране на известието до стила на текущото обаждане"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-bn/strings.xml b/testapps/transactionalVoipApp/res/values-bn/strings.xml
index af4ecbc..45f13be 100644
--- a/testapps/transactionalVoipApp/res/values-bn/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-bn/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"Transactional API টেস্ট সংক্রান্ত অ্যাক্টিভিটি"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"কল অ্যাক্টিভিটিতে হওয়া ট্রানজ্যাকশন"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"ফোনের অ্যাকাউন্ট রেজিস্টার করুন"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"আউটগোয়িং"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"ইনকামিং"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"কল ১ যোগ করুন"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"কল ১ ডিসকানেক্ট করুন"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"কল ২ যোগ করুন"</string>
-    <string name="set_call_active" msgid="248748409907478011">"কল ২ চালু আছে হিসেবে সেট করুন"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"কল ২ ডিসকানেক্ট করুন"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"FGS শুরু করুন (সিমুলেট MT + ব্যাকগ্রাউন্ডে থাকা অ্যাপ)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"আউটগোয়িং কল শুরু করুন"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"ইনকামিং কল শুরু করুন"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"কলার আইডি সেট করা নেই"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+    <string name="answer" msgid="5423590397665409939">"উত্তর দিন"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"ডিসকানেক্ট করুন"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"ইয়ারপিস"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"স্পিকার"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"ব্লুটুথ"</string>
+    <string name="start_stream" msgid="3567634786280097431">"স্ট্রিমিং শুরু করুন"</string>
+    <string name="crash_app" msgid="2548690390730057704">"এক্সেপশন যোগ করুন"</string>
+    <string name="update_notification" msgid="8677916482672588779">"চালু থাকা কলের স্টাইলে আপডেট সংক্রান্ত বিজ্ঞপ্তি"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-bs/strings.xml b/testapps/transactionalVoipApp/res/values-bs/strings.xml
index 96cedc5..24ffba2 100644
--- a/testapps/transactionalVoipApp/res/values-bs/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-bs/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"Aktivnost testa transakcijskog API-ja"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"Transakcijska aktivnost u pozivu"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"Registrirajte račun telefona"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"odlazno"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"dolazno"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"dodaj poziv 1"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"prekini poziv 1"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"dodaj poziv 2"</string>
-    <string name="set_call_active" msgid="248748409907478011">"postavi poziv 2 kao aktivan"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"prekini poziv 2"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"Pokreni FGS (simuliraj MT i aplikaciju u pozadini)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"Pokreni odlazni poziv"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"Pokreni dolazni poziv"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"ID poziva nije postavljen"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"postavi na Aktivno"</string>
+    <string name="answer" msgid="5423590397665409939">"odgovori"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"postavi na Neaktivno"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"prekini vezu"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Slušalica"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"Zvučnik"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+    <string name="start_stream" msgid="3567634786280097431">"pokreni prijenos"</string>
+    <string name="crash_app" msgid="2548690390730057704">"izbaci izuzetak"</string>
+    <string name="update_notification" msgid="8677916482672588779">"ažuriraj obavještenje u stil poziva u toku"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-ca/strings.xml b/testapps/transactionalVoipApp/res/values-ca/strings.xml
index ab16064..5500444 100644
--- a/testapps/transactionalVoipApp/res/values-ca/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-ca/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"Activitat de prova de l\'API transaccional"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"Activitat de transaccions durant la trucada"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"Registra el compte del telèfon"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"sortint"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"entrant"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"afegeix la trucada 1"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"desconnecta la trucada 1"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"afegeix la trucada 2"</string>
-    <string name="set_call_active" msgid="248748409907478011">"defineix la trucada 2 com a activa"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"desconnecta la trucada 2"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"Inicia FGS (simula MT + aplicació en segon pla)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"Inicia una trucada sortint"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"Inicia una trucada entrant"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"identificador de trucada no definit"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"defineix com a activa"</string>
+    <string name="answer" msgid="5423590397665409939">"respon"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"defineix com a inactiva"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"desconnecta"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Auricular"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"Altaveu"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+    <string name="start_stream" msgid="3567634786280097431">"inicia la reproducció en línia"</string>
+    <string name="crash_app" msgid="2548690390730057704">"llança una excepció"</string>
+    <string name="update_notification" msgid="8677916482672588779">"actualitza la notificació a l\'estil de trucada en curs"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-cs/strings.xml b/testapps/transactionalVoipApp/res/values-cs/strings.xml
index b7be57f..6632765 100644
--- a/testapps/transactionalVoipApp/res/values-cs/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-cs/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"Aktivita testování v transakčním rozhraní API"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"Transakční aktivita během hovoru"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"Registrovat telefonní účet"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"odchozí"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"příchozí"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"přidat hovor 1"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"odpojit hovor 1"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"přidat hovor 2"</string>
-    <string name="set_call_active" msgid="248748409907478011">"nastavit hovor 2 jako aktivní"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"odpojit hovor 2"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"Spustit službu v popředí (simulovat MT a aplikaci v pozadí)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"Zahájit odchozí hovor"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"Zahájit příchozí hovor"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"ID hovoru není nastaveno"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+    <string name="answer" msgid="5423590397665409939">"odpověď"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"odpojit"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Sluchátko"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"Reproduktor"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+    <string name="start_stream" msgid="3567634786280097431">"zahájit streamování"</string>
+    <string name="crash_app" msgid="2548690390730057704">"vyvolat výjimku"</string>
+    <string name="update_notification" msgid="8677916482672588779">"styl aktualizace oznámení o probíhajícím hovoru"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-da/strings.xml b/testapps/transactionalVoipApp/res/values-da/strings.xml
index 46e6a33..1a23b58 100644
--- a/testapps/transactionalVoipApp/res/values-da/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-da/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"Testaktivitet for transaktions-API"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"Transaktionsrelateret aktivitet i opkald"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"Registrer telefonkonto"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"udgående"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"indgående"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"tilføj opkald 1"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"afslut opkald 1"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"tilføj opkald 2"</string>
-    <string name="set_call_active" msgid="248748409907478011">"konfigurer opkald 2 som aktivt"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"afslut opkald 2"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"Start FGS (simuler maskinoversættelse + app i baggrunden)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"Start udgående opkald"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"Start indgående opkald"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"opkalds-id ikke konfigureret"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"Indstil som aktiv"</string>
+    <string name="answer" msgid="5423590397665409939">"svar"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"Indstil som inaktiv"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"afslut opkald"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Højttaler"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"Højttaler"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+    <string name="start_stream" msgid="3567634786280097431">"start med at streame"</string>
+    <string name="crash_app" msgid="2548690390730057704">"udløs en undtagelse"</string>
+    <string name="update_notification" msgid="8677916482672588779">"opdateringsnotifikation til igangværende opkaldsstil"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-de/strings.xml b/testapps/transactionalVoipApp/res/values-de/strings.xml
index b7d04e0..4f853fc 100644
--- a/testapps/transactionalVoipApp/res/values-de/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-de/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"Testaktivität zur transaktionalen API"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"Transaktionsaktivität bei aktiven Anruf"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"Telefonkonto registrieren"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"Ausgehend"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"Eingehend"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"Anruf 1 hinzufügen"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"Anruf 1 trennen"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"Anruf 2 hinzufügen"</string>
-    <string name="set_call_active" msgid="248748409907478011">"Anruf 2 aktivieren"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"Anruf 2 trennen"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"FGS starten (MT und App im Hintergrund simulieren)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"Ausgehenden Anruf starten"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"Eingehenden Anruf starten"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"Anrufer-ID nicht festgelegt"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"aktiv"</string>
+    <string name="answer" msgid="5423590397665409939">"annehmen"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"inaktiv"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"beenden"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Kopfhörer"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"Lautsprecher"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+    <string name="start_stream" msgid="3567634786280097431">"Streaming starten"</string>
+    <string name="crash_app" msgid="2548690390730057704">"Ausnahme auslösen"</string>
+    <string name="update_notification" msgid="8677916482672588779">"Benachrichtigung zum Stil des laufenden Anrufs aktualisieren"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-el/strings.xml b/testapps/transactionalVoipApp/res/values-el/strings.xml
index bb5b0d9..5553981 100644
--- a/testapps/transactionalVoipApp/res/values-el/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-el/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"Δοκιμαστική δραστηριότητα API συναλλαγών"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"Δραστηριότητα συναλλαγής στην κλήση"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"Εγγραφή λογαριασμού τηλεφώνου"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"εξερχόμενη"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"εισερχόμενη"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"προσθήκη κλήσης 1"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"αποσύνδεση κλήσης 1"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"προσθήκη κλήσης 2"</string>
-    <string name="set_call_active" msgid="248748409907478011">"ορισμός κλήσης 2 ως ενεργής"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"αποσύνδεση κλήσης 2"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"Έναρξη FGS (προσομοίωση MT + εφαρμογή στο παρασκήνιο)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"Έναρξη εξερχόμενης κλήσης"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"Έναρξη εισερχόμενης κλήσης"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"δεν έχει οριστεί αναγνωριστικό κλήσης"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+    <string name="answer" msgid="5423590397665409939">"απάντηση"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"αποσύνδεση"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Ακουστικό τηλεφώνου"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"Ηχείο"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+    <string name="start_stream" msgid="3567634786280097431">"έναρξη ροής"</string>
+    <string name="crash_app" msgid="2548690390730057704">"εμφάνιση εξαίρεσης"</string>
+    <string name="update_notification" msgid="8677916482672588779">"ενημέρωση ειδοποίησης στο στιλ κλήσης σε εξέλιξη"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-en-rAU/strings.xml b/testapps/transactionalVoipApp/res/values-en-rAU/strings.xml
index 95c71e4..bf68cf5 100644
--- a/testapps/transactionalVoipApp/res/values-en-rAU/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-en-rAU/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"Transactional API test activity"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"Transactional in-call activity"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"Register phone account"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"outgoing"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"incoming"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"add call 1"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"disconnect call 1"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"add call 2"</string>
-    <string name="set_call_active" msgid="248748409907478011">"set call 2 active"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"disconnect call 2"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"Start FGS (simulate MT + app in background)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"Start outgoing call"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"Start incoming call"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"call ID not set"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+    <string name="answer" msgid="5423590397665409939">"answer"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"disconnect"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Earpiece"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"Speaker"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+    <string name="start_stream" msgid="3567634786280097431">"start streaming"</string>
+    <string name="crash_app" msgid="2548690390730057704">"throw exception"</string>
+    <string name="update_notification" msgid="8677916482672588779">"Update notification to ongoing call style"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-en-rCA/strings.xml b/testapps/transactionalVoipApp/res/values-en-rCA/strings.xml
index 0c705e3..269f0d3 100644
--- a/testapps/transactionalVoipApp/res/values-en-rCA/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-en-rCA/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"Transactional API test Activity"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"Transactional In Call Activity"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"Register Phone Account"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"outgoing"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"incoming"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"add call 1"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"disconnect call 1"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"add call 2"</string>
-    <string name="set_call_active" msgid="248748409907478011">"set call 2 active"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"disconnect call 2"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"Start FGS (simulate MT + app in background)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"Start Outgoing Call"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"Start Incoming Call"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"call id not set"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+    <string name="answer" msgid="5423590397665409939">"answer"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"disconnect"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Earpiece"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"Speaker"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+    <string name="start_stream" msgid="3567634786280097431">"start streaming"</string>
+    <string name="crash_app" msgid="2548690390730057704">"throw exception"</string>
+    <string name="update_notification" msgid="8677916482672588779">"update notification to ongoing call style"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-en-rGB/strings.xml b/testapps/transactionalVoipApp/res/values-en-rGB/strings.xml
index 95c71e4..bf68cf5 100644
--- a/testapps/transactionalVoipApp/res/values-en-rGB/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-en-rGB/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"Transactional API test activity"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"Transactional in-call activity"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"Register phone account"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"outgoing"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"incoming"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"add call 1"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"disconnect call 1"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"add call 2"</string>
-    <string name="set_call_active" msgid="248748409907478011">"set call 2 active"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"disconnect call 2"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"Start FGS (simulate MT + app in background)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"Start outgoing call"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"Start incoming call"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"call ID not set"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+    <string name="answer" msgid="5423590397665409939">"answer"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"disconnect"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Earpiece"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"Speaker"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+    <string name="start_stream" msgid="3567634786280097431">"start streaming"</string>
+    <string name="crash_app" msgid="2548690390730057704">"throw exception"</string>
+    <string name="update_notification" msgid="8677916482672588779">"Update notification to ongoing call style"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-en-rIN/strings.xml b/testapps/transactionalVoipApp/res/values-en-rIN/strings.xml
index 95c71e4..bf68cf5 100644
--- a/testapps/transactionalVoipApp/res/values-en-rIN/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-en-rIN/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"Transactional API test activity"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"Transactional in-call activity"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"Register phone account"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"outgoing"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"incoming"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"add call 1"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"disconnect call 1"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"add call 2"</string>
-    <string name="set_call_active" msgid="248748409907478011">"set call 2 active"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"disconnect call 2"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"Start FGS (simulate MT + app in background)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"Start outgoing call"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"Start incoming call"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"call ID not set"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+    <string name="answer" msgid="5423590397665409939">"answer"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"disconnect"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Earpiece"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"Speaker"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+    <string name="start_stream" msgid="3567634786280097431">"start streaming"</string>
+    <string name="crash_app" msgid="2548690390730057704">"throw exception"</string>
+    <string name="update_notification" msgid="8677916482672588779">"Update notification to ongoing call style"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-en-rXC/strings.xml b/testapps/transactionalVoipApp/res/values-en-rXC/strings.xml
index 2e69f39..d94683a 100644
--- a/testapps/transactionalVoipApp/res/values-en-rXC/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-en-rXC/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‎‏‎‏‎‎‎‎‏‎‏‏‎‏‎‏‎‎‏‏‎‎‎‏‎‏‏‏‎‎‎‎‎‏‏‏‎‎‎‏‎‎‎‏‏‎‏‏‏‏‎‏‎‎‎‏‎‎‎‎‎‏‏‎Transactional API test Activity‎‏‎‎‏‎"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‎‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‎‎‎‎‏‏‏‏‏‏‏‎‏‎‎‏‏‎‏‏‎‎‏‎‏‎‏‎‏‏‎‎‎‏‎‏‏‎‎‎‏‎Transactional In Call Activity‎‏‎‎‏‎"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‎‏‏‏‎‏‎‏‎‏‎‎‏‏‎‎‏‎‏‎‏‎‏‎‎‏‏‎‎‎‏‏‎‏‏‎‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‏‏‏‎‏‏‏‏‏‏‎‎‎Register Phone Account‎‏‎‎‏‎"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‎‏‎‎‎‏‏‎‎‏‎‏‏‏‏‎‏‏‏‎‎‎‎‏‏‎‎‎‎‎‎‎‎‏‏‎‏‎‏‏‎‎‏‏‏‎‏‎‏‏‏‎‏‏‎‎‏‏‎‏‎‎‎‎outgoing‎‏‎‎‏‎"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‎‏‎‏‏‎‏‏‎‎‎‎‏‎‎‏‎‎‏‏‏‎‏‏‏‎‎‎‏‎‏‏‎‎‏‎‏‎‎‏‎‎‎‎‏‎‎‏‏‎‎‎‎‎‏‏‏‏‏‎‎‏‏‎incoming‎‏‎‎‏‎"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‏‏‎‏‎‎‎‎‏‏‎‏‏‎‎‏‎‎‎‏‎‎‎‏‎‎‏‏‏‎‏‏‎‏‏‏‎‏‎‏‎‎‎‏‏‏‎‎‏‏‎‎‏‎‎‎‎‏‎‏‏‎‎‏‎add call 1‎‏‎‎‏‎"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‎‏‎‎‏‎‏‎‏‎‎‏‏‎‏‏‎‎‎‏‏‏‎‎‎‎‏‏‎‏‏‏‏‎‎‎‏‏‏‎‎‎‏‎‎‎‏‏‎‎‎‏‏‎‎‏‎‎‎‏‎‏‏‎disconnect call 1‎‏‎‎‏‎"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‏‏‎‏‏‏‎‏‎‎‎‏‎‎‎‎‏‎‎‎‎‏‎‎‎‏‎‎‎‎‎‎‏‎‎‏‎‎‏‏‎‏‏‏‏‎‏‎‎‏‏‏‎‎‏‎‏‎‏‏‏‎‏‎‎add call 2‎‏‎‎‏‎"</string>
-    <string name="set_call_active" msgid="248748409907478011">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‎‏‎‏‏‎‏‏‏‎‎‏‏‏‎‏‏‏‎‏‏‎‏‎‏‏‏‎‎‎‏‏‎‎‎‏‎‏‏‏‏‏‎‏‎‎‏‏‏‏‏‎‏‏‏‏‏‏‎‏‏‎set call 2 active‎‏‎‎‏‎"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‎‎‏‏‏‏‎‏‏‎‎‎‏‏‏‎‏‎‎‏‏‏‏‏‎‎‎‏‎‏‏‎‏‎‏‏‎‎‏‏‎‎‎‏‎‏‏‎‎‏‏‎‏‏‏‏‏‏‎‎‎disconnect call 2‎‏‎‎‏‎"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‎‎‏‏‏‎‏‏‏‎‏‏‎‏‎‏‏‏‏‎‎‏‎‏‎‎‏‏‎‎‎‎‏‏‎‎‎‎‎‏‎‎‏‎‏‎‎‎‏‏‏‏‏‏‏‏‎‎Start FGS (simulate MT + app in background)‎‏‎‎‏‎"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‎‏‏‎‏‎‎‎‎‎‎‎‎‎‏‏‎‏‏‏‏‏‏‏‎‎‏‏‏‎‏‎‎‏‎‎‏‎‎‎‎‎‎‎‏‎‏‎‎‏‏‏‎‎‎‎‎‎‎‏‎‎‎‎Start Outgoing Call‎‏‎‎‏‎"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‏‏‎‏‏‎‎‏‎‏‏‏‎‎‎‏‎‎‏‎‏‏‏‎‎‎‏‎‏‎‏‎‎‎‎‏‏‏‏‎‏‏‏‎‏‏‏‎‏‏‎‎‏‏‎‏‏‎‏‏‎‏‏‏‎Start Incoming Call‎‏‎‎‏‎"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‏‏‎‎‏‏‎‎‏‎‎‎‎‏‎‏‎‏‏‏‎‏‏‎‎‎‏‎‏‎‏‎‎‏‎‎‎‏‏‏‎‎‎‏‎‏‎‏‏‎‏‎‏‏‎‎‎‏‏‎‎‏‎‎‎call id not set‎‏‎‎‏‎"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‎‏‎‏‏‏‎‏‎‏‏‎‏‎‎‎‏‎‏‎‎‎‏‎‏‏‏‎‏‏‏‎‎‎‏‏‎‎‎‏‎‎‎‏‏‏‏‎‎‎‎‏‏‏‏‎‎‎‎‏‎‏‏‎setActive‎‏‎‎‏‎"</string>
+    <string name="answer" msgid="5423590397665409939">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‏‏‎‎‏‎‏‏‎‏‎‎‎‏‎‎‎‏‏‏‎‏‏‎‏‎‏‏‏‎‏‏‎‎‏‏‎‏‎‎‎‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‏‎‎‏‏‎answer‎‏‎‎‏‎"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‎‏‎‏‎‎‎‎‎‎‏‎‏‎‏‏‎‎‏‏‎‎‏‏‎‏‏‎‎‎‏‎‎‎‎‏‎‏‏‏‎‏‏‏‎‏‏‎‎‏‎‏‎‏‎‏‏‎setInactive‎‏‎‎‏‎"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‎‏‏‎‎‏‎‏‎‏‏‏‎‏‎‎‎‎‏‎‎‏‏‎‏‏‎‏‎‎‏‎‎‎‏‎‎‎‎‎‎‏‏‎‏‏‎‎‏‎‎‎‏‏‏‎‏‏‏‏‎‎‏‎disconnect‎‏‎‎‏‎"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‏‏‎‏‏‏‎‎‎‏‎‎‏‎‎‎‎‎‎‎‎‏‏‎‎‏‏‏‏‏‎‏‎‏‎‎‏‏‏‏‏‎‏‎‎‎‏‎‏‏‏‏‏‎‎‎‏‎‏‏‏‏‎‏‎Earpiece‎‏‎‎‏‎"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‎‎‏‏‏‎‎‏‎‏‎‏‏‎‏‏‏‎‎‎‎‎‎‎‎‏‎‎‏‎‏‎‏‎‏‎‎‏‏‎‏‏‏‏‎‎‏‎‎‎‎‏‏‎‏‎‎‏‏‏‎‏‎Speaker‎‏‎‎‏‎"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‏‏‎‏‎‎‏‎‎‏‎‏‎‏‏‏‎‎‏‎‎‏‏‏‎‏‎‎‏‏‏‎‎‎‏‎‎‎‏‎‎‏‏‏‏‏‎‎‏‏‎‎‎‎‏‎‏‎‏‏‏‏‎‎‎Bluetooth‎‏‎‎‏‎"</string>
+    <string name="start_stream" msgid="3567634786280097431">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‎‏‏‎‎‎‏‏‎‎‎‎‎‏‎‏‏‎‎‏‎‎‎‏‏‏‎‏‏‏‏‏‎‎‏‏‏‎‎‏‎‎‎‏‏‎‏‎‏‎‎‎‏‏‎‏‎‎‏‎‏‏‏‎start streaming‎‏‎‎‏‎"</string>
+    <string name="crash_app" msgid="2548690390730057704">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‎‏‎‎‎‏‏‎‏‎‏‏‏‏‎‏‏‎‎‎‏‎‎‎‏‏‎‏‎‎‎‎‎‏‎‎‏‎‏‏‏‏‎‏‎‏‎‏‎‏‎‎‏‏‏‏‏‏‎‏‎‎‎‎throw exception‎‏‎‎‏‎"</string>
+    <string name="update_notification" msgid="8677916482672588779">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‎‎‏‏‎‏‏‏‎‎‎‏‎‎‏‏‏‎‎‎‏‎‎‎‎‎‎‏‏‏‏‎‎‎‎‏‏‎‏‎‎‏‏‏‏‎‏‏‏‏‏‏‎‏‎‏‏‎update notification to ongoing call style‎‏‎‎‏‎"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-es-rUS/strings.xml b/testapps/transactionalVoipApp/res/values-es-rUS/strings.xml
index 06098c4..da554d1 100644
--- a/testapps/transactionalVoipApp/res/values-es-rUS/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-es-rUS/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"Actividad de prueba de la API transaccional"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"Actividad transaccional en las llamadas"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"Registrar cuenta telefónica"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"saliente"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"entrante"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"agregar llamada 1"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"desconectar la llamada 1"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"agregar llamada 2"</string>
-    <string name="set_call_active" msgid="248748409907478011">"establecer llamada 2 activa"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"desconectar la llamada 2"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"Iniciar FGS (simulación de TA y app en segundo plano)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"Iniciar llamada saliente"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"Iniciar llamada entrante"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"no se estableció el identificador de llamadas"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+    <string name="answer" msgid="5423590397665409939">"responder"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"desconectar"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Auricular"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"Bocina"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+    <string name="start_stream" msgid="3567634786280097431">"Iniciar transmisión"</string>
+    <string name="crash_app" msgid="2548690390730057704">"generación de excepción"</string>
+    <string name="update_notification" msgid="8677916482672588779">"notificación de actualización del estilo de llamada en curso"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-es/strings.xml b/testapps/transactionalVoipApp/res/values-es/strings.xml
index 1391d3c..b3f2919 100644
--- a/testapps/transactionalVoipApp/res/values-es/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-es/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"Actividad de prueba de API transaccional"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"Actividad transaccional durante la llamada"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"Registrar cuenta de teléfono"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"saliente"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"entrante"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"añadir llamada 1"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"desconectar llamada 1"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"añadir llamada 2"</string>
-    <string name="set_call_active" msgid="248748409907478011">"activar llamada 2"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"desconectar llamada 2"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"Iniciar FGS (simular MT + aplicación en segundo plano)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"Iniciar llamada saliente"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"Iniciar llamada entrante"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"identificador de llamada no definido"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"Activar"</string>
+    <string name="answer" msgid="5423590397665409939">"responder"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"Desactivar"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"desconectar"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Auricular"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"Altavoz"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+    <string name="start_stream" msgid="3567634786280097431">"iniciar emisión"</string>
+    <string name="crash_app" msgid="2548690390730057704">"excepción de expresión \"throw\""</string>
+    <string name="update_notification" msgid="8677916482672588779">"actualizar notificación al estilo de llamada en curso"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-et/strings.xml b/testapps/transactionalVoipApp/res/values-et/strings.xml
index c2dbe2e..4cc5aab 100644
--- a/testapps/transactionalVoipApp/res/values-et/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-et/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"Tehingupõhise API testimise tegevus"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"Kõnesisene toimingutegevus"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"Telefonikonto registreerimine"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"väljaminevad"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"sissetulevad"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"lisa kõne 1"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"lõpeta kõne 1"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"lisa kõne 2"</string>
-    <string name="set_call_active" msgid="248748409907478011">"kõne 2 aktiivseks seadmine"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"lõpeta kõne 2"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"Käivita FGS (simuleeri taustal MT-d ja rakendust)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"Alusta väljuvat kõnet"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"Alusta sissetulevat kõnet"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"helistaja ID pole seadistatud"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+    <string name="answer" msgid="5423590397665409939">"vastus"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"katkesta ühendus"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Kuular"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"Kõlar"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+    <string name="start_stream" msgid="3567634786280097431">"käivita voogesitus"</string>
+    <string name="crash_app" msgid="2548690390730057704">"erandi viskamine"</string>
+    <string name="update_notification" msgid="8677916482672588779">"värskendage märguannet käimasoleva kõne stiilis"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-eu/strings.xml b/testapps/transactionalVoipApp/res/values-eu/strings.xml
index f3b0406..8b3a181 100644
--- a/testapps/transactionalVoipApp/res/values-eu/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-eu/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"Transakzio bidezko APIen proba-jarduerak"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"Deiko transakzio-jarduerak"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"Erregistratu telefonoaren kontua"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"irteerakoa"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"sarrerakoa"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"gehitu 1. deia"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"deskonektatu 1. deia"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"gehitu 2. deia"</string>
-    <string name="set_call_active" msgid="248748409907478011">"ezarri 2. deia aktibo gisa"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"deskonektatu 2. deia"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"Hasi FGS (simulatu itzulpen automatikoa + aplikazioa atzeko planoan)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"Hasi irteerako dei bat simulatzen"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"Hasi sarrerako dei bat simulatzen"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"ez da ezarri deiaren identifikatzailea"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+    <string name="answer" msgid="5423590397665409939">"erantzun"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"deskonektatu"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Aurikularrak"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"Bozgorailua"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetootha"</string>
+    <string name="start_stream" msgid="3567634786280097431">"hasi zuzenean igortzen"</string>
+    <string name="crash_app" msgid="2548690390730057704">"eman salbuespena"</string>
+    <string name="update_notification" msgid="8677916482672588779">"eguneratu jakinarazpena, abian den deiaren estiloarekin bat etor dadin"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-fa/strings.xml b/testapps/transactionalVoipApp/res/values-fa/strings.xml
index 634f55e..88143cb 100644
--- a/testapps/transactionalVoipApp/res/values-fa/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-fa/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"‏فعالیت آزمایشی Transactional API"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"تبادلی در فعالیت تماس"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"ثبت حساب تلفن"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"خروجی"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"ورودی"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"افزودن تماس ۱"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"قطع تماس ۱"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"افزودن تماس ۲"</string>
-    <string name="set_call_active" msgid="248748409907478011">"تنظیم تماس ۲ روی حالت فعال"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"قطع تماس ۲"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"‏شروع FGS (شبیه‌سازی ترجمه ماشینی + برنامه در پس‌زمینه)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"شروع تماس خروجی"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"شروع تماس ورودی"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"شناسه تماس تنظیم نشده است"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"تنظیم به‌عنوان فعال"</string>
+    <string name="answer" msgid="5423590397665409939">"پاسخ"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"تنظیم به‌عنوان غیرفعال"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"قطع ارتباط"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"گوشی"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"بلندگو"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"بلوتوث"</string>
+    <string name="start_stream" msgid="3567634786280097431">"شروع جاری‌سازی"</string>
+    <string name="crash_app" msgid="2548690390730057704">"استثنا قائل شدن"</string>
+    <string name="update_notification" msgid="8677916482672588779">"به‌روزرسانی اعلان به‌سبک تماس درحال انجام"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-fi/strings.xml b/testapps/transactionalVoipApp/res/values-fi/strings.xml
index 6c9c5f7..673d56d 100644
--- a/testapps/transactionalVoipApp/res/values-fi/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-fi/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"Tapahtuman API-testitoiminta"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"Tapahtuman puhelunaikainen toiminta"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"Rekisteröi puhelintili"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"lähtevä"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"saapuva"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"lisää puhelu 1"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"katkaise puhelu 1"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"lisää puhelu 2"</string>
-    <string name="set_call_active" msgid="248748409907478011">"aseta puhelu 2 aktiiviseksi"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"katkaise puhelu 2"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"Käynnistä FGS (simuloi MT + sovellus taustalla)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"Aloita lähtevä puhelu"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"Aloita saapuva puhelu"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"soittajan tunnusta ei asetettu"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"aseta aktiiviseksi"</string>
+    <string name="answer" msgid="5423590397665409939">"vastaa"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"aseta ei-aktiiviseksi"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"katkaise yhteys"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Kaiutin"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"Kaiutin"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+    <string name="start_stream" msgid="3567634786280097431">"aloita suoratoisto"</string>
+    <string name="crash_app" msgid="2548690390730057704">"lähetyspoikkeus"</string>
+    <string name="update_notification" msgid="8677916482672588779">"päivitä ilmoitus käynnissä olevan puhelun tyyliin"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-fr-rCA/strings.xml b/testapps/transactionalVoipApp/res/values-fr-rCA/strings.xml
index 5c9a397..d58aa13 100644
--- a/testapps/transactionalVoipApp/res/values-fr-rCA/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-fr-rCA/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"Activité de test de l\'API transactionnelle"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"Activité transactionnelle durant l\'appel"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"Inscrire un compte téléphonique"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"sortant"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"entrant"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"ajouter l\'appel 1"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"déconnecter l\'appel 1"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"ajouter l\'appel 2"</string>
-    <string name="set_call_active" msgid="248748409907478011">"définir l\'appel 2 comme actif"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"déconnecter l\'appel 2"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"Démarrer FGS (simuler TA + application en arrière-plan)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"Démarrer un appel sortant"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"Démarrer un appel entrant"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"identifiant de l\'appel non défini"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+    <string name="answer" msgid="5423590397665409939">"répondre"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"déconnecter"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Écouteur"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"Haut-parleur"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+    <string name="start_stream" msgid="3567634786280097431">"démarrer une diffusion"</string>
+    <string name="crash_app" msgid="2548690390730057704">"générer une exception"</string>
+    <string name="update_notification" msgid="8677916482672588779">"modifier la notification en fonction du style de l\'appel en cours"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-fr/strings.xml b/testapps/transactionalVoipApp/res/values-fr/strings.xml
index 48d8062..780b8e8 100644
--- a/testapps/transactionalVoipApp/res/values-fr/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-fr/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"Activité de test de l\'API transactionnelle"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"Activité transactionnelle en cours d\'appel"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"Enregistrer un compte de téléphonie"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"sortant"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"entrant"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"ajouter un appel 1"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"mettre fin à l\'appel 1"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"ajouter un appel 2"</string>
-    <string name="set_call_active" msgid="248748409907478011">"définir l\'appel 2 comme actif"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"mettre fin à l\'appel 2"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"Démarrer les services de premier plan (simuler la MT + l\'application en arrière-plan)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"Démarrer un appel sortant"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"Démarrer un appel entrant"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"affichage du numéro de l\'appelant non défini"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"Définir comme actif"</string>
+    <string name="answer" msgid="5423590397665409939">"réponse"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"Définir comme inactif"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"raccrocher"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Écouteur"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"Haut-parleur"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+    <string name="start_stream" msgid="3567634786280097431">"démarrer la diffusion"</string>
+    <string name="crash_app" msgid="2548690390730057704">"générer une exception"</string>
+    <string name="update_notification" msgid="8677916482672588779">"modifier la notification en fonction du style de l\'appel en cours"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-gl/strings.xml b/testapps/transactionalVoipApp/res/values-gl/strings.xml
index 70cc9f6..f168ab2 100644
--- a/testapps/transactionalVoipApp/res/values-gl/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-gl/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"Actividade de proba da API transaccional"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"Actividade transaccional nas chamadas"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"Rexistrar conta do teléfono"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"saínte"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"entrante"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"engadir chamada 1"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"desconectar chamada 1"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"engadir chamada 2"</string>
-    <string name="set_call_active" msgid="248748409907478011">"definir chamada 2 como activa"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"desconectar chamada 2"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"Iniciar FGS (simular MT + aplicación en segundo plano)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"Iniciar chamada saínte"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"Iniciar chamada entrante"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"identificador de chamada non definido"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+    <string name="answer" msgid="5423590397665409939">"responder"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"desconectar"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Auricular"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"Altofalante"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+    <string name="start_stream" msgid="3567634786280097431">"iniciar reprodución en tempo real"</string>
+    <string name="crash_app" msgid="2548690390730057704">"activar excepción"</string>
+    <string name="update_notification" msgid="8677916482672588779">"actualiza a notificación en función do estilo da chamada en curso"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-gu/strings.xml b/testapps/transactionalVoipApp/res/values-gu/strings.xml
index d9935ae..60bb0b7 100644
--- a/testapps/transactionalVoipApp/res/values-gu/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-gu/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"Transactional APIના પરીક્ષણની પ્રવૃત્તિ"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"કૉલમાંની વ્યવહારિક પ્રવૃત્તિ"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"ફોન એકાઉન્ટ રજિસ્ટર કરો"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"આઉટગોઇંગ"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"ઇનકમિંગ"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"કૉલ 1 ઉમેરો"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"કૉલ 1 ડિસ્કનેક્ટ કરો"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"કૉલ 2 ઉમેરો"</string>
-    <string name="set_call_active" msgid="248748409907478011">"કૉલ 2ને સક્રિય પર સેટ કરો"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"કૉલ 2 ડિસ્કનેક્ટ કરો"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"FGS (MT સિમ્યુલેટ કરવું + બૅકગ્રાઉન્ડમાં ઍપ) શરૂ કરો"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"આઉટગોઇંગ કૉલ શરૂ કરો"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"ઇનકમિંગ કૉલ શરૂ કરો"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"કૉલર ID સેટ કરેલું નથી"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"સક્રિય તરીકે સેટ કરો"</string>
+    <string name="answer" msgid="5423590397665409939">"જવાબ"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"નિષ્ક્રિય તરીકે સેટ કરો"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"ડિસ્કનેક્ટ કરો"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"ઇયરપીસ"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"સ્પીકર"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"બ્લૂટૂથ"</string>
+    <string name="start_stream" msgid="3567634786280097431">"સ્ટ્રીમિંગ શરૂ કરો"</string>
+    <string name="crash_app" msgid="2548690390730057704">"અપવાદ થ્રો કરો"</string>
+    <string name="update_notification" msgid="8677916482672588779">"ચાલુ કૉલ શૈલી પર નોટિફિકેશન અપડેટ કરો"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-hi/strings.xml b/testapps/transactionalVoipApp/res/values-hi/strings.xml
index 9121a37..ba4262a 100644
--- a/testapps/transactionalVoipApp/res/values-hi/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-hi/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"Transactional API से जुड़ी टेस्ट गतिविधि"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"कॉल में क्लाइंट और सर्वर के बीच हुई बातचीत से जुड़ी गतिविधि"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"Phone Account में रजिस्टर करें"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"किए जाने वाले (आउटगोइंग) कॉल"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"आने वाले (इनकमिंग) कॉल"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"कॉल 1 जोड़ें"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"कॉल 1 को डिसकनेक्ट करें"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"कॉल 2 जोड़ें"</string>
-    <string name="set_call_active" msgid="248748409907478011">"कॉल 2 को \'चालू है\' के तौर पर सेट करें"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"कॉल 2 को डिसकनेक्ट करें"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"FGS शुरू करें (बैकग्राउंड में MT + ऐप्लिकेशन को सिम्युलेट करें)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"आउटगोइंग कॉल शुरू करें"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"इनकमिंग कॉल शुरू करें"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"कॉल आईडी सेट नहीं है"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+    <string name="answer" msgid="5423590397665409939">"जवाब"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"डिसकनेक्ट करें"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"ईयरपीस"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"स्पीकर"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"ब्लूटूथ"</string>
+    <string name="start_stream" msgid="3567634786280097431">"स्ट्रीमिंग शुरू करें"</string>
+    <string name="crash_app" msgid="2548690390730057704">"अपवाद जोड़ें"</string>
+    <string name="update_notification" msgid="8677916482672588779">"मौजूदा कॉल की स्टाइल के हिसाब से सूचनाओं को अपडेट करें"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-hr/strings.xml b/testapps/transactionalVoipApp/res/values-hr/strings.xml
index 68291d6..c324f6d 100644
--- a/testapps/transactionalVoipApp/res/values-hr/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-hr/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"Testna aktivnost API-ja za transakcije"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"transakcijska aktivnost u pozivu"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"Registracija telefonskog računa"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"odlazni"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"dolazni"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"dodavanje 1. poziva"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"prekid 1. poziva"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"dodavanje 2. poziva"</string>
-    <string name="set_call_active" msgid="248748409907478011">"postavljane 2. poziva kao aktivnog"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"prekid 2. poziva"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"Pokretanje FGS-a (simulacija: MT i aplikacija u pozadini)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"Pokretanje odlaznog poziva"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"Pokretanje dolaznog poziva"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"id poziva nije postavljen"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"Postavljanje kao aktivno"</string>
+    <string name="answer" msgid="5423590397665409939">"odgovor"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"Postavljanje kao neaktivno"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"prekid veze"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Slušalica"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"Zvučnik"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+    <string name="start_stream" msgid="3567634786280097431">"pokretanje streaminga"</string>
+    <string name="crash_app" msgid="2548690390730057704">"izbacivanje iznimke"</string>
+    <string name="update_notification" msgid="8677916482672588779">"ažuriranje obavijesti u stil poziva u tijeku"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-hu/strings.xml b/testapps/transactionalVoipApp/res/values-hu/strings.xml
index fc903f8..205404e 100644
--- a/testapps/transactionalVoipApp/res/values-hu/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-hu/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"Tranzakciós API-teszttevékenység"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"Hívás közbeni tranzakciós tevékenység"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"Telefonáláshoz használt fiók regisztrálása"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"kimenő"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"bejövő"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"1. hívás hozzáadása"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"1. hívás megszakítása"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"2. hívás hozzáadása"</string>
-    <string name="set_call_active" msgid="248748409907478011">"2. hívás aktívra állítása"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"2. hívás megszakítása"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"Előtérben futó szolgáltatás indítása (gépi fordítás + alkalmazás szimulálása a háttérben)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"Kimenő hívás indítása"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"Bejövő hívás indítása"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"nincs beállítva hívásazonosító"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+    <string name="answer" msgid="5423590397665409939">"válasz"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"leválasztás"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Fülhallgató"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"Hangszóró"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+    <string name="start_stream" msgid="3567634786280097431">"streamelés indítása"</string>
+    <string name="crash_app" msgid="2548690390730057704">"kivétel dobása"</string>
+    <string name="update_notification" msgid="8677916482672588779">"értesítés frissítése a folyamatban lévő hívás stílusára"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-hy/strings.xml b/testapps/transactionalVoipApp/res/values-hy/strings.xml
index a781b20..85e6ae5 100644
--- a/testapps/transactionalVoipApp/res/values-hy/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-hy/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"Գործարքային API-ների փորձարկման գործողություն"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"Գործարքներ զանգի ժամանակ"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"Հեռախոսի հաշվի գրանցում"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"ելքային"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"մուտքային"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"ավելացնել զանգ 1-ը"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"ընդհատել զանգ 1-ը"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"ավելացնել զանգ 2-ը"</string>
-    <string name="set_call_active" msgid="248748409907478011">"զանգ 2-ը դարձնել ակտիվ"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"ընդհատել զանգ 2-ը"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"Գործարկել FGS-ը (ՄԹ-ի սիմուլացիա + հավելված ֆոնային ռեժիմում)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"Սկսել ելքային զանգ"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"Սկսել մուտքային զանգ"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"զանգի նույնացուցիչ սահմանված չէ"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"ակտիվացնել"</string>
+    <string name="answer" msgid="5423590397665409939">"պատասխանել"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"ապակտիվացնել"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"անջատել"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Լսափող"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"Բարձրախոս"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+    <string name="start_stream" msgid="3567634786280097431">"սկսել հեռարձակում"</string>
+    <string name="crash_app" msgid="2548690390730057704">"ուղարկել հաղորդագրություն բացառության մասին"</string>
+    <string name="update_notification" msgid="8677916482672588779">"ծանուցում ընթացիկ զանգի ոճի մասին"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-in/strings.xml b/testapps/transactionalVoipApp/res/values-in/strings.xml
index 0b1f12f..935f036 100644
--- a/testapps/transactionalVoipApp/res/values-in/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-in/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"Aktivitas pengujian API Transaksional"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"Aktivitas Transaksi Dalam Panggilan"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"Daftarkan Akun Ponsel"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"keluar"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"masuk"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"tambahkan panggilan 1"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"akhiri panggilan 1"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"tambahkan panggilan 2"</string>
-    <string name="set_call_active" msgid="248748409907478011">"setel panggilan 2 ke aktif"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"akhiri panggilan 2"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"Mulai FGS (simulasikan MT + aplikasi di latar belakang)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"Mulai Panggilan Keluar"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"Mulai Panggilan Masuk"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"id panggilan tidak ditetapkan"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"setelAktif"</string>
+    <string name="answer" msgid="5423590397665409939">"jawab"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"setelNonaktif"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"putuskan koneksi"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Earpiece"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"Speaker"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+    <string name="start_stream" msgid="3567634786280097431">"mulai streaming"</string>
+    <string name="crash_app" msgid="2548690390730057704">"tampilkan pengecualian"</string>
+    <string name="update_notification" msgid="8677916482672588779">"perbarui notifikasi ke gaya panggilan yang sedang berlangsung"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-is/strings.xml b/testapps/transactionalVoipApp/res/values-is/strings.xml
index c067c74..c0bcd23 100644
--- a/testapps/transactionalVoipApp/res/values-is/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-is/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"Prófun á virkni forritaskila færslna"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"Virkni í símtali"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"Skrá símareikning"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"hringt"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"móttekið"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"bæta við símtali 1"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"slíta símtali 1"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"bæta við símtali 2"</string>
-    <string name="set_call_active" msgid="248748409907478011">"virkja símtal 2"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"slíta símtali 2"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"Ræsa FGS (líkja eftir MT + forriti í bakgrunni)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"Hefja hringt símtal"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"Hefja símtal sem berst"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"númerabirting ekki stillt"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+    <string name="answer" msgid="5423590397665409939">"svara"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"aftengja"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Eyrnatól"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"Hátalari"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+    <string name="start_stream" msgid="3567634786280097431">"hefja streymi"</string>
+    <string name="crash_app" msgid="2548690390730057704">"nota undantekningu"</string>
+    <string name="update_notification" msgid="8677916482672588779">"uppfæra tilkynningu í stíl símtals sem stendur yfir"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-it/strings.xml b/testapps/transactionalVoipApp/res/values-it/strings.xml
index 35da8ae..36a2816 100644
--- a/testapps/transactionalVoipApp/res/values-it/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-it/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"Attività di test dell\'API transazionale"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"Attività di transazione durante la chiamata"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"Registra account telefono"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"in uscita"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"in arrivo"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"aggiungi chiamata 1"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"termina chiamata 1"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"aggiungi chiamata 2"</string>
-    <string name="set_call_active" msgid="248748409907478011">"imposta chiamata 2 attiva"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"termina chiamata 2"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"Avvia FGS (simulazione di MT + app in background)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"Avvia chiamata in uscita"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"Avvia chiamata in arrivo"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"id chiamata non impostato"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+    <string name="answer" msgid="5423590397665409939">"risposta"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"disconnetti"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Auricolare"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"Altoparlante"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+    <string name="start_stream" msgid="3567634786280097431">"avvia streaming"</string>
+    <string name="crash_app" msgid="2548690390730057704">"genera eccezione"</string>
+    <string name="update_notification" msgid="8677916482672588779">"aggiorna la notifica allo stile di chiamata in corso"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-iw/strings.xml b/testapps/transactionalVoipApp/res/values-iw/strings.xml
index 95557df..3accc06 100644
--- a/testapps/transactionalVoipApp/res/values-iw/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-iw/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"Transactional API test Activity"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"בר ביצוע בפעילות השיחה"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"רישום חשבון הטלפון"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"שיחה יוצאת"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"שיחה נכנסת"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"הוספת שיחה 1"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"ניתוק שיחה 1"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"הוספת שיחה 2"</string>
-    <string name="set_call_active" msgid="248748409907478011">"הגדרת שיחה 2 פעילה"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"ניתוק שיחה 2"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"‏הפעלת FGS (סימולציה של MT + אפליקציה ברקע)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"התחלת שיחה יוצאת"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"התחלת שיחה נכנסת"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"מזהה השיחה לא הוגדר"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"הגדרה כפעיל"</string>
+    <string name="answer" msgid="5423590397665409939">"תשובה"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"הגדרה כלא פעיל"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"ניתוק"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"אוזניה"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"רמקול"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+    <string name="start_stream" msgid="3567634786280097431">"התחלת השידור"</string>
+    <string name="crash_app" msgid="2548690390730057704">"חריגה להקפצה של הודעת שגיאה"</string>
+    <string name="update_notification" msgid="8677916482672588779">"עדכון ההתראה לסגנון של שיחה רציפה"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-ja/strings.xml b/testapps/transactionalVoipApp/res/values-ja/strings.xml
index 86d6189..faaede6 100644
--- a/testapps/transactionalVoipApp/res/values-ja/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-ja/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"Transactional API テスト アクティビティ"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"Transactional 通話アクティビティ"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"スマートフォン アカウントを登録"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"発信"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"着信"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"通話 1 を追加"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"通話 1 を切る"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"通話 2 を追加"</string>
-    <string name="set_call_active" msgid="248748409907478011">"通話 2 をアクティブに設定"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"通話 2 を切る"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"FGS を開始(MT + アプリをバックグラウンドでシミュレート)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"発信を開始"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"着信を開始"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"通話 ID が設定されていません"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+    <string name="answer" msgid="5423590397665409939">"応答"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"切断"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"受話口"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"スピーカー"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+    <string name="start_stream" msgid="3567634786280097431">"ストリーミングを開始"</string>
+    <string name="crash_app" msgid="2548690390730057704">"例外をスロー"</string>
+    <string name="update_notification" msgid="8677916482672588779">"通話中スタイルへの通知を更新"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-ka/strings.xml b/testapps/transactionalVoipApp/res/values-ka/strings.xml
index 1f3e69e..6d94f3e 100644
--- a/testapps/transactionalVoipApp/res/values-ka/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-ka/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"ტრანზაქციული API ტესტის აქტივობა"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"ტრანზაქციის ზარის აქტივობა"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"ტელეფონის ანგარიშის რეგისტრაცია"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"გამავალი"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"შემომავალი"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"ზარი 1-ის დამატება"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"ზარი 1-ის გათიშვა"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"ზარი 2-ის დამატება"</string>
-    <string name="set_call_active" msgid="248748409907478011">"ზარი 2-ის აქტივაცია"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"ზარის 2-ის გათიშვა"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"FGS-ის დაწყება (MT + აპის სიმულაცია ფონზე)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"დაიწყეთ გამავალი ზარი"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"დაიწყეთ შემომავალი ზარი"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"აბონენტის ID არ არის დაყენებული"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+    <string name="answer" msgid="5423590397665409939">"პასუხი"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"კავშირის გაწყვეტა"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"ყურმილი"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"დინამიკი"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+    <string name="start_stream" msgid="3567634786280097431">"სტრიმინგის დაწყება"</string>
+    <string name="crash_app" msgid="2548690390730057704">"ხარვეზის გადასროლა"</string>
+    <string name="update_notification" msgid="8677916482672588779">"განაახლეთ შეტყობინება მიმდინარე ზარის სტილში"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-kk/strings.xml b/testapps/transactionalVoipApp/res/values-kk/strings.xml
index a82c02a..03fd031 100644
--- a/testapps/transactionalVoipApp/res/values-kk/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-kk/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"Транзакциялық API сынағына қатысты әрекет"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"Қоңыраулар тарихындағы транзакциялық қолданба"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"Телефон аккаунтын тіркеу"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"шығыс"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"кіріс"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"1-қоңырауды қосу"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"1-қоңырауды ажырату"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"2-қоңырауды қосу"</string>
-    <string name="set_call_active" msgid="248748409907478011">"2-қоңырауды белсенді қылу"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"2-қоңырауды ажырату"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"FGS-ті бастау (MT мен қолданбаны фонда симуляциялау)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"Шығыс қоңырауын бастау"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"Кіріс қоңырауын бастау"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"қоңырау идентификаторы орнатылмады"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+    <string name="answer" msgid="5423590397665409939">"жауап беру"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"ажырату"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Телефон динамигі"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"Динамик"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+    <string name="start_stream" msgid="3567634786280097431">"трансляцияны бастау"</string>
+    <string name="crash_app" msgid="2548690390730057704">"ерекше жағдай туралы хабарлау"</string>
+    <string name="update_notification" msgid="8677916482672588779">"жүріп жатқан қоңырау стиліндегі хабарландыруды жаңату"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-km/strings.xml b/testapps/transactionalVoipApp/res/values-km/strings.xml
index 518fda6..b3e45e4 100644
--- a/testapps/transactionalVoipApp/res/values-km/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-km/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"សកម្មភាព​ធ្វើតេស្ត API ប្រតិបត្តិការ"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"សកម្មភាពប្រតិបត្តិការ​នៅក្នុងការហៅទូរសព្ទ"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"ចុះឈ្មោះ​គណនី​ទូរសព្ទ"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"ចេញ"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"ចូល"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"បញ្ចូល​ការហៅ​ទូរសព្ទទី 1"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"ផ្ដាច់​ការហៅទូរសព្ទទី 1"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"បញ្ចូល​ការហៅ​ទូរសព្ទទី 2"</string>
-    <string name="set_call_active" msgid="248748409907478011">"កំណត់​ការហៅ​ទូរសព្ទទី 2 ឱ្យសកម្ម"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"ផ្ដាច់​ការហៅទូរសព្ទទី 2"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"ចាប់ផ្ដើម FGS (ត្រាប់តាម MT + កម្មវិធី​នៅផ្ទៃខាងក្រោយ)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"ចាប់ផ្ដើម​ការហៅចេញ"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"ចាប់ផ្ដើម​ការហៅ​ចូល"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"មិនបានកំណត់​លេខសម្គាល់​ការហៅទូរសព្ទទេ"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+    <string name="answer" msgid="5423590397665409939">"ឆ្លើយ"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"ផ្ដាច់"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"ឧបករណ៍ស្ដាប់សំឡេង"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"ឧបករណ៍​បំពង​សំឡេង"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"ប៊្លូធូស"</string>
+    <string name="start_stream" msgid="3567634786280097431">"ចាប់ផ្ដើម​ការផ្សាយ"</string>
+    <string name="crash_app" msgid="2548690390730057704">"បោះ​ការលើកលែង"</string>
+    <string name="update_notification" msgid="8677916482672588779">"ធ្វើបច្ចុប្បន្នភាព​ការជូនដំណឹង​ចំពោះ​រចនាប័ទ្ម​នៃការហៅ​ទូរសព្ទ​ដែល​កំពុង​ដំណើរការ"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-kn/strings.xml b/testapps/transactionalVoipApp/res/values-kn/strings.xml
index 094bf01..dd3fdd9 100644
--- a/testapps/transactionalVoipApp/res/values-kn/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-kn/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"ಟ್ರಾನ್ಸಾಕ್ಷನಲ್ API ಪರೀಕ್ಷಾ ಚಟುವಟಿಕೆ"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"ಕರೆ ಚಟುವಟಿಕೆಯಲ್ಲಿ ಟ್ರಾನ್ಸಾಕ್ಷನಲ್"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"ಫೋನ್ ಖಾತೆಯನ್ನು ನೋಂದಾಯಿಸಿ"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"ಹೊರಹೋಗುವುದು"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"ಒಳಬರುವುದು"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"ಕರೆ 1 ಅನ್ನು ಸೇರಿಸಿ"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"ಕರೆ 1 ಅನ್ನು ಕಡಿತಗೊಳಿಸಿ"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"ಕರೆ 2 ಅನ್ನು ಸೇರಿಸಿ"</string>
-    <string name="set_call_active" msgid="248748409907478011">"ಕರೆ 2 ಅನ್ನು ಸಕ್ರಿಯ ಎಂದು ಸೆಟ್ ಮಾಡಿ"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"ಕರೆ 2 ಅನ್ನು ಕಡಿತಗೊಳಿಸಿ"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"FGS ಅನ್ನು ಪ್ರಾರಂಭಿಸಿ (MT + ಆ್ಯಪ್ ಅನ್ನು ಹಿನ್ನೆಲೆಯಲ್ಲಿ ಅನುಕರಿಸಿ)."</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"ಹೊರಹೋಗುವ ಕರೆಯನ್ನು ಪ್ರಾರಂಭಿಸಿ"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"ಒಳಬರುವ ಕರೆಯನ್ನು ಪ್ರಾರಂಭಿಸಿ"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"ಕರೆಮಾಡುವವರ ID ಅನ್ನು ಸೆಟ್ ಮಾಡಿಲ್ಲ"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+    <string name="answer" msgid="5423590397665409939">"ಉತ್ತರ"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"ಡಿಸ್‌ಕನೆಕ್ಟ್"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"ಇಯರ್‌ಪೀಸ್‌"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"ಸ್ಪೀಕರ್"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"ಬ್ಲೂಟೂತ್"</string>
+    <string name="start_stream" msgid="3567634786280097431">"ಸ್ಟ್ರೀಮ್ ಮಾಡುವುದನ್ನು ಪ್ರಾರಂಭಿಸಿ"</string>
+    <string name="crash_app" msgid="2548690390730057704">"ಥ್ರೋ ಎಕ್ಸೆಪ್ಶನ್"</string>
+    <string name="update_notification" msgid="8677916482672588779">"ಚಾಲ್ತಿಯಲ್ಲಿರುವ ಕರೆ ಶೈಲಿಗೆ ನೋಟಿಫಿಕೇಶನ್ ಅನ್ನು ಅಪ್‌ಡೇಟ್ ಮಾಡಿ"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-ko/strings.xml b/testapps/transactionalVoipApp/res/values-ko/strings.xml
index 10c9f62..762dc9c 100644
--- a/testapps/transactionalVoipApp/res/values-ko/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-ko/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"트랜잭션 API 테스트 활동"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"통화 중 거래 활동"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"전화 계정 등록"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"발신"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"수신"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"통화 1 추가"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"통화 1 끊기"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"통화 2 추가"</string>
-    <string name="set_call_active" msgid="248748409907478011">"통화 2 활성으로 설정"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"통화 2 끊기"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"FGS 시작(MT 및 백그라운드 앱 시뮬레이션)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"발신 전화 시작"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"수신 전화 시작"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"통화 ID가 설정되지 않음"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"활성으로 설정"</string>
+    <string name="answer" msgid="5423590397665409939">"답변"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"비활성으로 설정"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"연결 해제"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"스피커"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"스피커"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"블루투스"</string>
+    <string name="start_stream" msgid="3567634786280097431">"스트리밍 시작"</string>
+    <string name="crash_app" msgid="2548690390730057704">"예외 발생"</string>
+    <string name="update_notification" msgid="8677916482672588779">"진행 중인 통화 스타일로 알림 업데이트"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-ky/strings.xml b/testapps/transactionalVoipApp/res/values-ky/strings.xml
index 06bff74..47422a0 100644
--- a/testapps/transactionalVoipApp/res/values-ky/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-ky/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"Транзакциялык API сыноосунун активдүүлүгү"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"Чалуу учурундагы транзакциялар"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"Телефон аккаунтун каттоо"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"чыгуучу"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"келүүчү"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"1-чалууну кошуу"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"1-чалууну үзүү"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"2-чалууну кошуу"</string>
-    <string name="set_call_active" msgid="248748409907478011">"2-чалууну активдүү кылуу"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"2-чалууну үзүү"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"FGS\'ти иштетүү (фондо MT + колдонмону симуляциялоо)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"Чыгуучу чалууну баштоо"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"Кирүүчү чалууну баштоо"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"чалуунун идентификатору коюлган жок"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+    <string name="answer" msgid="5423590397665409939">"жооп берүү"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"ажыратуу"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Кулакчын"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"Динамик"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+    <string name="start_stream" msgid="3567634786280097431">"агымды баштоо"</string>
+    <string name="crash_app" msgid="2548690390730057704">"өзгөчө учурду түзүү"</string>
+    <string name="update_notification" msgid="8677916482672588779">"учурдагы чалуу үчүн жаңыртуу тууралуу билдирменин стили"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-lo/strings.xml b/testapps/transactionalVoipApp/res/values-lo/strings.xml
index f5f6dd5..1e1d247 100644
--- a/testapps/transactionalVoipApp/res/values-lo/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-lo/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"ກິດຈະກໍາການທົດສອບ API ທຸລະກໍາ"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"ການເຄື່ອນໄຫວຂອງທຸລະກຳລະຫວ່າງການໂທ"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"ລົງທະບຽນບັນຊີໂທລະສັບ"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"ສາຍໂທອອກ"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"ສາຍໂທເຂົ້າ"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"ເພີ່ມການໂທ 1"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"ຕັດເຊື່ອມຕໍ່ການໂທ 1"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"ເພີ່ມການໂທ 2"</string>
-    <string name="set_call_active" msgid="248748409907478011">"ຕັ້ງການໂທ 2 ເປັນນຳໃຊ້"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"ຕັດເຊື່ອມຕໍ່ການໂທ 2"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"ເລີ່ມ FGS (ຈຳລອງ MT + ແອັບໃນພື້ນຫຼັງ)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"ເລີ່ມສາຍໂທອອກ"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"ເລີ່ມສາຍໂທເຂົ້າ"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"ບໍ່ໄດ້ຕັ້ງໝາຍເລກຜູ້ໂທ"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"ຕັ້ງຄ່າເປັນນຳໃຊ້ຢູ່"</string>
+    <string name="answer" msgid="5423590397665409939">"ຄຳຕອບ"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"ຕັ້ງຄ່າເປັນບໍ່ໄດ້ນຳໃຊ້"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"ຕັດການເຊື່ອມຕໍ່"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"ຫູຟັງ"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"ລຳໂພງ"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+    <string name="start_stream" msgid="3567634786280097431">"ເລີ່ມການສະຕຣີມ"</string>
+    <string name="crash_app" msgid="2548690390730057704">"ຂໍ້ຍົກເວັ້ນໃນການໂຍນ"</string>
+    <string name="update_notification" msgid="8677916482672588779">"ອັບເດດການແຈ້ງເຕືອນເປັນຮູບແບບການໂທທີ່ກຳລັງດຳເນີນການຢູ່"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-lt/strings.xml b/testapps/transactionalVoipApp/res/values-lt/strings.xml
index 5e66c15..88cd414 100644
--- a/testapps/transactionalVoipApp/res/values-lt/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-lt/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"Operacijų API testavimo veikla"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"Operacijų skambutyje veikla"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"Užregistruoti telefono paskyrą"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"siunčiamieji"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"gaunamieji"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"pridėti 1 skambutį"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"atjungti 1 skambutį"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"pridėti 2 skambutį"</string>
-    <string name="set_call_active" msgid="248748409907478011">"nustatyti 2 skambutį kaip aktyvų"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"atjungti 2 skambutį"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"Pradėti FGS (modeliuoti MT ir programą fone)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"Pradėti siunčiamąjį skambutį"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"Pradėti gaunamąjį skambutį"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"skambučio ID nenustatytas"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+    <string name="answer" msgid="5423590397665409939">"atsakyti"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"atsijungti"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Garsiakalbis prie ausies"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"Garsiakalbis"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+    <string name="start_stream" msgid="3567634786280097431">"pradėti srautinį perdavimą"</string>
+    <string name="crash_app" msgid="2548690390730057704">"siųsti pranešimą apie išimtį"</string>
+    <string name="update_notification" msgid="8677916482672588779">"atnaujinti pranešimą į vykstančio skambučio stilių"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-lv/strings.xml b/testapps/transactionalVoipApp/res/values-lv/strings.xml
index 6678d6e..5e91ffe 100644
--- a/testapps/transactionalVoipApp/res/values-lv/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-lv/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"Transakciju API testa darbība"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"Ar darījumiem saistītas darbības zvana laikā"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"Reģistrēt tālruņa kontu"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"izejošs"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"ienākošs"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"pievienot 1. zvanu"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"pārtraukt 1. zvanu"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"pievienot 2. zvanu"</string>
-    <string name="set_call_active" msgid="248748409907478011">"iestatīt 2. zvanu kā aktīvu"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"pārtraukt 2. zvanu"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"Sākt FGS (simulēt mašīntulkojumu un lietotni fonā)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"Sākt izejoša zvana simulāciju"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"Sākt ienākoša zvana simulāciju"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"zvana ID nav iestatīts"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+    <string name="answer" msgid="5423590397665409939">"atbildēt"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"pārtraukt"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Auss skaļrunis"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"Skaļrunis"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+    <string name="start_stream" msgid="3567634786280097431">"sākt straumēšanu"</string>
+    <string name="crash_app" msgid="2548690390730057704">"sūtīt ziņojumu par izņēmumu"</string>
+    <string name="update_notification" msgid="8677916482672588779">"atjaunināt paziņojumu atbilstoši pašreizējā zvana stilam"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-mk/strings.xml b/testapps/transactionalVoipApp/res/values-mk/strings.xml
index 01cd42e..d86879d 100644
--- a/testapps/transactionalVoipApp/res/values-mk/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-mk/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"Активност на тестирање на API за трансакции"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"Трансакциска активност во повикот"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"Регистрирај телефонска сметка"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"појдовен"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"дојдовен"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"додај повик 1"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"прекини го повикот 1"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"додај повик 2"</string>
-    <string name="set_call_active" msgid="248748409907478011">"постави го повик 2 како активен"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"прекини го повикот 2"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"Започни FGS (симулирај MT + апликација во заднина)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"Започни појдовен повик"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"Започни дојдовен повик"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"не е поставен ID на повикувач"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+    <string name="answer" msgid="5423590397665409939">"одговори"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"прекини врска"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Слушалка"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"Звучник"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+    <string name="start_stream" msgid="3567634786280097431">"започни стриминг"</string>
+    <string name="crash_app" msgid="2548690390730057704">"отфрли исклучок"</string>
+    <string name="update_notification" msgid="8677916482672588779">"известување за ажурирање на стилот на тековниот повик"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-ml/strings.xml b/testapps/transactionalVoipApp/res/values-ml/strings.xml
index 66ec4ef..6c70b22 100644
--- a/testapps/transactionalVoipApp/res/values-ml/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-ml/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"ട്രാൻസാക്ഷണൽ API ടെസ്റ്റ് ആക്റ്റിവിറ്റി"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"ട്രാൻസാക്ഷണൽ ഇൻ കോൾ ആക്റ്റിവിറ്റി"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"ഫോൺ അക്കൗണ്ട് രജിസ്റ്റർ ചെയ്യുക"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"ഔട്ട്‌ഗോയിംഗ്"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"ഇൻകമിംഗ്"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"കോൾ 1 ചേർക്കുക"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"കോൾ 1 വിച്ഛേദിക്കുക"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"കോൾ 2 ചേർക്കുക"</string>
-    <string name="set_call_active" msgid="248748409907478011">"കോൾ 2 സജീവമായി സജ്ജീകരിക്കുക"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"കോൾ 2 വിച്ഛേദിക്കുക"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"FGS ആരംഭിക്കുക (പശ്ചാത്തലത്തിൽ മെഷീൻ ട്രാൻസ്‌ലേഷൻ + ആപ്പ് സിമുലേറ്റ് ചെയ്യുക)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"ഔട്ട്‌ഗോയിംഗ് കോൾ ആരംഭിക്കുക"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"ഇൻകമിംഗ് കോൾ ആരംഭിക്കുക"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"കോൾ ഐഡി സജ്ജീകരിച്ചിട്ടില്ല"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"സജീവമെന്ന് സജ്ജീകരിക്കുക"</string>
+    <string name="answer" msgid="5423590397665409939">"ഉത്തരം നൽകുക"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"സജീവമല്ലെന്ന് സജ്ജീകരിക്കുക"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"വിച്ഛേദിക്കുക"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"ഇയർഫോൺ"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"സ്പീക്കർ"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+    <string name="start_stream" msgid="3567634786280097431">"സ്‌ട്രീമിംഗ് ആരംഭിക്കുക"</string>
+    <string name="crash_app" msgid="2548690390730057704">"ഒഴിവാക്കൽ ത്രോ ചെയ്യുക"</string>
+    <string name="update_notification" msgid="8677916482672588779">"സജീവമായ കോൾ ശൈലിയിലേക്ക് അറിയിപ്പ് അപ്ഡേറ്റ് ചെയ്യുക"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-mn/strings.xml b/testapps/transactionalVoipApp/res/values-mn/strings.xml
index f056b2b..fecb956 100644
--- a/testapps/transactionalVoipApp/res/values-mn/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-mn/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"Гүйлгээний API-н туршилтын үйл ажиллагаа"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"Дуудлагын үйл ажиллагааны гүйлгээ"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"Утасны бүртгэл бүртгүүлэх"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"залгаж буй"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"ирж буй"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"1-р дуудлага нэмэх"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"1-р дуудлагыг салгах"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"2-р дуудлага нэмэх"</string>
-    <string name="set_call_active" msgid="248748409907478011">"2-р дуудлагыг идэвхтэй болгож тохируулах"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"2-р дуудлагыг салгах"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"FGS-г эхлүүлэх (дэвсгэрт MT + аппыг загварчлах)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"Залгасан дуудлагыг эхлүүлэх"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"Ирсэн дуудлагыг эхлүүлэх"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"дуудлагын ID-г тохируулаагүй"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+    <string name="answer" msgid="5423590397665409939">"хариулах"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"салгах"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Чихний спикер"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"Чанга яригч"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+    <string name="start_stream" msgid="3567634786280097431">"дамжуулалтыг эхлүүлэх"</string>
+    <string name="crash_app" msgid="2548690390730057704">"шидэх гажиг"</string>
+    <string name="update_notification" msgid="8677916482672588779">"үргэлжилж буй дуудлагын загварын шинэчлэлтийн мэдэгдэл"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-mr/strings.xml b/testapps/transactionalVoipApp/res/values-mr/strings.xml
index 4ca3b8e..97bf665 100644
--- a/testapps/transactionalVoipApp/res/values-mr/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-mr/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"व्यावहारिक API चाचणी अ‍ॅक्टिव्हिटी"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"कॉल अ‍ॅक्टिव्हिटी यामधील व्यवहार"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"फोन खात्याची नोंदणी करा"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"आउटगोइंग"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"इनकमिंग"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"कॉल १ जोडा"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"कॉल १ डिस्कनेक्ट करा"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"कॉल २ जोडा"</string>
-    <string name="set_call_active" msgid="248748409907478011">"कॉल २ अ‍ॅक्टिव्ह यावर सेट करा"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"कॉल २ डिस्कनेक्ट करा"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"FGS सुरू करा (बॅकग्राउंडमध्ये MT + अ‍ॅप सिम्युलेट करा)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"आउटगोइंग कॉल सुरू करा"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"इनकमिंग कॉल सुरू करा"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"कॉल आयडी सेट केलेला नाही"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+    <string name="answer" msgid="5423590397665409939">"उत्तर"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"‍डिस्कनेक्ट करा"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"इअरपिस"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"स्पीकर"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"ब्लूटूथ"</string>
+    <string name="start_stream" msgid="3567634786280097431">"स्ट्रीम करणे सुरू करा"</string>
+    <string name="crash_app" msgid="2548690390730057704">"एक्सेप्शन जोडा"</string>
+    <string name="update_notification" msgid="8677916482672588779">"सुरू असलेल्या कॉल शैलीवर सूचना अपडेट करा"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-ms/strings.xml b/testapps/transactionalVoipApp/res/values-ms/strings.xml
index d888672..abcb702 100644
--- a/testapps/transactionalVoipApp/res/values-ms/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-ms/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"Aktiviti ujian API transaksi"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"Transaksi Aktiviti Dalam Panggilan"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"Daftar Akaun Telefon"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"keluar"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"masuk"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"tambahkan panggilan 1"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"putuskan panggilan 1"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"tambahkan panggilan 2"</string>
-    <string name="set_call_active" msgid="248748409907478011">"tetapkan panggilan 2 sebagai aktif"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"putuskan panggilan 2"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"Mulakan FGS (simulasi MT + apl pada latar)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"Mulakan Panggilan Keluar"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"Mulakan Panggilan Masuk"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"ID panggilan tidak ditetapkan"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+    <string name="answer" msgid="5423590397665409939">"jawab"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"putuskan sambungan"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Alat dengar"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"Pembesar suara"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+    <string name="start_stream" msgid="3567634786280097431">"mulakan penstriman"</string>
+    <string name="crash_app" msgid="2548690390730057704">"buat pengecualian"</string>
+    <string name="update_notification" msgid="8677916482672588779">"kemas kinikan pemberitahuan kepada gaya panggilan keluar"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-my/strings.xml b/testapps/transactionalVoipApp/res/values-my/strings.xml
index 7422922..b8ee395 100644
--- a/testapps/transactionalVoipApp/res/values-my/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-my/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"အသိအမှတ်ပြုမှုဆိုင်ရာ API စမ်းသပ်လုပ်ဆောင်ချက်"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"ခေါ်ဆိုမှုလုပ်ဆောင်ချက်ရှိ မှတ်တမ်းဆိုင်ရာ"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"ဖုန်းအကောင့် မှတ်ပုံတင်ရန်"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"အထွက်"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"အဝင်"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"ခေါ်ဆိုမှု ၁ ထည့်ရန်"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"ခေါ်ဆိုမှု ၁ ဖြတ်တောက်ရန်"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"ခေါ်ဆိုမှု ၂ ထည့်ရန်"</string>
-    <string name="set_call_active" msgid="248748409907478011">"ခေါ်ဆိုမှု ၂ ကို လက်ရှိပြောနေကြောင်း သတ်မှတ်ရန်"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"ခေါ်ဆိုမှု ၂ ဖြတ်တောက်ရန်"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"FGS (အသွင်တူ MT + နောက်ခံရှိ အက်ပ်) စတင်ရန်"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"အထွက် ခေါ်ဆိုမှု စတင်ရန်"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"အဝင်ခေါ်ဆိုမှု စတင်ရန်"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"ခေါ်ဆိုမှု id သတ်မှတ်မထားပါ"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"ပြောနေသည်ဟု သတ်မှတ်ရန်"</string>
+    <string name="answer" msgid="5423590397665409939">"ဖြေကြားရန်"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"ပြောမနေပါဟု သတ်မှတ်ရန်"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"ချိတ်ဆက်မှုဖြုတ်ရန်"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"တယ်လီဖုန်းနားခွက်"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"စပီကာ"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"ဘလူးတုသ်"</string>
+    <string name="start_stream" msgid="3567634786280097431">"တိုက်ရိုက်လွှင့်ခြင်း စတင်ရန်"</string>
+    <string name="crash_app" msgid="2548690390730057704">"throw exception"</string>
+    <string name="update_notification" msgid="8677916482672588779">"လက်ရှိခေါ်ဆိုမှုပုံစံအတွက် အပ်ဒိတ်အကြောင်းကြားချက်"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-nb/strings.xml b/testapps/transactionalVoipApp/res/values-nb/strings.xml
index 124e0a0..22bb06f 100644
--- a/testapps/transactionalVoipApp/res/values-nb/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-nb/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"Testaktivitet for Transactional API"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"Transaksjonell i samtale-aktivitet"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"Registrer telefonkonto"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"utgående"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"innkommende"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"legg til anrop 1"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"avslutt anrop 1"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"legg til anrop 2"</string>
-    <string name="set_call_active" msgid="248748409907478011">"angi anrop 2 som aktivt"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"avslutt anrop 2"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"Start FGS (simuler MT + app i bakgrunnen)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"Start utgående anrop"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"Start innkommende anrop"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"anrops-ID er ikke angitt"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+    <string name="answer" msgid="5423590397665409939">"svar"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"koble fra"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Ørehøyttaler"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"Høyttaler"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+    <string name="start_stream" msgid="3567634786280097431">"start strømming"</string>
+    <string name="crash_app" msgid="2548690390730057704">"unntak – avbryt med en feil"</string>
+    <string name="update_notification" msgid="8677916482672588779">"oppdater varslingsstil til «Pågående anrop»"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-ne/strings.xml b/testapps/transactionalVoipApp/res/values-ne/strings.xml
index 84481ec..e9bc805 100644
--- a/testapps/transactionalVoipApp/res/values-ne/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-ne/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"Transactional API को परीक्षणसम्बन्धी गतिविधि"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"कलमा क्लाइन्ट र सर्भरबिच गरिएको कुराकानीसम्बन्धी क्रियाकलाप"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"फोन खाता दर्ता गर्नुहोस्"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"बहिर्गमन"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"आगमन"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"कल १ कनेक्ट गर्नुहोस्"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"कल १ डिस्कनेक्ट गर्नुहोस्"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"कल २ कनेक्ट गर्नुहोस्"</string>
-    <string name="set_call_active" msgid="248748409907478011">"कल २ लाई सक्रिय कलका रूपमा सेट गर्नुहोस्"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"कल २ डिस्कनेक्ट गर्नुहोस्"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"FGS सुरु गर्नुहोस् (ब्याकग्राउन्डमा MT + एप सिमुलेट गर्नुहोस्)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"बहिर्गमन कल सुरु गर्नुहोस्"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"आगमन कल सुरु गर्नुहोस्"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"कल ID सेट गरिएको छैन"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+    <string name="answer" msgid="5423590397665409939">"कल उठाउनुहोस्"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"डिस्कनेक्ट गर्नुहोस्"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"इयरपिस"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"स्पिकर"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"ब्लुटुथ"</string>
+    <string name="start_stream" msgid="3567634786280097431">"स्ट्रिम गर्न थाल्नुहोस्"</string>
+    <string name="crash_app" msgid="2548690390730057704">"अपवाद देखाउने काम"</string>
+    <string name="update_notification" msgid="8677916482672588779">"कल गरिरहेका बेला सूचना जुन शैलीमा देखिन्छ सोही शैली प्रयोग गर्नुहोस्"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-nl/strings.xml b/testapps/transactionalVoipApp/res/values-nl/strings.xml
index 949d71e..1ba3f9c 100644
--- a/testapps/transactionalVoipApp/res/values-nl/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-nl/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"Testactiviteit Transactional API"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"Beveiligd gesprek"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"Telefoonaccount registreren"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"uitgaand"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"binnenkomend"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"gesprek 1 toevoegen"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"gesprek 1 beëindigen"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"gesprek 2 toevoegen"</string>
-    <string name="set_call_active" msgid="248748409907478011">"gesprek 2 instellen als actief"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"gesprek 2 beëindigen"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"Service op de voorgrond (FGS) starten (MT + app op de achtergrond simuleren)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"Uitgaand gesprek starten"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"Inkomend gesprek starten"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"Beller-ID niet ingesteld"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+    <string name="answer" msgid="5423590397665409939">"antwoord"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"loskoppelen"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Oortelefoon"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"Speaker"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+    <string name="start_stream" msgid="3567634786280097431">"streamen starten"</string>
+    <string name="crash_app" msgid="2548690390730057704">"uitzondering activeren"</string>
+    <string name="update_notification" msgid="8677916482672588779">"updatemelding naar actieve gespreksstijl"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-or/strings.xml b/testapps/transactionalVoipApp/res/values-or/strings.xml
index 7cda1c8..f3391ea 100644
--- a/testapps/transactionalVoipApp/res/values-or/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-or/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"ଟ୍ରାଞ୍ଜେକସନାଲ API ପରୀକ୍ଷଣର କାର୍ଯ୍ୟକଳାପ"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"ଟ୍ରାଞ୍ଜେକସନାଲ ଇନ କଲ କାର୍ଯ୍ୟକଳାପ"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"ଫୋନ ଆକାଉଣ୍ଟର ପଞ୍ଜିକରଣ କରନ୍ତୁ"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"ଆଉଟଗୋଇଂ"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"ଇନକମିଂ"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"କଲ 1 ଯୋଗ କରନ୍ତୁ"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"କଲ 1 ଡିସକନେକ୍ଟ କରନ୍ତୁ"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"କଲ 2 ଯୋଗ କରନ୍ତୁ"</string>
-    <string name="set_call_active" msgid="248748409907478011">"କଲ 2କୁ ସକ୍ରିୟ ଭାବେ ସେଟ କରନ୍ତୁ"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"କଲ 2 ଡିସକନେକ୍ଟ କରନ୍ତୁ"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"FGS ଆରମ୍ଭ କରନ୍ତୁ (ପୃଷ୍ଠପଟରେ MT + ଆପକୁ ସିମୁଲେଟ କରନ୍ତୁ)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"ଆଉଟଗୋଇଂ କଲ ଆରମ୍ଭ କରନ୍ତୁ"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"ଇନକମିଂ କଲ ଆରମ୍ଭ କରନ୍ତୁ"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"କଲ ID ସେଟ କରାଯାଇନାହିଁ"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+    <string name="answer" msgid="5423590397665409939">"ଉତ୍ତର ଦିଅନ୍ତୁ"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"ଡିସକନେକ୍ଟ କରନ୍ତୁ"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"ଇୟରପିସ"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"ସ୍ପିକର"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"ବ୍ଲୁଟୁଥ"</string>
+    <string name="start_stream" msgid="3567634786280097431">"ଷ୍ଟ୍ରିମିଂ ଆରମ୍ଭ କରନ୍ତୁ"</string>
+    <string name="crash_app" msgid="2548690390730057704">"ଥ୍ରୋ ଏକ୍ସସେପସନ"</string>
+    <string name="update_notification" msgid="8677916482672588779">"ଚାଲିଥିବା କଲ ଷ୍ଟାଇଲ ପାଇଁ ବିଜ୍ଞପ୍ତିକୁ ଅପଡେଟ କରନ୍ତୁ"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-pa/strings.xml b/testapps/transactionalVoipApp/res/values-pa/strings.xml
index c01d6a3..76e367d 100644
--- a/testapps/transactionalVoipApp/res/values-pa/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-pa/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"ਲੈਣ-ਦੇਣ API ਜਾਂਚ ਸਰਗਰਮੀ"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"ਲੈਣ-ਦੇਣ ਸੰਬੰਧੀ ਇਨ-ਕਾਲ ਸਰਗਰਮੀ"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"ਫ਼ੋਨ ਖਾਤਾ ਰਜਿਸਟਰ ਕਰੋ"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"ਆਊਟਗੋਇੰਗ"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"ਇਨਕਮਿੰਗ"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"ਕਾਲ 1 ਸ਼ਾਮਲ ਕਰੋ"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"ਕਾਲ 1 ਕੱਟੋ"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"ਕਾਲ 2 ਸ਼ਾਮਲ ਕਰੋ"</string>
-    <string name="set_call_active" msgid="248748409907478011">"ਕਾਲ 2 ਨੂੰ ਕਿਰਿਆਸ਼ੀਲ ਵਜੋਂ ਸੈੱਟ ਕਰੋ"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"ਕਾਲ 2 ਕੱਟੋ"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"FGS ਸ਼ੁਰੂ ਕਰੋ (ਬੈਕਗ੍ਰਾਊਂਡ ਵਿੱਚ MT + ਐਪ ਨੂੰ ਸਿਮੂਲੇਟ ਕਰੋ)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"ਆਊਟਗੋਇੰਗ ਕਾਲ ਸ਼ੁਰੂ ਕਰੋ"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"ਇਨਕਮਿੰਗ ਕਾਲ ਸ਼ੁਰੂ ਕਰੋ"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"ਕਾਲਰ ਆਈਡੀ ਸੈੱਟ ਨਹੀਂ ਹੈ"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+    <string name="answer" msgid="5423590397665409939">"ਜਵਾਬ"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"ਡਿਸਕਨੈਕਟ ਕਰੋ"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"ਈਯਰਪੀਸ"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"ਸਪੀਕਰ"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"ਬਲੂਟੁੱਥ"</string>
+    <string name="start_stream" msgid="3567634786280097431">"ਸਟ੍ਰੀਮਿੰਗ ਸ਼ੁਰੂ ਕਰੋ"</string>
+    <string name="crash_app" msgid="2548690390730057704">"ਅਪਵਾਦ ਸ਼ਾਮਲ ਕਰੋ"</string>
+    <string name="update_notification" msgid="8677916482672588779">"ਜਾਰੀ ਕਾਲ ਸਟਾਈਲ \'ਤੇ ਸੂਚਨਾ ਅੱਪਡੇਟ ਕਰੋ"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-pl/strings.xml b/testapps/transactionalVoipApp/res/values-pl/strings.xml
index 60f370b..c6115b8 100644
--- a/testapps/transactionalVoipApp/res/values-pl/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-pl/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"Czynność testowa dotycząca transakcji związanej z interfejsem API"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"Aktywność transakcyjna w trakcie rozmowy"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"Zarejestruj konto telefonu"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"wychodzące"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"przychodzące"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"dodaj połączenie 1"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"rozłącz połączenie 1"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"dodaj połączenie 2"</string>
-    <string name="set_call_active" msgid="248748409907478011">"ustaw połączenie 2 jako aktywne"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"rozłącz połączenie 2"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"Uruchom FGS (symulacja MT + aplikacja w tle)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"Rozpocznij połączenie wychodzące"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"Rozpocznij połączenie przychodzące"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"nie ustawiono ID rozmówcy"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"ustawAktywny"</string>
+    <string name="answer" msgid="5423590397665409939">"odpowiedź"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"ustawNieaktywny"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"rozłącz"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Słuchawka"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"Głośnik"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+    <string name="start_stream" msgid="3567634786280097431">"rozpocznij transmisję"</string>
+    <string name="crash_app" msgid="2548690390730057704">"wyjątek dotyczący zgłoszenia"</string>
+    <string name="update_notification" msgid="8677916482672588779">"zaktualizuj powiadomienie do stylu trwającej rozmowy"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-pt-rPT/strings.xml b/testapps/transactionalVoipApp/res/values-pt-rPT/strings.xml
index ebeb482..a5b3ea0 100644
--- a/testapps/transactionalVoipApp/res/values-pt-rPT/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-pt-rPT/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"Atividade de teste da API transacional"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"Transacional na atividade da chamada"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"Registar conta do telemóvel"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"feita"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"recebida"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"adicionar chamada 1"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"desligar chamada 1"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"adicionar chamada 2"</string>
-    <string name="set_call_active" msgid="248748409907478011">"definir chamada 2 como ativa"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"desligar chamada 2"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"Iniciar FGS (simular TA + app em segundo plano)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"Iniciar chamada feita"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"Iniciar chamada recebida"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"ID da chamada não definido"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+    <string name="answer" msgid="5423590397665409939">"atender"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"desligar"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Auricular"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"Altifalante"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+    <string name="start_stream" msgid="3567634786280097431">"Iniciar stream"</string>
+    <string name="crash_app" msgid="2548690390730057704">"acionar exceção"</string>
+    <string name="update_notification" msgid="8677916482672588779">"atualizar estilo de notificação para chamada em curso"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-pt/strings.xml b/testapps/transactionalVoipApp/res/values-pt/strings.xml
index ea46628..a09c64d 100644
--- a/testapps/transactionalVoipApp/res/values-pt/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-pt/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"Atividade de teste da API transacional"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"Atividade em chamadas transacionais"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"Registrar conta telefônica"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"realizada"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"recebida"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"adicionar ligação 1"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"encerrar ligação 1"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"adicionar ligação 2"</string>
-    <string name="set_call_active" msgid="248748409907478011">"definir a ligação 2 como ativa"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"encerrar ligação 2"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"Iniciar FGS (simular MT + app em segundo plano)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"Iniciar ligação efetuada"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"Iniciar ligação recebida"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"identificador de chamadas não definido"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+    <string name="answer" msgid="5423590397665409939">"resposta"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"desconectar"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Minifone de ouvido"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"Alto-falante"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+    <string name="start_stream" msgid="3567634786280097431">"Iniciar transmissão"</string>
+    <string name="crash_app" msgid="2548690390730057704">"gerar exceção"</string>
+    <string name="update_notification" msgid="8677916482672588779">"notificação de atualização para o estilo \"Chamada em andamento\""</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-ro/strings.xml b/testapps/transactionalVoipApp/res/values-ro/strings.xml
index ee6bfa2..261a5ad 100644
--- a/testapps/transactionalVoipApp/res/values-ro/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-ro/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"Activitate de testare a API-ului tranzacțional"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"Activitate tranzacțională în timpul apelului"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"Înregistrează contul de telefon"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"efectuat"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"primit"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"adaugă apelul 1"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"deconectează apelul 1"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"adaugă apelul 2"</string>
-    <string name="set_call_active" msgid="248748409907478011">"setează apelul 2 ca activ"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"deconectează apelul 2"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"Pornește FGS (simulează MT + aplicația în fundal)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"Inițiază un apel efectuat"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"Inițiază un apel primit"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"ID-ul apelului nu este setat"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+    <string name="answer" msgid="5423590397665409939">"răspuns"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"deconectează"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Cască"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"Difuzor"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+    <string name="start_stream" msgid="3567634786280097431">"începe streamingul"</string>
+    <string name="crash_app" msgid="2548690390730057704">"trimite excepție"</string>
+    <string name="update_notification" msgid="8677916482672588779">"actualizează notificarea la stilul de apel în desfășurare"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-ru/strings.xml b/testapps/transactionalVoipApp/res/values-ru/strings.xml
index 5345e7b..c05e7ea 100644
--- a/testapps/transactionalVoipApp/res/values-ru/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-ru/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"Активность тестирования API транзакций"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"Транзакции во время вызовов"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"Зарегистрировать аккаунт телефона"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"исходящий"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"входящий"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"добавить звонок 1"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"отключить звонок 1"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"добавить звонок 2"</string>
-    <string name="set_call_active" msgid="248748409907478011">"сделать звонок 2 активным"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"отключить звонок 2"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"Запустить активную службу (симуляция МП + приложение в фоновом режиме)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"Начать исходящий вызов"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"Начать входящий вызов"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"идентификатор вызова не задан"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"Активировать"</string>
+    <string name="answer" msgid="5423590397665409939">"ответить"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"Деактивировать"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"разъединить"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Динамик телефона"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"Колонка"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+    <string name="start_stream" msgid="3567634786280097431">"Начать трансляцию"</string>
+    <string name="crash_app" msgid="2548690390730057704">"отправить сообщение об исключении"</string>
+    <string name="update_notification" msgid="8677916482672588779">"стиль уведомления об обновлении для текущего звонка"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-si/strings.xml b/testapps/transactionalVoipApp/res/values-si/strings.xml
index 87d421c..d8b8a6f 100644
--- a/testapps/transactionalVoipApp/res/values-si/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-si/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"ගනුදෙනු API පරීක්ෂණ ක්‍රියාකාරකම්"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"ඇමතුම් ක්‍රියාකාරකම්වල ගනුදෙනු"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"දුරකථන ගිණුම ලියාපදිංචි කරන්න"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"පිටතට යන"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"ඇතුළට එන"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"ඇමතුම 1 එක් කරන්න"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"ඇමතුම 1 විසන්ධි කරන්න"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"ඇමතුම 2 එක් කරන්න"</string>
-    <string name="set_call_active" msgid="248748409907478011">"ඇමතුම 2 සක්‍රිය ලෙස සකසන්න"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"ඇමතුම 2 විසන්ධි කරන්න"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"FGS අරඹන්න (පසුබිමේ MT + යෙදුම අනුකරණය කරන්න)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"පිටතට යන ඇමතුම අරඹන්න"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"එන ඇමතුම අරඹන්න"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"අමතුම්කරුගේ id සකසා නැත"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+    <string name="answer" msgid="5423590397665409939">"පිළිතුරු දෙන්න"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"විසන්ධි කරන්න"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"සවන් කඩ"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"ස්පීකරය"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"බ්ලූටූත්"</string>
+    <string name="start_stream" msgid="3567634786280097431">"ප්‍රවාහය අරඹන්න"</string>
+    <string name="crash_app" msgid="2548690390730057704">"ව්‍යතිරේකය දමන්න"</string>
+    <string name="update_notification" msgid="8677916482672588779">"පවතින ඇමතුම් විලාසයට යාවත්කාලීනයේ දැනුම්දීම"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-sk/strings.xml b/testapps/transactionalVoipApp/res/values-sk/strings.xml
index 1d08d78..3847882 100644
--- a/testapps/transactionalVoipApp/res/values-sk/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-sk/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"Testovacia aktivita transakčného rozhrania API"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"Transakčná aktivita počas hovoru"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"Registrovať telefónny účet"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"odchádzajúci"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"prichádzajúci"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"pridať hovor 1"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"odpojiť hovor 1"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"pridať hovor 2"</string>
-    <string name="set_call_active" msgid="248748409907478011">"nastaviť hovor 2 ako aktívny"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"odpojiť hovor 2"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"Spustiť FGS (simulácia MT a aplikácie na pozadí)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"Začať odchádzajúci hovor"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"Začať prichádzajúci hovor"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"identifikátor hovoru nie je nastavený"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+    <string name="answer" msgid="5423590397665409939">"prijať"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"odpojiť"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Slúchadlo"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"Reproduktor"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+    <string name="start_stream" msgid="3567634786280097431">"spustiť streamovanie"</string>
+    <string name="crash_app" msgid="2548690390730057704">"vyvolať výnimku"</string>
+    <string name="update_notification" msgid="8677916482672588779">"aktualizovať upozornenie na štýl prebiehajúceho hovoru"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-sl/strings.xml b/testapps/transactionalVoipApp/res/values-sl/strings.xml
index c82c86d..dec3622 100644
--- a/testapps/transactionalVoipApp/res/values-sl/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-sl/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"Preizkusna dejavnost transakcijskega API-ja"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"Transakcijska dejavnost v klicu"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"Registracija telefonskega računa"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"odhodni"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"dohodni"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"dodaj klic 1"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"prekinitev klica 1"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"dodaj klic 2"</string>
-    <string name="set_call_active" msgid="248748409907478011">"nastavi klic 2 kot aktiven"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"prekinitev klica 2"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"Zaženi FGS (simuliraj strojni prevod + aplikacijo v ozadju)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"Začni odhodni klic"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"Začni dohodni klic"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"id klica ni nastavljen"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"Nastavi kot aktivno"</string>
+    <string name="answer" msgid="5423590397665409939">"sprejmi"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"Nastavi kot neaktivno"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"prekini klic"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Slušalka"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"Zvočnik"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+    <string name="start_stream" msgid="3567634786280097431">"začni pretočno predvajanje"</string>
+    <string name="crash_app" msgid="2548690390730057704">"sprožitev izjeme"</string>
+    <string name="update_notification" msgid="8677916482672588779">"posodobi obvestilo na slog trenutnega klica"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-sq/strings.xml b/testapps/transactionalVoipApp/res/values-sq/strings.xml
index 8a44cdd..ddaba66 100644
--- a/testapps/transactionalVoipApp/res/values-sq/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-sq/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"Aktiviteti i testimit të API-së së transaksioneve"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"Aktivitet transaksioni brenda telefonatës"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"Regjistro llogarinë e telefonit"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"dalëse"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"hyrëse"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"shto telefonatën 1"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"shkëput telefonatën 1"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"shto telefonatën 2"</string>
-    <string name="set_call_active" msgid="248748409907478011">"caktoje telefonatën 2 si aktive"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"shkëput telefonatën 2"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"Nis shërbimin FGS (simulo përkthimin kompjuterik dhe aplikacionin në sfond)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"Nis një telefonatë dalëse"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"Nis një telefonatë hyrëse"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"ID-ja e telefonatës nuk është caktuar"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+    <string name="answer" msgid="5423590397665409939">"përgjigju"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"shkëput"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Receptori"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"Altoparlanti"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+    <string name="start_stream" msgid="3567634786280097431">"nis transmetimin"</string>
+    <string name="crash_app" msgid="2548690390730057704">"gjenero një përjashtim"</string>
+    <string name="update_notification" msgid="8677916482672588779">"përditëso njoftimin me stilin e telefonatës në vazhdim"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-sr/strings.xml b/testapps/transactionalVoipApp/res/values-sr/strings.xml
index 8e66da7..cd413f4 100644
--- a/testapps/transactionalVoipApp/res/values-sr/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-sr/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"Активност тестирања трансакционог API-ја"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"Активност позива у вези са трансакцијама"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"Региструј налог телефона"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"одлазни"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"долазни"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"додај 1. позив"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"прекини 1. позив"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"додај 2. позив"</string>
-    <string name="set_call_active" msgid="248748409907478011">"подеси 2. позив као активан"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"прекини 2. позив"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"Покрени FGS (симулирајте MT + апликацију у позадини)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"Започните одлазни позив"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"Започните долазни позив"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"ИД позива није подешен"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+    <string name="answer" msgid="5423590397665409939">"одговори"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"прекини везу"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Слушалица"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"Звучник"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+    <string name="start_stream" msgid="3567634786280097431">"почните да стримујете"</string>
+    <string name="crash_app" msgid="2548690390730057704">"избацити изузетак"</string>
+    <string name="update_notification" msgid="8677916482672588779">"ажурирајте обавештење на стил актуелног позива"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-sv/strings.xml b/testapps/transactionalVoipApp/res/values-sv/strings.xml
index 586d1a4..f74b775 100644
--- a/testapps/transactionalVoipApp/res/values-sv/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-sv/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"Aktiviteten Test av transaktions-API"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"Transaktioner i samtalsaktivitet"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"Registrera telefonkonto"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"utgående"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"inkommande"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"lägg till samtal 1"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"lägg på samtal 1"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"lägg till samtal 2"</string>
-    <string name="set_call_active" msgid="248748409907478011">"ställ in samtal 2 som aktivt"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"lägg på samtal 2"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"Starta FGS (simulera MT + app i bakgrunden)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"Starta utgående samtal"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"Starta inkommande samtal"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"nummerpresentatör inte inställd"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+    <string name="answer" msgid="5423590397665409939">"svara"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"koppla från"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Lur"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"Högtalare"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+    <string name="start_stream" msgid="3567634786280097431">"starta streaming"</string>
+    <string name="crash_app" msgid="2548690390730057704">"utlös undantag"</string>
+    <string name="update_notification" msgid="8677916482672588779">"uppdatera avisering till format för pågående samtal"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-sw/strings.xml b/testapps/transactionalVoipApp/res/values-sw/strings.xml
index 11a5a77..b7d0d0f 100644
--- a/testapps/transactionalVoipApp/res/values-sw/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-sw/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"Shughuli za jaribio la API ya Uthibitishaji"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"Shughuli ya Muamala Kwenye Simu"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"Sajili Akaunti ya Simu"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"simu unazopiga"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"simu zinazoingia"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"weka simu ya 1"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"kata simu ya 1"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"weka simu ya 2"</string>
-    <string name="set_call_active" msgid="248748409907478011">"piga simu ya 2"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"kata simu ya 2"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"Anzisha FGS (kuiga Tafsiri ya Mashine na programu katika hali ya chinichini)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"Anzisha Uigaji wa Simu Unayopiga"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"Anzisha Uigaji wa Simu Uliyopigiwa"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"kitambulisho cha anayepiga hakijawekwa"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+    <string name="answer" msgid="5423590397665409939">"jibu"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"ondoa"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Spika ya sikioni"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"Spika"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+    <string name="start_stream" msgid="3567634786280097431">"anzisha kutiririsha"</string>
+    <string name="crash_app" msgid="2548690390730057704">"hitilafu wakati wa kutekeleza programu"</string>
+    <string name="update_notification" msgid="8677916482672588779">"sasisha arifa kwenye mtindo wa simu inayoendelea"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-ta/strings.xml b/testapps/transactionalVoipApp/res/values-ta/strings.xml
index 73d9944..39b410a 100644
--- a/testapps/transactionalVoipApp/res/values-ta/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-ta/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"Transactional API சோதனை செயல்பாடு"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"டிரான்சாக்‌ஷனல் இன் கால் ஆக்டிவிட்டி"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"மொபைல் கணக்கைப் பதிவுசெய்தல்"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"வெளிச்செல்லும் அழைப்பு"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"உள்வரும் அழைப்பு"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"அழைப்பு 1ஐச் சேர்த்தல்"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"அழைப்பு 1ஐத் துண்டித்தல்"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"அழைப்பு 2ஐச் சேர்த்தல்"</string>
-    <string name="set_call_active" msgid="248748409907478011">"அழைப்பு 2 செயலில் உள்ளதாக அமைத்தல்"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"அழைப்பு 2ஐத் துண்டித்தல்"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"FGSஸைத் தொடங்கு (MT + ஆப்ஸைப் பின்னணியில் சிமுலேட் செய்)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"வெளிச்செல்லும் அழைப்பைத் தொடங்கு"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"உள்வரும் அழைப்பைத் தொடங்கு"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"அழைப்பு ஐடி அமைக்கப்படவில்லை"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"செயலில் அமை"</string>
+    <string name="answer" msgid="5423590397665409939">"பதில்"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"செயலற்ற நிலையில் அமை"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"துண்டி"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"ஒலி கேட்கும் பகுதி"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"ஸ்பீக்கர்"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"புளூடூத்"</string>
+    <string name="start_stream" msgid="3567634786280097431">"ஸ்ட்ரீமிங்கைத் தொடங்கு"</string>
+    <string name="crash_app" msgid="2548690390730057704">"விதிவிலக்கைத் தொடங்கு"</string>
+    <string name="update_notification" msgid="8677916482672588779">"செயலில் உள்ள அழைப்பு ஸ்டைலுக்கான அறிவிப்பைப் புதுப்பிக்கவும்"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-te/strings.xml b/testapps/transactionalVoipApp/res/values-te/strings.xml
index 708122a..f4560ab 100644
--- a/testapps/transactionalVoipApp/res/values-te/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-te/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"లావాదేవీల API టెస్ట్ యాక్టివిటీ"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"కాల్ యాక్టివిటీలో లావాదేవీ"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"ఫోన్ ఖాతాను రిజిస్టర్ చేయండి"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"అవుట్‌గోయింగ్"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"ఇన్‌కమింగ్"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"కాల్ 1ని జోడించండి"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"కాల్ 1ని డిస్కనెక్ట్ చేయండి"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"కాల్ 2ను జోడించండి"</string>
-    <string name="set_call_active" msgid="248748409907478011">"కాల్ 2ను యాక్టివ్‌గా సెట్ చేయండి"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"కాల్ 2ను డిస్కనెక్ట్ చేయండి"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"FGS (అనుకరణ MT + బ్యాక్‌గ్రౌండ్‌లో యాప్)ను ప్రారంభించండి"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"అవుట్‌గోయింగ్ కాల్‌ను ప్రారంభించండి"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"ఇన్‌కమింగ్ కాల్‌ను ప్రారంభించండి"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"కాల్ id సెట్ చేయబడలేదు"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+    <string name="answer" msgid="5423590397665409939">"సమాధానం"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"డిస్‌కనెక్ట్ చేయండి"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"ఇయర్‌పీస్"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"స్పీకర్"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"బ్లూటూత్"</string>
+    <string name="start_stream" msgid="3567634786280097431">"స్ట్రీమింగ్‌ను ప్రారంభించండి"</string>
+    <string name="crash_app" msgid="2548690390730057704">"మినహాయింపు వేయండి"</string>
+    <string name="update_notification" msgid="8677916482672588779">"జరుగుతున్న కాల్ స్టయిల్‌కి నోటిఫికేషన్‌ను అప్‌డేట్ చేయండి"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-th/strings.xml b/testapps/transactionalVoipApp/res/values-th/strings.xml
index 207f291..545110b 100644
--- a/testapps/transactionalVoipApp/res/values-th/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-th/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"กิจกรรมการทดสอบ API ธุรกรรม"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"กิจกรรมธุรกรรมระหว่างการโทร"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"ลงทะเบียนบัญชีของโทรศัพท์"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"สายโทรออก"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"สายเรียกเข้า"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"เพิ่มการโทร 1"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"ตัดสาย 1"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"เพิ่มการโทร 2"</string>
-    <string name="set_call_active" msgid="248748409907478011">"ตั้งค่าการโทร 2 เป็นใช้งานอยู่"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"ตัดสาย 2"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"เริ่ม FGS (จําลอง MT + แอปในพื้นหลัง)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"เริ่มสายโทรออก"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"เริ่มสายเรียกเข้า"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"ไม่ได้ตั้งค่าหมายเลขผู้โทร"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"ตั้งค่าเป็นใช้งานอยู่"</string>
+    <string name="answer" msgid="5423590397665409939">"คำตอบ"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"ตั้งค่าเป็นไม่ใช้งาน"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"ยกเลิกการเชื่อมต่อ"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"หูฟังโทรศัพท์"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"ลำโพง"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"บลูทูธ"</string>
+    <string name="start_stream" msgid="3567634786280097431">"เริ่มสตรีมมิง"</string>
+    <string name="crash_app" msgid="2548690390730057704">"ส่งข้อยกเว้น"</string>
+    <string name="update_notification" msgid="8677916482672588779">"อัปเดตการแจ้งเตือนไปยังรูปแบบการโทรที่ดำเนินอยู่"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-tl/strings.xml b/testapps/transactionalVoipApp/res/values-tl/strings.xml
index 77f963b..6cc2a2b 100644
--- a/testapps/transactionalVoipApp/res/values-tl/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-tl/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"Aktibidad ng pansubok na Transactional API"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"Transaksyonal na In Call na Aktibidad"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"Irehistro ang Phone Account"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"papalabas"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"incoming"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"magdagdag ng tawag 1"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"idiskonekta ang tawag 1"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"magdagdag ng tawag 2"</string>
-    <string name="set_call_active" msgid="248748409907478011">"itakdang aktibo ang tawag 2"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"idiskonekta ang tawag 2"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"Simulan ang FGS (i-simulate ang MT + app sa background)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"Magsimula ng Papalabas na Tawag"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"Magsimula ng Papasok na Tawag"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"hindi naitakda ang call id"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+    <string name="answer" msgid="5423590397665409939">"sagutin"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"idiskonekta"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Earpiece"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"Speaker"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+    <string name="start_stream" msgid="3567634786280097431">"simulan ang streaming"</string>
+    <string name="crash_app" msgid="2548690390730057704">"throw exception"</string>
+    <string name="update_notification" msgid="8677916482672588779">"i-update ang notification sa istilo ng kasalukuyang tawag"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-tr/strings.xml b/testapps/transactionalVoipApp/res/values-tr/strings.xml
index 4d31191..ec23048 100644
--- a/testapps/transactionalVoipApp/res/values-tr/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-tr/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"Transactional API test etkinliği"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"Görüşme İçin İşlem Etkinliği"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"Telefon Hesabını Kaydet"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"giden"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"gelen"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"1. aramayı ekle"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"1. aramayı sonlandır"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"2. aramayı ekle"</string>
-    <string name="set_call_active" msgid="248748409907478011">"2. aramayı etkinleştir"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"2. aramayı sonlandır"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"Ön plan hizmetlerini (FGS) başlat (makine çevirisi + arka plandaki uygulamayı simüle et)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"Giden Arama Başlat"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"Gelen Arama Başlat"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"arama kimliği ayarlanmadı"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+    <string name="answer" msgid="5423590397665409939">"yanıtla"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"bağlantıyı kes"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Kulaklık"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"Hoparlör"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+    <string name="start_stream" msgid="3567634786280097431">"yayın başlat"</string>
+    <string name="crash_app" msgid="2548690390730057704">"istisna gönder"</string>
+    <string name="update_notification" msgid="8677916482672588779">"bildirimi devam eden arama stiline güncelle"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-uk/strings.xml b/testapps/transactionalVoipApp/res/values-uk/strings.xml
index d1f5f1d..0069f3d 100644
--- a/testapps/transactionalVoipApp/res/values-uk/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-uk/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"Тестування API підтвердження"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"Трансакції під час викликів"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"Зареєструвати обліковий запис телефона"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"вихідні дзвінки"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"вхідні дзвінки"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"додати дзвінок 1"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"завершити дзвінок 1"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"додати дзвінок 2"</string>
-    <string name="set_call_active" msgid="248748409907478011">"активувати дзвінок 2"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"завершити дзвінок 2"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"Запустити активний сервіс (симуляція МП + додаток у фоновому режимі)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"Почати вихідний виклик"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"Почати вхідний виклик"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"ідентифікатор виклику не налаштовано"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"позначити як активний"</string>
+    <string name="answer" msgid="5423590397665409939">"відповідь"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"позначити як неактивний"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"від’єднати"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Динамік"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"Колонка"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+    <string name="start_stream" msgid="3567634786280097431">"Почати трансляцію"</string>
+    <string name="crash_app" msgid="2548690390730057704">"надіслати повідомлення про виняток"</string>
+    <string name="update_notification" msgid="8677916482672588779">"стиль сповіщення про оновлення для поточного дзвінка"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-ur/strings.xml b/testapps/transactionalVoipApp/res/values-ur/strings.xml
index 8c14f04..e41027a 100644
--- a/testapps/transactionalVoipApp/res/values-ur/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-ur/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"‏ٹرانزیکشنل API ٹیسٹ کی سرگرمی"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"کال کی سرگرمی میں ٹرانزیکشنل"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"فون کے اکاؤنٹ کو رجسٹر کریں"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"آؤٹ گوئنگ"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"اِن کمنگ"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"کال 1 کو شامل کریں"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"کال 1 کو منقطع کریں"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"کال 2 کو شامل کریں"</string>
-    <string name="set_call_active" msgid="248748409907478011">"کال 2 کو فعال کریں"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"کال 2 کو منقطع کریں"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"‏FGS شروع کریں ( بیک گراؤنڈ میں MT +‎ ایپ کی نقل کریں)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"آؤٹ گوئنگ کال شروع کریں"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"اِن کمنگ کال شروع کریں"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"‏کال ID سیٹ نہیں ہے"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"فعال پر سیٹ کریں"</string>
+    <string name="answer" msgid="5423590397665409939">"جواب"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"غیر فعال پر سیٹ کریں"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"غیر منسلک کریں"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"ایئر پیس"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"اسپیکر"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"بلوٹوتھ"</string>
+    <string name="start_stream" msgid="3567634786280097431">"سلسلہ بندی شروع کریں"</string>
+    <string name="crash_app" msgid="2548690390730057704">"تھرو ایکسیپشن"</string>
+    <string name="update_notification" msgid="8677916482672588779">"اطلاع کو جاری کال طرز پر اپ ڈیٹ کریں"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-uz/strings.xml b/testapps/transactionalVoipApp/res/values-uz/strings.xml
index 4e212a1..faa0b4b 100644
--- a/testapps/transactionalVoipApp/res/values-uz/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-uz/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"Tranzaksiyaviy API sinovi faoliyati"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"Chaqiruvda tranzaksiya faoliyati"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"Telefon hisobini ro‘yxatdan o‘tkazish"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"chiquvchi"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"kiruvchi"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"chaqiruv qo‘shish 1"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"chaqiruvni uzish 1"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"chaqiruv qo‘shish 2"</string>
-    <string name="set_call_active" msgid="248748409907478011">"chaqiruv 2-ni faol qilish"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"chaqiruvni uzish 2"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"FGS boshlash (MT + fonda ilova simulyatsiyasi)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"Chiquvchi chaqiruvni boshlash"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"Kiruvchi chaqiruvni boshlash"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"chaqiruv id belgilanmagan"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+    <string name="answer" msgid="5423590397665409939">"javob berish"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"uzish"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Quloq karnaychasi"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"Karnay"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+    <string name="start_stream" msgid="3567634786280097431">"strimingni boshlash"</string>
+    <string name="crash_app" msgid="2548690390730057704">"istisno berish"</string>
+    <string name="update_notification" msgid="8677916482672588779">"bildirishnomani joriy chaqiruv uslubida yangilash"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-vi/strings.xml b/testapps/transactionalVoipApp/res/values-vi/strings.xml
index 2bcd65f..a54d544 100644
--- a/testapps/transactionalVoipApp/res/values-vi/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-vi/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"Hoạt động kiểm tra cho API Xác nhận trao đổi"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"Hoạt động giao dịch trong cuộc gọi"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"Đăng ký tài khoản điện thoại"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"cuộc gọi đi"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"cuộc gọi đến"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"thêm cuộc gọi 1"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"ngắt cuộc gọi 1"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"thêm cuộc gọi 2"</string>
-    <string name="set_call_active" msgid="248748409907478011">"đặt cuộc gọi 2 ở trạng thái đang diễn ra"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"ngắt cuộc gọi 2"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"Khởi động FGS (mô phỏng MT + ứng dụng trong nền)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"Bắt đầu cuộc gọi đi"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"Bắt đầu cuộc gọi đến"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"chưa đặt mã cuộc gọi"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"Đặt thành đang hoạt động"</string>
+    <string name="answer" msgid="5423590397665409939">"trả lời"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"Đặt thành không hoạt động"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"ngắt kết nối"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Loa tai nghe"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"Loa"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"Bluetooth"</string>
+    <string name="start_stream" msgid="3567634786280097431">"bắt đầu phát trực tuyến"</string>
+    <string name="crash_app" msgid="2548690390730057704">"đưa ra trường hợp ngoại lệ"</string>
+    <string name="update_notification" msgid="8677916482672588779">"cập nhật thông báo thành kiểu cuộc gọi đang diễn ra"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-zh-rCN/strings.xml b/testapps/transactionalVoipApp/res/values-zh-rCN/strings.xml
index 512fdea..a74cbb5 100644
--- a/testapps/transactionalVoipApp/res/values-zh-rCN/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-zh-rCN/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"事务性 API 测试活动"</string>
-    <string name="register_phone_account" msgid="1920315963082350332">"注册电话帐号"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"外拨电话"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"来电"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"添加通话 1"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"中断通话 1"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"添加通话 2"</string>
-    <string name="set_call_active" msgid="248748409907478011">"将通话 2 设置为通话中"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"中断通话 2"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"通话活动中的事务"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"注册电话账号"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"启动 FGS(在后台模拟 MT + 应用)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"开始去电"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"开始来电"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"未设置来电显示/本机号码"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"setActive"</string>
+    <string name="answer" msgid="5423590397665409939">"回复"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"setInactive"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"断开连接"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"手机听筒"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"扬声器"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"蓝牙"</string>
+    <string name="start_stream" msgid="3567634786280097431">"开始直播"</string>
+    <string name="crash_app" msgid="2548690390730057704">"抛出异常"</string>
+    <string name="update_notification" msgid="8677916482672588779">"将通知更新为当前通话样式"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-zh-rHK/strings.xml b/testapps/transactionalVoipApp/res/values-zh-rHK/strings.xml
index 7729a28..e00caa9 100644
--- a/testapps/transactionalVoipApp/res/values-zh-rHK/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-zh-rHK/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"Transactional API 測試活動"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"交易來電活動"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"註冊電話帳戶"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"撥出"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"來電"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"新增通話 1"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"中斷通話 1"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"新增通話 2"</string>
-    <string name="set_call_active" msgid="248748409907478011">"將通話 2 設為進行中"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"中斷通話 2"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"開始 FGS (模擬 MT + 背景應用程式)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"開始撥出電話"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"開始來電"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"未設定來電顯示"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"設為使用中"</string>
+    <string name="answer" msgid="5423590397665409939">"接聽"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"設為停用"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"解除連結"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"聽筒"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"喇叭"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"藍牙"</string>
+    <string name="start_stream" msgid="3567634786280097431">"開始串流播放"</string>
+    <string name="crash_app" msgid="2548690390730057704">"擲回例外狀況"</string>
+    <string name="update_notification" msgid="8677916482672588779">"更新通知至通話中樣式"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-zh-rTW/strings.xml b/testapps/transactionalVoipApp/res/values-zh-rTW/strings.xml
index 90f8299..1a6da94 100644
--- a/testapps/transactionalVoipApp/res/values-zh-rTW/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-zh-rTW/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"交易 API 測試活動"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"通話活動交易資訊"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"註冊電話帳戶"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"撥出通話"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"來電"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"新增通話 1"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"中斷通話 1"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"新增通話 2"</string>
-    <string name="set_call_active" msgid="248748409907478011">"將通話 2 設為啟用"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"中斷通話 2"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"啟動 FGS (在背景模擬機器翻譯和應用程式)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"開始模擬撥出電話"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"開始模擬來電"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"未設定通話 ID"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"設為使用中"</string>
+    <string name="answer" msgid="5423590397665409939">"接聽"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"設為閒置"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"掛斷"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"耳機"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"喇叭"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"藍牙"</string>
+    <string name="start_stream" msgid="3567634786280097431">"開始串流播放"</string>
+    <string name="crash_app" msgid="2548690390730057704">"擲回例外狀況"</string>
+    <string name="update_notification" msgid="8677916482672588779">"將通知更新為通話中樣式"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values-zu/strings.xml b/testapps/transactionalVoipApp/res/values-zu/strings.xml
index 7689224..cd86e81 100644
--- a/testapps/transactionalVoipApp/res/values-zu/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-zu/strings.xml
@@ -18,12 +18,20 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="2907804426411305091">"Umsebenzi wokuhlolwa kwe-Transactional API"</string>
+    <string name="in_call_activity_name" msgid="7545884666442897585">"Okwenziwayo Emsebenzini Wekholi"</string>
     <string name="register_phone_account" msgid="1920315963082350332">"Bhalisa I-akhawunti Yefoni"</string>
-    <string name="direction_outgoing" msgid="2535369086068422248">"okuphumayo"</string>
-    <string name="direction_incoming" msgid="3279919900558410227">"okungenayo"</string>
-    <string name="add_call_1" msgid="5825706540046010457">"engeza ikholi e-1"</string>
-    <string name="disconnect_call_1" msgid="2687960802565131403">"Nqamula ikholi e-1"</string>
-    <string name="add_call_2" msgid="6706005258041717434">"engeza ikholi yesi-2"</string>
-    <string name="set_call_active" msgid="248748409907478011">"setha ikholi yesi-2 ukuthi isebenze"</string>
-    <string name="disconnect_call_2" msgid="133113412102219516">"Nqamula ikholi yesi-2"</string>
+    <string name="start_foreground_service" msgid="8968755699895128574">"Qala ama-FGS (lingisa i-app ye-MT + ngemuva)"</string>
+    <string name="start_outgoing" msgid="1441644037370361864">"Qala ikholi ephumela ngaphandle"</string>
+    <string name="start_incoming" msgid="6444983300186361271">"Qala Ikholi Engenayo"</string>
+    <string name="get_call_id" msgid="5513943242738347108">"I-ID yekholi ayisethiwe"</string>
+    <string name="set_call_active" msgid="3365404393507589899">"I-setActive"</string>
+    <string name="answer" msgid="5423590397665409939">"impendulo"</string>
+    <string name="set_call_inactive" msgid="7106775211368705195">"I-setInactive"</string>
+    <string name="disconnect_call" msgid="1349412380315371385">"nqamula"</string>
+    <string name="request_earpiece_endpoint" msgid="6649571985089296573">"Isipikha sendlebe"</string>
+    <string name="request_speaker_endpoint" msgid="1033259535289845405">"Isipikha"</string>
+    <string name="request_bluetooth_endpoint" msgid="5933254250623451836">"I-Bluetooth"</string>
+    <string name="start_stream" msgid="3567634786280097431">"Qala ukusakaza-bukhoma"</string>
+    <string name="crash_app" msgid="2548690390730057704">"phonsela okuhlukile"</string>
+    <string name="update_notification" msgid="8677916482672588779">"buyekeza isaziso kusitayela sekholi eqhubekayo"</string>
 </resources>
diff --git a/testapps/transactionalVoipApp/res/values/strings.xml b/testapps/transactionalVoipApp/res/values/strings.xml
index 038adc1..8239a0e 100644
--- a/testapps/transactionalVoipApp/res/values/strings.xml
+++ b/testapps/transactionalVoipApp/res/values/strings.xml
@@ -17,13 +17,28 @@
 
 <resources>
     <string name="app_name">Transactional API test Activity</string>
+    <string name="in_call_activity_name">Transactional In Call Activity</string>
+
+    <!-- Main Activity -->
     <string name="register_phone_account">Register Phone Account</string>
-    <string name="direction_outgoing">outgoing</string>
-    <string name="direction_incoming">incoming</string>
-    <string name="add_call_1">add call 1</string>
-    <string name="disconnect_call_1">disconnect call 1</string>
-    <string name="add_call_2">add call 2</string>
-    <string name="set_call_active">set call 2 active</string>
-    <string name="disconnect_call_2">disconnect call 2</string>
+    <string name="start_foreground_service">Start FGS (simulate MT + app in background)</string>
+    <string name="start_outgoing">Start Outgoing Call</string>
+    <string name="start_incoming">Start Incoming Call</string>
+
+    <!-- InCall Activity -->
+    <string name="get_call_id">call id not set</string>
+    <!--  control the call state -->
+    <string name="set_call_active">setActive</string>
+    <string name="answer">answer</string>
+    <string name="set_call_inactive">setInactive</string>
+    <string name="disconnect_call">disconnect</string>
+    <!-- control the call audio -->
+    <string name="request_earpiece_endpoint">Earpiece</string>
+    <string name="request_speaker_endpoint">Speaker</string>
+    <string name="request_bluetooth_endpoint">Bluetooth</string>
+    <!-- extra functionality -->
+    <string name="start_stream">start streaming</string>
+    <string name="crash_app">throw exception</string>
+    <string name="update_notification"> update notification to ongoing call style</string>
 
 </resources>
\ No newline at end of file
diff --git a/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/BackgroundIncomingCallService.java b/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/BackgroundIncomingCallService.java
new file mode 100644
index 0000000..b503e94
--- /dev/null
+++ b/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/BackgroundIncomingCallService.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2023 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.transactionalVoipApp;
+
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL;
+
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.Service;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.IBinder;
+import android.util.Log;
+
+public class BackgroundIncomingCallService extends Service {
+    // finals
+    private static final String TAG = "BackgroundIncomingCallService";
+    // instance vars
+    private NotificationManager mNotificationManager;
+    private final IBinder mBinder = new LocalBinder();
+
+    @Override
+    public void onCreate() {
+        Log.i(TAG, "onCreate");
+        mNotificationManager = getSystemService(NotificationManager.class);
+    }
+
+    @Override
+    @StartResult
+    public int onStartCommand(Intent intent, @StartArgFlags int flags, int startId) {
+        Log.i(TAG, String.format("onStartCommand: intent=[%s]", intent));
+
+        // create the notification channel
+        if (mNotificationManager != null) {
+            mNotificationManager.createNotificationChannel(new NotificationChannel(
+                    Utils.CHANNEL_ID, "incoming calls", NotificationManager.IMPORTANCE_DEFAULT));
+        }
+
+        // start the foreground service and post a notification
+        startForeground(98765, Utils.createCallStyleNotification(this),
+                FOREGROUND_SERVICE_TYPE_PHONE_CALL);
+
+        return Service.START_STICKY_COMPATIBILITY;
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        Log.i(TAG, String.format("onBind: intent=[%s]", intent));
+        return mBinder;
+    }
+
+    /**
+     * Class used for the client Binder.  Because we know this service always
+     * runs in the same process as its clients, we don't need to deal with IPC.
+     */
+    public class LocalBinder extends Binder {
+        BackgroundIncomingCallService getService() {
+            // Return this instance of LocalService so clients can call public methods
+            return BackgroundIncomingCallService.this;
+        }
+    }
+}
diff --git a/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/InCallActivity.java b/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/InCallActivity.java
new file mode 100644
index 0000000..50556a1
--- /dev/null
+++ b/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/InCallActivity.java
@@ -0,0 +1,303 @@
+/*
+ * Copyright (C) 2023 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.transactionalVoipApp;
+
+import static android.telecom.CallAttributes.AUDIO_CALL;
+import static android.telecom.CallAttributes.DIRECTION_INCOMING;
+import static android.telecom.CallAttributes.DIRECTION_OUTGOING;
+
+import android.app.Activity;
+import android.media.AudioManager;
+import android.media.AudioRecord;
+import android.media.MediaPlayer;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.OutcomeReceiver;
+import android.telecom.CallAttributes;
+import android.telecom.CallControl;
+import android.telecom.CallEndpoint;
+import android.telecom.CallException;
+import android.telecom.DisconnectCause;
+import android.telecom.TelecomManager;
+import android.util.Log;
+import android.view.View;
+import android.widget.TextView;
+
+public class InCallActivity extends Activity {
+    private static final String TAG = "InCallActivity";
+    private final AudioManager.AudioRecordingCallback mAudioRecordingCallback =
+            Utils.getAudioRecordingCallback();
+    private static TelecomManager mTelecomManager;
+    private MyVoipCall mVoipCall;
+    private MediaPlayer mMediaPlayer;
+    private AudioRecord mAudioRecord;
+    private int mCallDirection = DIRECTION_INCOMING;
+    private TextView mCurrentEndpointTextView;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        Log.i(TAG, "#onCreate: in function");
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.in_call_activity);
+
+        Bundle extras = getIntent().getExtras();
+        // Copy the extras with properties like call direction into the extras so the below
+        // code can access them.
+        if (extras != null && extras.containsKey(Utils.sEXTRAS_KEY)) {
+            extras.putAll(extras.getBundle(Utils.sEXTRAS_KEY));
+        }
+        if (extras != null) {
+            mCallDirection = extras.getInt(Utils.sCALL_DIRECTION_KEY, DIRECTION_INCOMING);
+        }
+        mCurrentEndpointTextView = findViewById(R.id.current_endpoint);
+        mCurrentEndpointTextView.setText("Endpoint/Audio Route NOT ESTABLISHED");
+        updateCallId();
+        mTelecomManager = getSystemService(TelecomManager.class);
+        mMediaPlayer = Utils.createMediaPlayer(getApplicationContext());
+        mAudioRecord = Utils.createAudioRecord();
+        mAudioRecord.registerAudioRecordingCallback(Runnable::run, mAudioRecordingCallback);
+
+        if (mVoipCall == null) {
+            addCall();
+        }
+
+        findViewById(R.id.set_call_active_button).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                updateCurrentEndpoint();
+                if (canUseCallControl()) {
+                    mVoipCall.mCallControl.setActive(Runnable::run,
+                            Utils.getLoggableOutcomeReceiver("setActive"));
+                }
+                mAudioRecord.startRecording();
+                mMediaPlayer.start();
+            }
+        });
+
+
+        findViewById(R.id.answer_button).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                updateCurrentEndpoint();
+                if (canUseCallControl() && mCallDirection != DIRECTION_OUTGOING) {
+                    mVoipCall.mCallControl.answer(AUDIO_CALL, Runnable::run,
+                            Utils.getLoggableOutcomeReceiver("answer"));
+                    mAudioRecord.startRecording();
+                    mMediaPlayer.start();
+                }
+            }
+        });
+
+
+        findViewById(R.id.set_call_inactive_button).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                if (canUseCallControl()) {
+                    mVoipCall.mCallControl.setInactive(Runnable::run,
+                            Utils.getLoggableOutcomeReceiver("setInactive"));
+                }
+                mAudioRecord.stop();
+                mMediaPlayer.pause();
+            }
+        });
+
+        findViewById(R.id.disconnect_call_button).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                disconnectAndStopAudio();
+                finish();
+            }
+        });
+
+        findViewById(R.id.start_stream_button).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                if (canUseCallControl()) {
+                    mVoipCall.mCallControl.startCallStreaming(Runnable::run,
+                            Utils.getLoggableOutcomeReceiver("startCallStream"));
+                }
+            }
+        });
+
+        findViewById(R.id.request_earpiece).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                if (canUseCallControl() && mVoipCall.mEarpieceEndpoint != null) {
+                    requestEndpointChange(mVoipCall.mEarpieceEndpoint,
+                            "Request EARPIECE Endpoint:");
+                }
+            }
+        });
+
+        findViewById(R.id.request_speaker).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                if (canUseCallControl() && mVoipCall.mSpeakerEndpoint != null) {
+                    requestEndpointChange(mVoipCall.mSpeakerEndpoint,
+                            "Request SPEAKER Endpoint:");
+                }
+            }
+        });
+
+        findViewById(R.id.request_bluetooth).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                if (canUseCallControl() && mVoipCall.mBluetoothEndpoint != null) {
+                    requestEndpointChange(mVoipCall.mBluetoothEndpoint,
+                            "Request BLUETOOTH Endpoint:");
+                }
+            }
+        });
+
+        findViewById(R.id.crash_app).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                // To test edge cases, it may be useful to crash the app. To do this, throwing a
+                // RuntimeException is sufficient.
+                throw new RuntimeException(
+                        "Intentionally throwing RuntimeException from InCallActivity");
+            }
+        });
+
+        findViewById(R.id.updateCallStyleNotification).setOnClickListener(
+                new View.OnClickListener() {
+                    @Override
+                    public void onClick(View v) {
+                        Utils.updateCallStyleNotification_toOngoingCall(getApplicationContext());
+                    }
+                });
+    }
+
+    @Override
+    protected void onStop() {
+        Log.i(TAG, "onStop: InCallActivity has stopped");
+        super.onStop();
+    }
+
+    @Override
+    protected void onDestroy() {
+        Log.i(TAG, "onDestroy: InCallActivity has been destroyed");
+        disconnectAndStopAudio();
+        super.onDestroy();
+    }
+
+    private boolean canUseCallControl() {
+        return mVoipCall != null && mVoipCall.mCallControl != null;
+    }
+
+    private void updateCurrentEndpoint() {
+        if (mCurrentEndpointTextView != null) {
+            if (mVoipCall != null && mVoipCall.mCurrentEndpoint != null) {
+                mCurrentEndpointTextView.setText("CallEndpoint=[" +
+                        mVoipCall.mCurrentEndpoint.getEndpointName() + "]");
+            }
+        }
+    }
+
+    private void updateCurrentEndpointWithOnResult(CallEndpoint endpoint) {
+        if (mCurrentEndpointTextView != null) {
+            if (mVoipCall != null && mVoipCall.mCurrentEndpoint != null) {
+                mCurrentEndpointTextView.setText("CallEndpoint=[" +
+                        endpoint.getEndpointName() + "]");
+            }
+        }
+    }
+
+    private void updateCallId() {
+        TextView view = findViewById(R.id.getCallIdTextView);
+        StringBuilder sb = new StringBuilder();
+        sb.append("[");
+        if (canUseCallControl()) {
+            String id = mVoipCall.mCallControl.getCallId().toString();
+            sb.append(id);
+        } else {
+            sb.append("Error Getting Id");
+        }
+        sb.append("]");
+        try {
+            view.setText(sb.toString());
+        }
+        catch (Exception e){
+            // ignore updating the ui
+        }
+    }
+
+    private void addCall() {
+        mVoipCall = new MyVoipCall("123");
+
+        CallAttributes callAttributes =
+                new CallAttributes.Builder(
+                        Utils.PHONE_ACCOUNT_HANDLE,
+                        mCallDirection,
+                        "Alan Turing",
+                        Uri.parse("tel:+16506959001")).build();
+
+        mTelecomManager.addCall(callAttributes, Runnable::run,
+                new OutcomeReceiver<CallControl, CallException>() {
+                    @Override
+                    public void onResult(CallControl callControl) {
+                        Log.i(TAG, "addCall: onResult: callback fired");
+                        Utils.postIncomingCallStyleNotification(getApplicationContext());
+                        mVoipCall.onAddCallControl(callControl);
+                        updateCallId();
+                        updateCurrentEndpoint();
+                    }
+
+                    @Override
+                    public void onError(CallException exception) {
+
+                    }
+                },
+                mVoipCall, mVoipCall);
+    }
+
+    private void disconnectAndStopAudio() {
+        if (mVoipCall != null) {
+            mVoipCall.mCallControl.disconnect(
+                    new DisconnectCause(DisconnectCause.LOCAL),
+                    Runnable::run,
+                    Utils.getLoggableOutcomeReceiver("disconnect"));
+        }
+        mMediaPlayer.stop();
+        mAudioRecord.stop();
+        try {
+            mAudioRecord.unregisterAudioRecordingCallback(mAudioRecordingCallback);
+            Utils.clearNotification(getApplicationContext());
+        } catch (Exception e) {
+            // pass through
+        }
+    }
+
+    private void requestEndpointChange(CallEndpoint endpoint, String tag) {
+        mVoipCall.mCallControl.requestCallEndpointChange(
+                endpoint,
+                Runnable::run,
+                new OutcomeReceiver<Void, CallException>() {
+                    @Override
+                    public void onResult(Void result) {
+                        Log.i(TAG, String.format("requestEndpointChange: success w/ %s", tag));
+                        updateCurrentEndpointWithOnResult(endpoint);
+                    }
+
+                    @Override
+                    public void onError(CallException e) {
+                        Log.i(TAG, String.format("requestEndpointChange: %s failed to switch to "
+                                + "endpoint=[%s] due to exception=[%s]", tag, endpoint, e));
+                    }
+                });
+    }
+}
diff --git a/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/MyVoipCall.java b/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/MyVoipCall.java
index 690311e..e2b5b14 100644
--- a/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/MyVoipCall.java
+++ b/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/MyVoipCall.java
@@ -24,6 +24,7 @@
 import android.telecom.DisconnectCause;
 import android.util.Log;
 
+import java.util.ArrayList;
 import java.util.List;
 
 import androidx.annotation.NonNull;
@@ -34,7 +35,12 @@
 
     private static final String TAG = "MyVoipCall";
     private final String mCallId;
-    CallControl mCallControl;
+    public CallControl mCallControl;
+    public CallEndpoint mCurrentEndpoint;
+    public CallEndpoint mEarpieceEndpoint;
+    public CallEndpoint mSpeakerEndpoint;
+    public CallEndpoint mBluetoothEndpoint;
+    List<CallEndpoint> mAvailableEndpoint = new ArrayList<>();
 
     MyVoipCall(String id) {
         mCallId = id;
@@ -89,6 +95,7 @@
     @Override
     public void onCallEndpointChanged(@NonNull CallEndpoint newCallEndpoint) {
         Log.i(TAG, String.format("onCallEndpointChanged: endpoint=[%s]", newCallEndpoint));
+        mCurrentEndpoint = newCallEndpoint;
     }
 
     @Override
@@ -97,7 +104,17 @@
         Log.i(TAG, String.format("onAvailableCallEndpointsChanged: callId=[%s]", mCallId));
         for (CallEndpoint endpoint : availableEndpoints) {
             Log.i(TAG, String.format("endpoint=[%s]", endpoint));
+            if (endpoint != null && endpoint.getEndpointType() == CallEndpoint.TYPE_EARPIECE) {
+                mEarpieceEndpoint = endpoint;
+            }
+            if (endpoint != null && endpoint.getEndpointType() == CallEndpoint.TYPE_SPEAKER) {
+                mSpeakerEndpoint = endpoint;
+            }
+            if (endpoint != null && endpoint.getEndpointType() == CallEndpoint.TYPE_BLUETOOTH) {
+                mBluetoothEndpoint = endpoint;
+            }
         }
+        mAvailableEndpoint = availableEndpoints;
     }
 
     @Override
diff --git a/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/Utils.java b/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/Utils.java
new file mode 100644
index 0000000..ec448b2
--- /dev/null
+++ b/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/Utils.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2023 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.transactionalVoipApp;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Person;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.AudioRecord;
+import android.media.AudioRecordingConfiguration;
+import android.media.MediaPlayer;
+import android.media.MediaRecorder;
+import android.os.OutcomeReceiver;
+import android.telecom.CallException;
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
+import android.util.Log;
+
+import java.util.List;
+
+public class Utils {
+    public static final String TAG = "TransactionalAppUtils";
+    public static final String CALLER_NAME = "Sundar Pichai";
+    public static final String sEXTRAS_KEY = "ExtrasKey";
+    public static final String sCALL_DIRECTION_KEY = "CallDirectionKey";
+    public static final String CHANNEL_ID = "TelecomVoipAppChannelId";
+    public static final int CALL_NOTIFICATION_ID = 123456;
+    private static final int SAMPLING_RATE_HZ = 44100;
+
+    public static final PhoneAccountHandle PHONE_ACCOUNT_HANDLE = new PhoneAccountHandle(
+            new ComponentName("com.android.server.telecom.transactionalVoipApp",
+                    "com.android.server.telecom.transactionalVoipApp.VoipAppMainActivity"), "123");
+
+    public static final PhoneAccount PHONE_ACCOUNT =
+            PhoneAccount.builder(PHONE_ACCOUNT_HANDLE, "test label")
+                    .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED |
+                            PhoneAccount.CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS).build();
+
+
+    public static Notification createCallStyleNotification(Context context) {
+        Intent answerIntent = new Intent(context, InCallActivity.class);
+        Intent rejectIntent = new Intent(context, InCallActivity.class);
+
+        // Creating a pending intent and wrapping our intent
+        PendingIntent pendingAnswer = PendingIntent.getActivity(context, 0,
+                answerIntent, PendingIntent.FLAG_IMMUTABLE);
+        PendingIntent pendingReject = PendingIntent.getActivity(context, 0,
+                rejectIntent, PendingIntent.FLAG_IMMUTABLE);
+
+
+        Notification callStyleNotification = new Notification.Builder(context,
+                CHANNEL_ID)
+                .setContentText("Answer/Reject call")
+                .setContentTitle("Incoming call")
+                .setSmallIcon(R.drawable.ic_android_black_24dp)
+                .setStyle(Notification.CallStyle.forIncomingCall(
+                        new Person.Builder().setName(CALLER_NAME).setImportant(true).build(),
+                        pendingAnswer, pendingReject)
+                )
+                .setFullScreenIntent(pendingAnswer, true)
+                .setOngoing(true)
+                .build();
+
+        return callStyleNotification;
+    }
+
+    public static void postIncomingCallStyleNotification(Context context) {
+        NotificationManager nm = context.getSystemService(NotificationManager.class);
+        nm.notify(Utils.CALL_NOTIFICATION_ID, createCallStyleNotification(context));
+    }
+
+    public static void updateCallStyleNotification_toOngoingCall(Context context) {
+        PendingIntent ongoingCall = PendingIntent.getActivity(context, 0,
+                new Intent(""), PendingIntent.FLAG_IMMUTABLE);
+
+        Notification callStyleNotification = new Notification.Builder(context,
+                CHANNEL_ID)
+                .setContentText("active call in the TransactionalTestApp")
+                .setContentTitle("Ongoing call")
+                .setSmallIcon(R.drawable.ic_android_black_24dp)
+                .setStyle(Notification.CallStyle.forOngoingCall(
+                        new Person.Builder().setName(CALLER_NAME).setImportant(true).build(),
+                        ongoingCall)
+                )
+                .setFullScreenIntent(ongoingCall, true)
+                .setOngoing(true)
+                .build();
+
+        NotificationManager notificationManager =
+                context.getSystemService(NotificationManager.class);
+
+        notificationManager.notify(CALL_NOTIFICATION_ID, callStyleNotification);
+    }
+
+    public static void clearNotification(Context context) {
+        NotificationManager notificationManager =
+                context.getSystemService(NotificationManager.class);
+        if (notificationManager != null) {
+            notificationManager.cancel(CALL_NOTIFICATION_ID);
+        }
+    }
+
+    public static MediaPlayer createMediaPlayer(Context context) {
+        int audioToPlay = (Math.random() > 0.5f) ?
+                com.android.server.telecom.transactionalVoipApp.R.raw.sample_audio :
+                com.android.server.telecom.transactionalVoipApp.R.raw.sample_audio2;
+        MediaPlayer mediaPlayer = MediaPlayer.create(context, audioToPlay);
+        mediaPlayer.setLooping(true);
+        return mediaPlayer;
+    }
+
+    public static AudioRecord createAudioRecord() {
+        return new AudioRecord.Builder()
+                .setAudioFormat(new AudioFormat.Builder()
+                        .setSampleRate(SAMPLING_RATE_HZ)
+                        .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+                        .setChannelMask(AudioFormat.CHANNEL_IN_MONO).build())
+                .setAudioSource(MediaRecorder.AudioSource.DEFAULT)
+                .setBufferSizeInBytes(
+                        AudioRecord.getMinBufferSize(SAMPLING_RATE_HZ,
+                                AudioFormat.CHANNEL_IN_MONO,
+                                AudioFormat.ENCODING_PCM_16BIT) * 10)
+                .build();
+    }
+
+
+    public static AudioManager.AudioRecordingCallback getAudioRecordingCallback() {
+        return new AudioManager.AudioRecordingCallback() {
+            @Override
+            public void onRecordingConfigChanged(List<AudioRecordingConfiguration> configs) {
+                super.onRecordingConfigChanged(configs);
+
+                for (AudioRecordingConfiguration config : configs) {
+                    if (config != null) {
+                        Log.i(TAG, String.format("onRecordingConfigChanged: random: "
+                                        + "isClientSilenced=[%b], config=[%s]",
+                                config.isClientSilenced(), config));
+                    }
+                }
+            }
+        };
+    }
+
+    public static OutcomeReceiver<Void, CallException> getLoggableOutcomeReceiver(String tag) {
+        return new OutcomeReceiver<Void, CallException>() {
+            @Override
+            public void onResult(Void result) {
+                Log.i(TAG, tag + " : onResult");
+            }
+
+            @Override
+            public void onError(CallException exception) {
+                Log.i(TAG, tag + " : onError");
+            }
+        };
+    }
+}
\ No newline at end of file
diff --git a/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/VoipAppMainActivity.java b/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/VoipAppMainActivity.java
index 4e1ec4c..72a3906 100644
--- a/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/VoipAppMainActivity.java
+++ b/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/VoipAppMainActivity.java
@@ -20,7 +20,12 @@
 import static android.telecom.CallAttributes.DIRECTION_OUTGOING;
 
 import android.app.Activity;
-import android.content.ComponentName;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.content.Intent;
+import android.media.AudioManager;
+import android.media.AudioRecord;
+import android.media.MediaPlayer;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.OutcomeReceiver;
@@ -28,142 +33,114 @@
 import android.telecom.CallControl;
 import android.telecom.CallException;
 import android.telecom.DisconnectCause;
-import android.telecom.PhoneAccount;
-import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
 import android.util.Log;
 import android.view.View;
 import android.widget.ToggleButton;
 
 public class VoipAppMainActivity extends Activity {
-
     private static final String TAG = "VoipAppMainActivity";
+    private static final String ACT_STATE_TAG = "VoipActivityState";
     private static TelecomManager mTelecomManager;
-    private MyVoipCall mCall1;
-    private MyVoipCall mCall2;
-    private ToggleButton mCallDirectionButton;
-
-    PhoneAccountHandle handle = new PhoneAccountHandle(
-            new ComponentName("com.android.server.telecom.transactionalVoipApp",
-                    "com.android.server.telecom.transactionalVoipApp.VoipAppMainActivity"), "123");
-
-    PhoneAccount mPhoneAccount = PhoneAccount.builder(handle, "test label")
-            .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED |
-                    PhoneAccount.CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS).build();
+    NotificationManager mNotificationManager;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
+        Log.i(TAG, ACT_STATE_TAG + "onCreate");
         super.onCreate(savedInstanceState);
         setContentView(R.layout.main_activity);
 
         mTelecomManager = getSystemService(TelecomManager.class);
-        mCallDirectionButton = findViewById(R.id.callDirectionButton);
+        mNotificationManager = getSystemService(NotificationManager.class);
+        // create a notification channel
+        if (mNotificationManager != null) {
+            mNotificationManager.createNotificationChannel(new NotificationChannel(
+                    Utils.CHANNEL_ID, "new call channel",
+                    NotificationManager.IMPORTANCE_DEFAULT));
+        }
 
         // register account
         findViewById(R.id.registerButton).setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View v) {
-                mTelecomManager.registerPhoneAccount(mPhoneAccount);
+                mTelecomManager.registerPhoneAccount(Utils.PHONE_ACCOUNT);
             }
         });
 
-        // call 1 buttons
-        findViewById(R.id.add_call_1_button).setOnClickListener(new View.OnClickListener() {
+        // Start a foreground service that will post a notification within 10 seconds.
+        // This is helpful for debugging scenarios where the app is in the background and posting
+        // an incoming call notification.
+        findViewById(R.id.startForegroundService).setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View v) {
-                Bundle extras = new Bundle();
-                extras.putString("testKey", "testValue");
-                mCall1 = new MyVoipCall("1");
-                addCall(mCall1, true);
+                Intent startForegroundService = new Intent(getApplicationContext(),
+                        BackgroundIncomingCallService.class);
+                getApplicationContext().startForegroundService(startForegroundService);
             }
         });
 
-        findViewById(R.id.disconnect_call_1_button).setOnClickListener(new View.OnClickListener() {
+
+        // post a new call notification and start an InCall activity
+        findViewById(R.id.startOutgoingCall).setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View v) {
-                disconnectCall(mCall1);
+                startInCallActivity(DIRECTION_OUTGOING);
             }
         });
 
-
-        //call 2 buttons
-        findViewById(R.id.add_call_2_button).setOnClickListener(new View.OnClickListener() {
+        // post a new call notification and start an InCall activity
+        findViewById(R.id.startIncomingCall).setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View v) {
-                Bundle extras = new Bundle();
-                extras.putString("call2extraKey", "call2Value");
-                mCall2 = new MyVoipCall("2");
-                addCall(mCall2, false);
+                startInCallActivity(DIRECTION_INCOMING);
             }
         });
 
-        findViewById(R.id.set_call_2_active_button).setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                setCallActive(mCall2);
-            }
-        });
-
-        findViewById(R.id.disconnect_call_2_button).setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                disconnectCall(mCall2);
-            }
-        });
     }
 
-    private void addCall(MyVoipCall call, boolean setActive) {
-        int direction = (mCallDirectionButton.isChecked() ? DIRECTION_INCOMING
-                : DIRECTION_OUTGOING);
-
-        CallAttributes callAttributes = new CallAttributes.Builder(handle, direction, "Alan Turing",
-                Uri.fromParts("tel", "abc", "123")).build();
-
-        mTelecomManager.addCall(callAttributes, Runnable::run,
-                new OutcomeReceiver<CallControl, CallException>() {
-                    @Override
-                    public void onResult(CallControl callControl) {
-                        Log.i(TAG, "addCall: onResult: callback fired");
-                        call.onAddCallControl(callControl);
-                        if (setActive) {
-                            setCallActive(call);
-                        }
-                    }
-
-                    @Override
-                    public void onError(CallException exception) {
-
-                    }
-                },
-                call, call);
+    private void startInCallActivity(int direction) {
+        Bundle extras = new Bundle();
+        extras.putInt(Utils.sCALL_DIRECTION_KEY, direction);
+        Intent intent = new Intent(getApplicationContext(), InCallActivity.class);
+        intent.putExtra(Utils.sEXTRAS_KEY, extras);
+        startActivity(intent);
     }
 
-    private void setCallActive(MyVoipCall call) {
-        call.mCallControl.setActive(Runnable::run, new OutcomeReceiver<Void, CallException>() {
-            @Override
-            public void onResult(Void result) {
-                Log.i(TAG, "setCallActive: onResult");
-            }
-
-            @Override
-            public void onError(CallException exception) {
-                Log.i(TAG, "setCallActive: onError");
-            }
-        });
+    @Override
+    protected void onResume() {
+        Log.i(TAG, ACT_STATE_TAG + " onResume: When the activity enters the Resumed state,"
+                + " it comes to the foreground");
+        super.onResume();
     }
 
-    private void disconnectCall(MyVoipCall call) {
-        call.mCallControl.disconnect(new DisconnectCause(DisconnectCause.LOCAL), Runnable::run,
-                new OutcomeReceiver<Void, CallException>() {
-                    @Override
-                    public void onResult(Void result) {
-                        Log.i(TAG, "disconnectCall: onResult");
-                    }
+    @Override
+    protected void onPause() {
+        Log.i(TAG, ACT_STATE_TAG + " onPause: The system calls this method as the first"
+                + " indication that the user is leaving your activity.  It indicates that the"
+                + " activity is no longer in the foreground, but it is still visible if the user"
+                + " is in multi-window mode");
+        super.onPause();
+    }
 
-                    @Override
-                    public void onError(CallException exception) {
-                        Log.i(TAG, "disconnectCall: onError");
-                    }
-                });
+    @Override
+    protected void onStop() {
+        Log.i(TAG, ACT_STATE_TAG + "onStop: When your activity is no longer visible to"
+                + " the user, it enters the Stopped state,");
+        super.onStop();
+    }
+
+    @Override
+    protected void onRestart() {
+        Log.i(TAG, ACT_STATE_TAG + " onRestart: onStop has called onRestart and the "
+                + "activity comes back to interact with the user");
+        super.onRestart();
+    }
+
+    @Override
+    protected void onDestroy() {
+        Log.i(TAG, ACT_STATE_TAG + " onDestroy: is called before the activity is"
+                + " destroyed. ");
+        Utils.clearNotification(getApplicationContext());
+        super.onDestroy();
     }
 }
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index c84db3b..4ca6030 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -44,6 +44,9 @@
     <!-- Used to access PlatformCompat APIs -->
     <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG" />
     <uses-permission android:name="android.permission.LOG_COMPAT_CHANGE" />
+    
+    <!-- Used to register NotificationListenerService -->
+    <uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
 
     <application android:label="@string/app_name"
                  android:debuggable="true">
diff --git a/tests/src/com/android/server/telecom/tests/AnalyticsTests.java b/tests/src/com/android/server/telecom/tests/AnalyticsTests.java
index 5da7f31..54aedc4 100644
--- a/tests/src/com/android/server/telecom/tests/AnalyticsTests.java
+++ b/tests/src/com/android/server/telecom/tests/AnalyticsTests.java
@@ -381,7 +381,7 @@
 
         waitForHandlerAction(
                 mTelecomSystem.getCallsManager().getCallAudioManager()
-                        .getCallAudioRouteStateMachine().getHandler(),
+                        .getCallAudioRouteAdapter().getAdapterHandler(),
                 TEST_TIMEOUT);
         waitForHandlerAction(
                 mTelecomSystem.getCallsManager().getCallAudioManager()
@@ -391,7 +391,7 @@
         mInCallServiceFixtureX.getInCallAdapter().setAudioRoute(CallAudioState.ROUTE_SPEAKER, null);
         waitForHandlerAction(
                 mTelecomSystem.getCallsManager().getCallAudioManager()
-                        .getCallAudioRouteStateMachine().getHandler(),
+                        .getCallAudioRouteAdapter().getAdapterHandler(),
                 TEST_TIMEOUT);
         waitForHandlerAction(
                 mTelecomSystem.getCallsManager().getCallAudioManager()
diff --git a/tests/src/com/android/server/telecom/tests/BasicCallTests.java b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
index 453450d..d2937e2 100644
--- a/tests/src/com/android/server/telecom/tests/BasicCallTests.java
+++ b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
@@ -16,8 +16,11 @@
 
 package com.android.server.telecom.tests;
 
+import static com.android.server.telecom.tests.ConnectionServiceFixture.STATUS_HINTS_EXTRA;
+
 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.nullable;
@@ -36,7 +39,9 @@
 
 import android.content.Context;
 import android.content.IContentProvider;
+import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.graphics.drawable.Icon;
 import android.media.AudioDeviceInfo;
 import android.media.AudioManager;
 import android.net.Uri;
@@ -54,14 +59,18 @@
 import android.telecom.ParcelableCall;
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
+import android.telecom.StatusHints;
 import android.telecom.TelecomManager;
 import android.telecom.VideoProfile;
 import android.test.suitebuilder.annotation.LargeTest;
 import android.test.suitebuilder.annotation.MediumTest;
 
 import androidx.test.filters.FlakyTest;
+import androidx.test.filters.SmallTest;
 
 import com.android.internal.telecom.IInCallAdapter;
+import com.android.server.telecom.InCallController;
+
 import android.telecom.CallerInfo;
 
 import com.google.common.base.Predicate;
@@ -100,6 +109,7 @@
         doReturn(mContext).when(mContext).createContextAsUser(any(UserHandle.class), anyInt());
         mPackageManager = mContext.getPackageManager();
         when(mPackageManager.getPackageUid(anyString(), eq(0))).thenReturn(Binder.getCallingUid());
+        when(mFeatureFlags.telecomResolveHiddenDependencies()).thenReturn(false);
     }
 
     @Override
@@ -195,7 +205,7 @@
     @Test
     public void testTelecomManagerAcceptRingingVideoCall() throws Exception {
         IdPair ids = startIncomingPhoneCall("650-555-1212", mPhoneAccountA0.getAccountHandle(),
-                VideoProfile.STATE_BIDIRECTIONAL, mConnectionServiceFixtureA);
+                VideoProfile.STATE_BIDIRECTIONAL, mConnectionServiceFixtureA, null);
 
         assertEquals(Call.STATE_RINGING, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
         assertEquals(Call.STATE_RINGING, mInCallServiceFixtureY.getCall(ids.mCallId).getState());
@@ -224,7 +234,7 @@
     @Test
     public void testTelecomManagerAcceptRingingVideoCallAsAudio() throws Exception {
         IdPair ids = startIncomingPhoneCall("650-555-1212", mPhoneAccountA0.getAccountHandle(),
-                VideoProfile.STATE_BIDIRECTIONAL, mConnectionServiceFixtureA);
+                VideoProfile.STATE_BIDIRECTIONAL, mConnectionServiceFixtureA, null);
 
         assertEquals(Call.STATE_RINGING, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
         assertEquals(Call.STATE_RINGING, mInCallServiceFixtureY.getCall(ids.mCallId).getState());
@@ -255,7 +265,7 @@
     @Test
     public void testTelecomManagerAcceptRingingInvalidVideoState() throws Exception {
         IdPair ids = startIncomingPhoneCall("650-555-1212", mPhoneAccountA0.getAccountHandle(),
-                VideoProfile.STATE_BIDIRECTIONAL, mConnectionServiceFixtureA);
+                VideoProfile.STATE_BIDIRECTIONAL, mConnectionServiceFixtureA, null);
 
         assertEquals(Call.STATE_RINGING, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
         assertEquals(Call.STATE_RINGING, mInCallServiceFixtureY.getCall(ids.mCallId).getState());
@@ -616,6 +626,48 @@
 
     @LargeTest
     @Test
+    public void testIncomingThenOutgoingCalls_AssociatedUsersNotEqual() throws Exception {
+        when(mFeatureFlags.workProfileAssociatedUser()).thenReturn(true);
+        InCallServiceFixture.setIgnoreOverrideAdapterFlag(true);
+
+        // Receive incoming call via mPhoneAccountMultiUser
+        IdPair incoming = startAndMakeActiveIncomingCall("650-555-2323",
+                mPhoneAccountMultiUser.getAccountHandle(), mConnectionServiceFixtureA);
+        waitForHandlerAction(mConnectionServiceFixtureA.mConnectionServiceDelegate.getHandler(),
+                TEST_TIMEOUT);
+        // Make outgoing call on mPhoneAccountMultiUser (unassociated sim to simulate guest/
+        // secondary user scenario where both MO/MT calls exist).
+        IdPair outgoing = startAndMakeActiveOutgoingCall("650-555-1212",
+                mPhoneAccountMultiUser.getAccountHandle(), mConnectionServiceFixtureA);
+        waitForHandlerAction(mConnectionServiceFixtureA.mConnectionServiceDelegate.getHandler(),
+                TEST_TIMEOUT);
+
+        // Outgoing call should be on hold while incoming call is made active
+        mConnectionServiceFixtureA.mConnectionById.get(incoming.mConnectionId).state =
+                Connection.STATE_HOLDING;
+
+        // Swap calls and verify that outgoing call is now the active call while the incoming call
+        // is the held call.
+        mConnectionServiceFixtureA.sendSetOnHold(outgoing.mConnectionId);
+        waitForHandlerAction(mConnectionServiceFixtureA.mConnectionServiceDelegate.getHandler(),
+                TEST_TIMEOUT);
+        assertEquals(Call.STATE_HOLDING,
+                mInCallServiceFixtureX.getCall(outgoing.mCallId).getState());
+        assertEquals(Call.STATE_ACTIVE,
+                mInCallServiceFixtureX.getCall(incoming.mCallId).getState());
+
+        // Ensure no issues with call disconnect.
+        mInCallServiceFixtureX.mInCallAdapter.disconnectCall(incoming.mCallId);
+        mInCallServiceFixtureX.mInCallAdapter.disconnectCall(outgoing.mCallId);
+        assertEquals(Call.STATE_DISCONNECTING,
+                mInCallServiceFixtureX.getCall(incoming.mCallId).getState());
+        assertEquals(Call.STATE_DISCONNECTING,
+                mInCallServiceFixtureX.getCall(outgoing.mCallId).getState());
+        InCallServiceFixture.setIgnoreOverrideAdapterFlag(false);
+    }
+
+    @LargeTest
+    @Test
     public void testAudioManagerOperations() throws Exception {
         AudioManager audioManager = (AudioManager) mComponentContextFixture.getTestDouble()
                 .getApplicationContext().getSystemService(Context.AUDIO_SERVICE);
@@ -638,15 +690,15 @@
 
         mInCallServiceFixtureX.mInCallAdapter.setAudioRoute(CallAudioState.ROUTE_SPEAKER, null);
         waitForHandlerAction(mTelecomSystem.getCallsManager().getCallAudioManager()
-                .getCallAudioRouteStateMachine().getHandler(), TEST_TIMEOUT);
+                .getCallAudioRouteAdapter().getAdapterHandler(), TEST_TIMEOUT);
         ArgumentCaptor<AudioDeviceInfo> infoArgumentCaptor =
                 ArgumentCaptor.forClass(AudioDeviceInfo.class);
-        verify(audioManager, timeout(TEST_TIMEOUT)).setCommunicationDevice(
-                infoArgumentCaptor.capture());
+        verify(audioManager, timeout(TEST_TIMEOUT).atLeast(1))
+                .setCommunicationDevice(infoArgumentCaptor.capture());
         assertEquals(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, infoArgumentCaptor.getValue().getType());
         mInCallServiceFixtureX.mInCallAdapter.setAudioRoute(CallAudioState.ROUTE_EARPIECE, null);
         waitForHandlerAction(mTelecomSystem.getCallsManager().getCallAudioManager()
-                .getCallAudioRouteStateMachine().getHandler(), TEST_TIMEOUT);
+                .getCallAudioRouteAdapter().getAdapterHandler(), TEST_TIMEOUT);
         // setSpeakerPhoneOn(false) gets called once during the call initiation phase
         verify(audioManager, timeout(TEST_TIMEOUT).atLeast(1))
                 .clearCommunicationDevice();
@@ -657,7 +709,7 @@
         waitForHandlerAction(mTelecomSystem.getCallsManager().getCallAudioManager()
                 .getCallAudioModeStateMachine().getHandler(), TEST_TIMEOUT);
         waitForHandlerAction(mTelecomSystem.getCallsManager().getCallAudioManager()
-                .getCallAudioRouteStateMachine().getHandler(), TEST_TIMEOUT);
+                .getCallAudioRouteAdapter().getAdapterHandler(), TEST_TIMEOUT);
         verify(audioManager, timeout(TEST_TIMEOUT))
                 .abandonAudioFocusForCall();
         verify(audioManager, timeout(TEST_TIMEOUT).atLeastOnce())
@@ -693,13 +745,13 @@
     @MediumTest
     @Test
     public void testBasicConferenceCall() throws Exception {
-        makeConferenceCall();
+        makeConferenceCall(null, null);
     }
 
     @MediumTest
     @Test
     public void testAddCallToConference1() throws Exception {
-        ParcelableCall conferenceCall = makeConferenceCall();
+        ParcelableCall conferenceCall = makeConferenceCall(null, null);
         IdPair callId3 = startAndMakeActiveOutgoingCall("650-555-1214",
                 mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
         // testAddCallToConference{1,2} differ in the order of arguments to InCallAdapter#conference
@@ -717,7 +769,7 @@
     @MediumTest
     @Test
     public void testAddCallToConference2() throws Exception {
-        ParcelableCall conferenceCall = makeConferenceCall();
+        ParcelableCall conferenceCall = makeConferenceCall(null, null);
         IdPair callId3 = startAndMakeActiveOutgoingCall("650-555-1214",
                 mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
         mInCallServiceFixtureX.getInCallAdapter()
@@ -975,7 +1027,7 @@
     public void testOutgoingCallSelectPhoneAccountVideo() throws Exception {
         startOutgoingPhoneCallPendingCreateConnection("650-555-1212",
                 null, mConnectionServiceFixtureA,
-                Process.myUserHandle(), VideoProfile.STATE_BIDIRECTIONAL);
+                Process.myUserHandle(), VideoProfile.STATE_BIDIRECTIONAL, null);
         com.android.server.telecom.Call call = mTelecomSystem.getCallsManager().getCalls()
                 .iterator().next();
         assert(call.isVideoCallingSupportedByPhoneAccount());
@@ -985,6 +1037,7 @@
         call.setTargetPhoneAccount(mPhoneAccountA1.getAccountHandle());
         assert(call.isVideoCallingSupportedByPhoneAccount());
         assertEquals(VideoProfile.STATE_BIDIRECTIONAL, call.getVideoState());
+        call.setIsCreateConnectionComplete(true);
     }
 
     /**
@@ -998,7 +1051,7 @@
     public void testOutgoingCallSelectPhoneAccountNoVideo() throws Exception {
         startOutgoingPhoneCallPendingCreateConnection("650-555-1212",
                 null, mConnectionServiceFixtureA,
-                Process.myUserHandle(), VideoProfile.STATE_BIDIRECTIONAL);
+                Process.myUserHandle(), VideoProfile.STATE_BIDIRECTIONAL, null);
         com.android.server.telecom.Call call = mTelecomSystem.getCallsManager().getCalls()
                 .iterator().next();
         assert(call.isVideoCallingSupportedByPhoneAccount());
@@ -1008,6 +1061,7 @@
         call.setTargetPhoneAccount(mPhoneAccountA2.getAccountHandle());
         assert(!call.isVideoCallingSupportedByPhoneAccount());
         assertEquals(VideoProfile.STATE_AUDIO_ONLY, call.getVideoState());
+        call.setIsCreateConnectionComplete(true);
     }
 
     /**
@@ -1185,7 +1239,7 @@
                 .getState());
         mInCallServiceFixtureX.mInCallAdapter.mute(true);
         waitForHandlerAction(mTelecomSystem.getCallsManager().getCallAudioManager()
-                .getCallAudioRouteStateMachine().getHandler(), TEST_TIMEOUT);
+                .getCallAudioRouteAdapter().getAdapterHandler(), TEST_TIMEOUT);
         assertTrue(mTelecomSystem.getCallsManager().getAudioState().isMuted());
 
         // Make an emergency call.
@@ -1194,14 +1248,14 @@
         assertEquals(Call.STATE_DIALING, mInCallServiceFixtureX.getCall(emergencyCall.mCallId)
                 .getState());
         waitForHandlerAction(mTelecomSystem.getCallsManager().getCallAudioManager()
-                .getCallAudioRouteStateMachine().getHandler(), TEST_TIMEOUT);
+                .getCallAudioRouteAdapter().getAdapterHandler(), TEST_TIMEOUT);
         // Should be unmute automatically.
         assertFalse(mTelecomSystem.getCallsManager().getAudioState().isMuted());
 
         // Toggle mute during an emergency call.
         mTelecomSystem.getCallsManager().getCallAudioManager().toggleMute();
         waitForHandlerAction(mTelecomSystem.getCallsManager().getCallAudioManager()
-                .getCallAudioRouteStateMachine().getHandler(), TEST_TIMEOUT);
+                .getCallAudioRouteAdapter().getAdapterHandler(), TEST_TIMEOUT);
         // Should keep unmute.
         assertFalse(mTelecomSystem.getCallsManager().getAudioState().isMuted());
 
@@ -1213,4 +1267,144 @@
         assertTrue(muteValues.get(0));
         assertFalse(muteValues.get(1));
     }
+
+    /**
+     * Verifies that StatusHints image is validated in ConnectionServiceWrapper#addConferenceCall
+     * when the image doesn't belong to the calling user. Simulates a scenario where an app
+     * could manipulate the contents of the bundle and send it via the binder to upload an image
+     * from another user.
+     *
+     * @throws Exception
+     */
+    @SmallTest
+    @Test
+    public void testValidateStatusHintsImage_addConferenceCall() throws Exception {
+        Intent callIntent1 = new Intent();
+        // Stub intent for call2
+        Intent callIntent2 = new Intent();
+        Bundle callExtras1 = new Bundle();
+        Icon icon = Icon.createWithContentUri("content://10@media/external/images/media/");
+        // Load StatusHints extra into TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS to be processed
+        // as the call extras. This will be leveraged in ConnectionServiceFixture to set the
+        // StatusHints for the given connection.
+        StatusHints statusHints = new StatusHints(icon);
+        assertNotNull(statusHints.getIcon());
+        callExtras1.putParcelable(STATUS_HINTS_EXTRA, statusHints);
+        callIntent1.putExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, callExtras1);
+
+        // Start conference call to invoke ConnectionServiceWrapper#addConferenceCall.
+        // Note that the calling user would be User 0.
+        ParcelableCall conferenceCall = makeConferenceCall(callIntent1, callIntent2);
+
+        // Ensure that StatusHints was set.
+        assertNotNull(mInCallServiceFixtureX.getCall(mInCallServiceFixtureX.mLatestCallId)
+                .getStatusHints());
+        // Ensure that the StatusHints image icon was disregarded.
+        assertNull(mInCallServiceFixtureX.getCall(mInCallServiceFixtureX.mLatestCallId)
+                .getStatusHints().getIcon());
+    }
+
+    /**
+     * Verifies that StatusHints image is validated in
+     * ConnectionServiceWrapper#handleCreateConnectionComplete when the image doesn't belong to the
+     * calling user. Simulates a scenario where an app could manipulate the contents of the
+     * bundle and send it via the binder to upload an image from another user.
+     *
+     * @throws Exception
+     */
+    @SmallTest
+    @Test
+    public void testValidateStatusHintsImage_handleCreateConnectionComplete() throws Exception {
+        Bundle extras = new Bundle();
+        Icon icon = Icon.createWithContentUri("content://10@media/external/images/media/");
+        // Load the bundle with the test extra in order to simulate an app directly invoking the
+        // binder on ConnectionServiceWrapper#handleCreateConnectionComplete.
+        StatusHints statusHints = new StatusHints(icon);
+        assertNotNull(statusHints.getIcon());
+        extras.putParcelable(STATUS_HINTS_EXTRA, statusHints);
+
+        // Start incoming call with StatusHints extras
+        // Note that the calling user in ConnectionServiceWrapper#handleCreateConnectionComplete
+        // would be User 0.
+        IdPair ids = startIncomingPhoneCallWithExtras("650-555-1212",
+                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA, extras);
+
+        // Ensure that StatusHints was set.
+        assertNotNull(mInCallServiceFixtureX.getCall(ids.mCallId).getStatusHints());
+        // Ensure that the StatusHints image icon was disregarded.
+        assertNull(mInCallServiceFixtureX.getCall(ids.mCallId).getStatusHints().getIcon());
+    }
+
+    /**
+     * Verifies that StatusHints image is validated in ConnectionServiceWrapper#setStatusHints
+     * when the image doesn't belong to the calling user. Simulates a scenario where an app
+     * could manipulate the contents of the bundle and send it via the binder to upload an image
+     * from another user.
+     *
+     * @throws Exception
+     */
+    @SmallTest
+    @Test
+    public void testValidateStatusHintsImage_setStatusHints() throws Exception {
+        IdPair outgoing = startAndMakeActiveOutgoingCall("650-555-1214",
+                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
+
+        // Modify existing connection with StatusHints image exploit
+        Icon icon = Icon.createWithContentUri("content://10@media/external/images/media/");
+        StatusHints statusHints = new StatusHints(icon);
+        assertNotNull(statusHints.getIcon());
+        ConnectionServiceFixture.ConnectionInfo connectionInfo = mConnectionServiceFixtureA
+                .mConnectionById.get(outgoing.mConnectionId);
+        connectionInfo.statusHints = statusHints;
+
+        // Invoke ConnectionServiceWrapper#setStatusHints.
+        // Note that the calling user would be User 0.
+        mConnectionServiceFixtureA.sendSetStatusHints(outgoing.mConnectionId);
+        waitForHandlerAction(mConnectionServiceFixtureA.mConnectionServiceDelegate.getHandler(),
+                TEST_TIMEOUT);
+
+        // Ensure that StatusHints was set.
+        assertNotNull(mInCallServiceFixtureX.getCall(outgoing.mCallId).getStatusHints());
+        // Ensure that the StatusHints image icon was disregarded.
+        assertNull(mInCallServiceFixtureX.getCall(outgoing.mCallId)
+                .getStatusHints().getIcon());
+    }
+
+    /**
+     * Verifies that StatusHints image is validated in
+     * ConnectionServiceWrapper#addExistingConnection when the image doesn't belong to the calling
+     * user. Simulates a scenario where an app could manipulate the contents of the bundle and
+     * send it via the binder to upload an image from another user.
+     *
+     * @throws Exception
+     */
+    @SmallTest
+    @Test
+    public void testValidateStatusHintsImage_addExistingConnection() throws Exception {
+        IdPair outgoing = startAndMakeActiveOutgoingCall("650-555-1214",
+                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
+
+        // Modify existing connection with StatusHints image exploit
+        Icon icon = Icon.createWithContentUri("content://10@media/external/images/media/");
+        StatusHints modifiedStatusHints = new StatusHints(icon);
+        assertNotNull(modifiedStatusHints.getIcon());
+        ConnectionServiceFixture.ConnectionInfo connectionInfo = mConnectionServiceFixtureA
+                .mConnectionById.get(outgoing.mConnectionId);
+        connectionInfo.statusHints = modifiedStatusHints;
+
+        // Invoke ConnectionServiceWrapper#addExistingConnection.
+        // Note that the calling user would be User 0.
+        mConnectionServiceFixtureA.sendAddExistingConnection(outgoing.mConnectionId);
+        waitForHandlerAction(mConnectionServiceFixtureA.mConnectionServiceDelegate.getHandler(),
+                TEST_TIMEOUT);
+
+        // Ensure that StatusHints was set. Due to test setup, the ParcelableConnection object that
+        // is passed into sendAddExistingConnection is instantiated on invocation. The call's
+        // StatusHints are not updated at the time of completion, so instead, we can verify that
+        // the ParcelableConnection object was modified.
+        assertNotNull(mConnectionServiceFixtureA.mLatestParcelableConnection.getStatusHints());
+        // Ensure that the StatusHints image icon was disregarded.
+        assertNull(mConnectionServiceFixtureA.mLatestParcelableConnection
+                .getStatusHints().getIcon());
+    }
 }
diff --git a/tests/src/com/android/server/telecom/tests/BlockCheckerFilterTest.java b/tests/src/com/android/server/telecom/tests/BlockCheckerFilterTest.java
index 7df4f29..a98c1ee 100644
--- a/tests/src/com/android/server/telecom/tests/BlockCheckerFilterTest.java
+++ b/tests/src/com/android/server/telecom/tests/BlockCheckerFilterTest.java
@@ -72,6 +72,7 @@
     private static final CallFilteringResult BLOCK_RESULT = new CallFilteringResult.Builder()
             .setShouldAllowCall(false)
             .setShouldReject(true)
+            .setShouldSilence(true)
             .setShouldAddToCallLog(true)
             .setShouldShowNotification(false)
             .setCallBlockReason(CallLog.Calls.BLOCK_REASON_BLOCKED_NUMBER)
diff --git a/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java b/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java
index c37d136..e3d4ec2 100644
--- a/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java
@@ -24,13 +24,14 @@
 import android.bluetooth.BluetoothHearingAid;
 import android.bluetooth.BluetoothLeAudio;
 import android.bluetooth.BluetoothProfile;
-import android.content.BroadcastReceiver;
 import android.content.Intent;
 import android.media.AudioDeviceInfo;
 import android.media.AudioManager;
+import android.os.Bundle;
 import android.os.Parcel;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import com.android.server.telecom.CallAudioCommunicationDeviceTracker;
 import com.android.server.telecom.bluetooth.BluetoothDeviceManager;
 import com.android.server.telecom.bluetooth.BluetoothRouteManager;
 import com.android.server.telecom.bluetooth.BluetoothStateReceiver;
@@ -51,6 +52,7 @@
 import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
@@ -76,6 +78,7 @@
     BluetoothDeviceManager mBluetoothDeviceManager;
     BluetoothProfile.ServiceListener serviceListenerUnderTest;
     BluetoothStateReceiver receiverUnderTest;
+    CallAudioCommunicationDeviceTracker mCommunicationDeviceTracker;
     ArgumentCaptor<BluetoothLeAudio.Callback> leAudioCallbacksTest;
 
     private BluetoothDevice device1;
@@ -103,8 +106,11 @@
         when(mBluetoothHearingAid.getHiSyncId(device4)).thenReturn(100L);
 
         mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
-        mBluetoothDeviceManager = new BluetoothDeviceManager(mContext, mAdapter);
+        mCommunicationDeviceTracker = new CallAudioCommunicationDeviceTracker(mContext);
+        mBluetoothDeviceManager = new BluetoothDeviceManager(mContext, mAdapter,
+                mCommunicationDeviceTracker, mFeatureFlags);
         mBluetoothDeviceManager.setBluetoothRouteManager(mRouteManager);
+        mCommunicationDeviceTracker.setBluetoothRouteManager(mRouteManager);
 
         mockAudioManager = mContext.getSystemService(AudioManager.class);
 
@@ -114,7 +120,8 @@
                 serviceCaptor.capture(), eq(BluetoothProfile.HEADSET));
         serviceListenerUnderTest = serviceCaptor.getValue();
 
-        receiverUnderTest = new BluetoothStateReceiver(mBluetoothDeviceManager, mRouteManager);
+        receiverUnderTest = new BluetoothStateReceiver(mBluetoothDeviceManager,
+                mRouteManager, mCommunicationDeviceTracker, mFeatureFlags);
 
         mBluetoothDeviceManager.setHeadsetServiceForTesting(mBluetoothHeadset);
         mBluetoothDeviceManager.setHearingAidServiceForTesting(mBluetoothHearingAid);
@@ -125,6 +132,7 @@
         verify(mBluetoothLeAudio).registerCallback(any(), leAudioCallbacksTest.capture());
 
         when(mSpeakerInfo.getType()).thenReturn(TYPE_BUILTIN_SPEAKER);
+        when(mFeatureFlags.callAudioCommunicationDeviceRefactor()).thenReturn(false);
     }
 
     @Override
@@ -178,6 +186,7 @@
                 buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device5,
                         BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
         leAudioCallbacksTest.getValue().onGroupNodeAdded(device5, 1);
+        when(mBluetoothLeAudio.getGroupId(device5)).thenReturn(1);
         when(mBluetoothLeAudio.getConnectedGroupLeadDevice(1)).thenReturn(device5);
 
         receiverUnderTest.onReceive(mContext,
@@ -188,6 +197,7 @@
                         BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
         leAudioCallbacksTest.getValue().onGroupNodeAdded(device6, 2);
         when(mBluetoothLeAudio.getConnectedGroupLeadDevice(2)).thenReturn(device6);
+        when(mBluetoothLeAudio.getGroupId(device6)).thenReturn(1);
         receiverUnderTest.onReceive(mContext,
                 buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device3,
                         BluetoothDeviceManager.DEVICE_TYPE_HEADSET));
@@ -263,17 +273,19 @@
     @Test
     public void testLeAudioDedup() {
         receiverUnderTest.onReceive(mContext,
-                buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device1,
+                buildConnectionActionIntent(BluetoothProfile.STATE_CONNECTED, device1,
                         BluetoothDeviceManager.DEVICE_TYPE_HEADSET));
         receiverUnderTest.onReceive(mContext,
-                buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device5,
+                buildConnectionActionIntent(BluetoothProfile.STATE_CONNECTED, device5,
                         BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
         leAudioCallbacksTest.getValue().onGroupNodeAdded(device5, 1);
         receiverUnderTest.onReceive(mContext,
-                buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device6,
+                buildConnectionActionIntent(BluetoothProfile.STATE_CONNECTED, device6,
                         BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
         leAudioCallbacksTest.getValue().onGroupNodeAdded(device6, 1);
         when(mBluetoothLeAudio.getConnectedGroupLeadDevice(1)).thenReturn(device5);
+        when(mBluetoothLeAudio.getGroupId(device5)).thenReturn(1);
+        when(mBluetoothLeAudio.getGroupId(device6)).thenReturn(1);
         assertEquals(2, mBluetoothDeviceManager.getNumConnectedDevices());
         assertEquals(2, mBluetoothDeviceManager.getUniqueConnectedDevices().size());
     }
@@ -408,8 +420,8 @@
         when(mAdapter.setActiveDevice(nullable(BluetoothDevice.class),
                 eq(BluetoothAdapter.ACTIVE_DEVICE_ALL))).thenReturn(true);
 
-        AudioDeviceInfo mockAudioDeviceInfo = mock(AudioDeviceInfo.class);
-        when(mockAudioDeviceInfo.getType()).thenReturn(AudioDeviceInfo.TYPE_HEARING_AID);
+        AudioDeviceInfo mockAudioDeviceInfo = createMockAudioDeviceInfo(device5.getAddress(),
+                AudioDeviceInfo.TYPE_HEARING_AID);
         List<AudioDeviceInfo> devices = new ArrayList<>();
         devices.add(mockAudioDeviceInfo);
 
@@ -429,7 +441,7 @@
 
         when(mockAudioManager.getCommunicationDevice()).thenReturn(mockAudioDeviceInfo);
         mBluetoothDeviceManager.disconnectAudio();
-        verify(mockAudioManager).clearCommunicationDevice();
+        verify(mockAudioManager, atLeastOnce()).clearCommunicationDevice();
     }
 
     @SmallTest
@@ -443,8 +455,8 @@
         when(mAdapter.setActiveDevice(nullable(BluetoothDevice.class),
                 eq(BluetoothAdapter.ACTIVE_DEVICE_ALL))).thenReturn(true);
 
-        AudioDeviceInfo mockAudioDeviceInfo = mock(AudioDeviceInfo.class);
-        when(mockAudioDeviceInfo.getType()).thenReturn(AudioDeviceInfo.TYPE_BLE_HEADSET);
+        AudioDeviceInfo mockAudioDeviceInfo = createMockAudioDeviceInfo(device5.getAddress(),
+                AudioDeviceInfo.TYPE_BLE_HEADSET);
         List<AudioDeviceInfo> devices = new ArrayList<>();
         devices.add(mockAudioDeviceInfo);
 
@@ -458,12 +470,14 @@
         verify(mBluetoothHeadset, never()).connectAudio();
         verify(mAdapter, never()).setActiveDevice(nullable(BluetoothDevice.class),
                 eq(BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL));
+        verify(mAdapter, never()).setActiveDevice(nullable(BluetoothDevice.class),
+                eq(BluetoothAdapter.ACTIVE_DEVICE_AUDIO));
 
         receiverUnderTest.onReceive(mContext, buildActiveDeviceChangeActionIntent(device5,
                 BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
 
         mBluetoothDeviceManager.disconnectAudio();
-        verify(mockAudioManager).clearCommunicationDevice();
+        verify(mockAudioManager, atLeastOnce()).clearCommunicationDevice();
     }
 
     @SmallTest
@@ -485,6 +499,8 @@
         verify(mBluetoothHeadset, never()).connectAudio();
         verify(mAdapter, never()).setActiveDevice(nullable(BluetoothDevice.class),
                 eq(BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL));
+        verify(mAdapter, never()).setActiveDevice(nullable(BluetoothDevice.class),
+                eq(BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL));
 
         when(mAdapter.getActiveDevices(eq(BluetoothProfile.LE_AUDIO)))
                 .thenReturn(Arrays.asList(device5, device6));
@@ -499,23 +515,219 @@
 
     @SmallTest
     @Test
-    public void testClearHearingAidCommunicationDevice() {
-        AudioDeviceInfo mockAudioDeviceInfo = mock(AudioDeviceInfo.class);
-        when(mockAudioDeviceInfo.getAddress()).thenReturn(DEVICE_ADDRESS_1);
-        when(mockAudioDeviceInfo.getType()).thenReturn(AudioDeviceInfo.TYPE_HEARING_AID);
+    public void testConnectMultipleLeAudioDevices() {
+        when(mFeatureFlags.callAudioCommunicationDeviceRefactor()).thenReturn(true);
+        receiverUnderTest.setIsInCall(true);
+        receiverUnderTest.onReceive(mContext,
+                buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device1,
+                        BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
+        leAudioCallbacksTest.getValue().onGroupNodeAdded(device1, 1);
+        receiverUnderTest.onReceive(mContext,
+                buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device2,
+                        BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
+        leAudioCallbacksTest.getValue().onGroupNodeAdded(device2, 1);
+        when(mAdapter.setActiveDevice(nullable(BluetoothDevice.class),
+                eq(BluetoothAdapter.ACTIVE_DEVICE_ALL))).thenReturn(true);
+
         List<AudioDeviceInfo> devices = new ArrayList<>();
-        devices.add(mockAudioDeviceInfo);
+        AudioDeviceInfo leAudioDevice1 = createMockAudioDeviceInfo(device1.getAddress(),
+                AudioDeviceInfo.TYPE_BLE_HEADSET);
+        AudioDeviceInfo leAudioDevice2 = createMockAudioDeviceInfo(device2.getAddress(),
+                AudioDeviceInfo.TYPE_BLE_HEADSET);
+        devices.add(leAudioDevice1);
+        devices.add(leAudioDevice2);
 
         when(mockAudioManager.getAvailableCommunicationDevices())
                 .thenReturn(devices);
-        when(mockAudioManager.setCommunicationDevice(eq(mockAudioDeviceInfo)))
+        when(mockAudioManager.setCommunicationDevice(any(AudioDeviceInfo.class)))
                 .thenReturn(true);
 
-        mBluetoothDeviceManager.setHearingAidCommunicationDevice();
-        when(mockAudioManager.getCommunicationDevice()).thenReturn(mSpeakerInfo);
-        mBluetoothDeviceManager.clearHearingAidCommunicationDevice();
+        // Connect LE audio device
+        mBluetoothDeviceManager.connectAudio(device1.getAddress(), false);
+        verify(mAdapter).setActiveDevice(device1, BluetoothAdapter.ACTIVE_DEVICE_ALL);
+        verify(mBluetoothHeadset, never()).connectAudio();
+        verify(mAdapter, never()).setActiveDevice(nullable(BluetoothDevice.class),
+                eq(BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL));
+        // Verify that we set the communication device for device 1
+        verify(mockAudioManager).setCommunicationDevice(leAudioDevice1);
+
+        // Change active device to other LE audio device
+        receiverUnderTest.onReceive(mContext, buildActiveDeviceChangeActionIntent(device2,
+                BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
+
+        // Verify call to clearLeAudioCommunicationDevice
         verify(mRouteManager).onAudioLost(eq(DEVICE_ADDRESS_1));
-        assertFalse(mBluetoothDeviceManager.isHearingAidSetAsCommunicationDevice());
+        // Verify that we set the communication device for device2
+        verify(mockAudioManager).setCommunicationDevice(leAudioDevice2);
+    }
+
+    @SmallTest
+    @Test
+    public void testClearCommunicationDeviceOnActiveDeviceChange() {
+        when(mFeatureFlags.callAudioCommunicationDeviceRefactor()).thenReturn(true);
+        receiverUnderTest.setIsInCall(true);
+
+        List<AudioDeviceInfo> devices = new ArrayList<>();
+        AudioDeviceInfo leAudioDevice1 = createMockAudioDeviceInfo(device1.getAddress(),
+                AudioDeviceInfo.TYPE_BLE_HEADSET);
+        devices.add(leAudioDevice1);
+
+        when(mockAudioManager.getAvailableCommunicationDevices())
+                .thenReturn(devices);
+        when(mockAudioManager.setCommunicationDevice(any(AudioDeviceInfo.class)))
+                .thenReturn(true);
+
+        // Pretend that the speaker device is currently the requested device set for communication.
+        // This test ensures that the set/clear communication logic for audio switching in/out of BT
+        // is properly working when the receiver processes an active device change intent.
+        mCommunicationDeviceTracker.setTestCommunicationDevice(TYPE_BUILTIN_SPEAKER);
+
+        // Notify that LE audio device has been turned on
+        receiverUnderTest.onReceive(mContext, buildActiveDeviceChangeActionIntent(device1,
+                BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
+        // Verify call to clear speaker communication device
+        verify(mockAudioManager).clearCommunicationDevice();
+        // Verify that LE audio communication device was set after clearing the speaker device
+        verify(mockAudioManager).setCommunicationDevice(leAudioDevice1);
+    }
+
+    @SmallTest
+    @Test
+    public void testConnectDualModeEarbud() {
+        when(mFeatureFlags.callAudioCommunicationDeviceRefactor()).thenReturn(true);
+        receiverUnderTest.setIsInCall(true);
+
+        // LE Audio earbuds connected
+        receiverUnderTest.onReceive(mContext,
+                buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device5,
+                        BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
+        leAudioCallbacksTest.getValue().onGroupNodeAdded(device5, 1);
+        receiverUnderTest.onReceive(mContext,
+                buildConnectionActionIntent(BluetoothLeAudio.STATE_CONNECTED, device6,
+                        BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
+        leAudioCallbacksTest.getValue().onGroupNodeAdded(device6, 1);
+        // HFP device connected
+        receiverUnderTest.onReceive(mContext,
+                buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device5,
+                        BluetoothDeviceManager.DEVICE_TYPE_HEADSET));
+        when(mAdapter.setActiveDevice(nullable(BluetoothDevice.class),
+                eq(BluetoothAdapter.ACTIVE_DEVICE_ALL))).thenReturn(true);
+
+        AudioDeviceInfo mockAudioDevice5Info = createMockAudioDeviceInfo(device5.getAddress(),
+                AudioDeviceInfo.TYPE_BLE_HEADSET);
+        AudioDeviceInfo mockAudioDevice6Info = createMockAudioDeviceInfo(device6.getAddress(),
+                AudioDeviceInfo.TYPE_BLE_HEADSET);
+        when(mockAudioDevice5Info.getType()).thenReturn(AudioDeviceInfo.TYPE_BLE_HEADSET);
+        when(mockAudioDevice6Info.getType()).thenReturn(AudioDeviceInfo.TYPE_BLE_HEADSET);
+        List<AudioDeviceInfo> devices = new ArrayList<>();
+        devices.add(mockAudioDevice5Info);
+        devices.add(mockAudioDevice6Info);
+
+        when(mockAudioManager.getAvailableCommunicationDevices())
+                .thenReturn(devices);
+        when(mockAudioManager.setCommunicationDevice(mockAudioDevice5Info))
+                .thenReturn(true);
+
+        Bundle hfpPreferred = new Bundle();
+        hfpPreferred.putInt(BluetoothAdapter.AUDIO_MODE_DUPLEX, BluetoothProfile.HEADSET);
+        Bundle leAudioPreferred = new Bundle();
+        leAudioPreferred.putInt(BluetoothAdapter.AUDIO_MODE_DUPLEX, BluetoothProfile.LE_AUDIO);
+
+        // TEST 1: LE Audio preferred for DUPLEX
+        when(mAdapter.getPreferredAudioProfiles(device5)).thenReturn(leAudioPreferred);
+        when(mAdapter.getPreferredAudioProfiles(device6)).thenReturn(leAudioPreferred);
+        mBluetoothDeviceManager.connectAudio(device5.getAddress(), false);
+        verify(mAdapter, times(1)).setActiveDevice(device5, BluetoothAdapter.ACTIVE_DEVICE_ALL);
+        verify(mBluetoothHeadset, never()).connectAudio();
+        verify(mAdapter, never()).setActiveDevice(nullable(BluetoothDevice.class),
+                eq(BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL));
+        verify(mockAudioManager).setCommunicationDevice(mockAudioDevice5Info);
+
+        when(mAdapter.getActiveDevices(eq(BluetoothProfile.LE_AUDIO)))
+                .thenReturn(Arrays.asList(device5, device6));
+
+        // Check disconnect during a call
+        devices.remove(mockAudioDevice5Info);
+        receiverUnderTest.onReceive(mContext,
+                buildConnectionActionIntent(BluetoothHeadset.STATE_DISCONNECTED, device5,
+                        BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
+        leAudioCallbacksTest.getValue().onGroupNodeRemoved(device5, 1);
+
+        mBluetoothDeviceManager.connectAudio(device6.getAddress(), false);
+        verify(mAdapter).setActiveDevice(device6, BluetoothAdapter.ACTIVE_DEVICE_ALL);
+        verify(mBluetoothHeadset, never()).connectAudio();
+        verify(mAdapter, never()).setActiveDevice(nullable(BluetoothDevice.class),
+                eq(BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL));
+        verify(mockAudioManager, times(1)).clearCommunicationDevice();
+
+        // Reconnect other LE Audio earbud
+        devices.add(mockAudioDevice5Info);
+        receiverUnderTest.onReceive(mContext,
+                buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device5,
+                        BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
+        leAudioCallbacksTest.getValue().onGroupNodeAdded(device5, 1);
+
+        // Disconnects audio
+        mBluetoothDeviceManager.disconnectAudio();
+        verify(mockAudioManager, times(2)).clearCommunicationDevice();
+        verify(mBluetoothHeadset, times(1)).disconnectAudio();
+
+        // TEST 2: HFP preferred for DUPLEX
+        when(mAdapter.getPreferredAudioProfiles(device5)).thenReturn(hfpPreferred);
+        when(mAdapter.getPreferredAudioProfiles(device6)).thenReturn(hfpPreferred);
+        when(mAdapter.setActiveDevice(nullable(BluetoothDevice.class),
+                eq(BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL))).thenReturn(true);
+        mBluetoothDeviceManager.connectAudio(device5.getAddress(), false);
+        verify(mAdapter).setActiveDevice(device5, BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL);
+        verify(mAdapter, times(1)).setActiveDevice(device5,
+                BluetoothAdapter.ACTIVE_DEVICE_ALL);
+        verify(mBluetoothHeadset).connectAudio();
+        mBluetoothDeviceManager.disconnectAudio();
+        verify(mBluetoothHeadset, times(2)).disconnectAudio();
+    }
+
+    @SmallTest
+    @Test
+    public void testClearHearingAidCommunicationDeviceLegacy() {
+        assertClearHearingAidOrLeCommunicationDevice(false, AudioDeviceInfo.TYPE_HEARING_AID);
+    }
+
+    @SmallTest
+    @Test
+    public void testClearHearingAidCommunicationDeviceWithFlag() {
+        when(mFeatureFlags.callAudioCommunicationDeviceRefactor()).thenReturn(true);
+        assertClearHearingAidOrLeCommunicationDevice(true, AudioDeviceInfo.TYPE_HEARING_AID);
+    }
+
+    @SmallTest
+    @Test
+    public void testClearLeAudioCommunicationDeviceLegacy() {
+        assertClearHearingAidOrLeCommunicationDevice(false, AudioDeviceInfo.TYPE_BLE_HEADSET);
+    }
+
+    @SmallTest
+    @Test
+    public void testClearLeAudioCommunicationDeviceWithFlag() {
+        when(mFeatureFlags.callAudioCommunicationDeviceRefactor()).thenReturn(true);
+        assertClearHearingAidOrLeCommunicationDevice(true, AudioDeviceInfo.TYPE_BLE_HEADSET);
+    }
+
+    @SmallTest
+    @Test
+    public void testConnectedDevicesDoNotContainDuplicateDevices() {
+        BluetoothDevice hfpDevice = mock(BluetoothDevice.class);
+        when(hfpDevice.getAddress()).thenReturn("00:00:00:00:00:00");
+        when(hfpDevice.getType()).thenReturn(BluetoothDeviceManager.DEVICE_TYPE_HEADSET);
+        BluetoothDevice leDevice = mock(BluetoothDevice.class);
+        when(hfpDevice.getAddress()).thenReturn("00:00:00:00:00:00");
+        when(hfpDevice.getType()).thenReturn(BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO);
+
+        mBluetoothDeviceManager.onDeviceConnected(hfpDevice,
+                BluetoothDeviceManager.DEVICE_TYPE_HEADSET);
+        mBluetoothDeviceManager.onDeviceConnected(leDevice,
+                BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO);
+
+        assertEquals(1, mBluetoothDeviceManager.getNumConnectedDevices());
     }
 
     @SmallTest
@@ -525,22 +737,67 @@
         when(mBluetoothLeAudio.isInbandRingtoneEnabled(1)).thenReturn(true);
         when(mBluetoothLeAudio.getGroupId(eq(device3))).thenReturn(1);
         receiverUnderTest.onReceive(mContext,
-                buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device1,
-                        BluetoothDeviceManager.DEVICE_TYPE_HEADSET));
-        receiverUnderTest.onReceive(mContext,
-                buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device2,
-                        BluetoothDeviceManager.DEVICE_TYPE_HEADSET));
-        receiverUnderTest.onReceive(mContext,
                 buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device3,
                         BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
         leAudioCallbacksTest.getValue().onGroupNodeAdded(device3, 1);
-        when(mBluetoothLeAudio.getConnectedGroupLeadDevice(1)).thenReturn(device3);
         when(mRouteManager.getBluetoothAudioConnectedDevice()).thenReturn(device3);
         when(mRouteManager.isCachedLeAudioDevice(eq(device3))).thenReturn(true);
-        assertEquals(3, mBluetoothDeviceManager.getNumConnectedDevices());
+        when(mBluetoothLeAudio.getConnectedGroupLeadDevice(1)).thenReturn(device3);
+        when(mRouteManager.getMostRecentlyReportedActiveDevice()).thenReturn(device3);
+        assertEquals(1, mBluetoothDeviceManager.getNumConnectedDevices());
         assertTrue(mBluetoothDeviceManager.isInbandRingingEnabled());
     }
 
+    private void assertClearHearingAidOrLeCommunicationDevice(
+            boolean flagEnabled, int device_type
+    ) {
+        AudioDeviceInfo mockAudioDeviceInfo = mock(AudioDeviceInfo.class);
+        when(mockAudioDeviceInfo.getAddress()).thenReturn(DEVICE_ADDRESS_1);
+        when(mockAudioDeviceInfo.getType()).thenReturn(device_type);
+        List<AudioDeviceInfo> devices = new ArrayList<>();
+        devices.add(mockAudioDeviceInfo);
+
+        when(mockAudioManager.getAvailableCommunicationDevices())
+                .thenReturn(devices);
+        when(mockAudioManager.setCommunicationDevice(eq(mockAudioDeviceInfo)))
+                .thenReturn(true);
+
+        if (flagEnabled) {
+            BluetoothDevice btDevice = device_type == AudioDeviceInfo.TYPE_BLE_HEADSET
+                    ? device1 : null;
+            mCommunicationDeviceTracker.setCommunicationDevice(device_type, btDevice);
+        } else {
+            if (device_type == AudioDeviceInfo.TYPE_BLE_HEADSET) {
+                mBluetoothDeviceManager.setLeAudioCommunicationDevice();
+            } else {
+                mBluetoothDeviceManager.setHearingAidCommunicationDevice();
+            }
+        }
+        when(mockAudioManager.getCommunicationDevice()).thenReturn(mSpeakerInfo);
+        if (flagEnabled) {
+            mCommunicationDeviceTracker.clearCommunicationDevice(device_type);
+            assertFalse(mCommunicationDeviceTracker.isAudioDeviceSetForType(device_type));
+        } else {
+            if (device_type == AudioDeviceInfo.TYPE_BLE_HEADSET) {
+                mBluetoothDeviceManager.clearLeAudioCommunicationDevice();
+                assertFalse(mBluetoothDeviceManager.isLeAudioCommunicationDevice());
+            } else {
+                mBluetoothDeviceManager.clearHearingAidCommunicationDevice();
+                assertFalse(mBluetoothDeviceManager.isHearingAidSetAsCommunicationDevice());
+            }
+        }
+        verify(mRouteManager).onAudioLost(eq(DEVICE_ADDRESS_1));
+    }
+
+    private AudioDeviceInfo createMockAudioDeviceInfo(String address, int audioType) {
+        AudioDeviceInfo mockAudioDeviceInfo = mock(AudioDeviceInfo.class);
+        when(mockAudioDeviceInfo.getType()).thenReturn(audioType);
+        if (address != null) {
+            when(mockAudioDeviceInfo.getAddress()).thenReturn(address);
+        }
+        return mockAudioDeviceInfo;
+    }
+
     private Intent buildConnectionActionIntent(int state, BluetoothDevice device, int deviceType) {
         String intentString;
 
diff --git a/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java b/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java
index 1a6fb88..e1ef08a 100644
--- a/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java
@@ -16,6 +16,16 @@
 
 package com.android.server.telecom.tests;
 
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadset;
@@ -28,7 +38,10 @@
 import android.telecom.Log;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import android.media.AudioDeviceInfo;
+
 import com.android.internal.os.SomeArgs;
+import com.android.server.telecom.CallAudioCommunicationDeviceTracker;
 import com.android.server.telecom.TelecomSystem;
 import com.android.server.telecom.Timeouts;
 import com.android.server.telecom.bluetooth.BluetoothDeviceManager;
@@ -46,23 +59,20 @@
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
-import static org.junit.Assert.assertEquals;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.nullable;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
 @RunWith(JUnit4.class)
 public class BluetoothRouteManagerTest extends TelecomTestCase {
     private static final int TEST_TIMEOUT = 1000;
     static final BluetoothDevice DEVICE1 = makeBluetoothDevice("00:00:00:00:00:01");
     static final BluetoothDevice DEVICE2 = makeBluetoothDevice("00:00:00:00:00:02");
     static final BluetoothDevice DEVICE3 = makeBluetoothDevice("00:00:00:00:00:03");
-    static final BluetoothDevice HEARING_AID_DEVICE = makeBluetoothDevice("00:00:00:00:00:04");
+    static final BluetoothDevice HEARING_AID_DEVICE_LEFT = makeBluetoothDevice("CA:FE:DE:CA:00:01");
+    static final BluetoothDevice HEARING_AID_DEVICE_RIGHT =
+      makeBluetoothDevice("CA:FE:DE:CA:00:02");
+    // See HearingAidService#getActiveDevices
+    // Note: It is really important that the left HA is the first one. The left HA is always
+    // in the first index (0) and the right one in the second index (1).
+    static final BluetoothDevice[] HEARING_AIDS =
+      new BluetoothDevice[]{HEARING_AID_DEVICE_LEFT, HEARING_AID_DEVICE_RIGHT};
 
     @Mock private BluetoothAdapter mBluetoothAdapter;
     @Mock private BluetoothDeviceManager mDeviceManager;
@@ -71,6 +81,7 @@
     @Mock private BluetoothLeAudio mBluetoothLeAudio;
     @Mock private Timeouts.Adapter mTimeoutsAdapter;
     @Mock private BluetoothRouteManager.BluetoothStateListener mListener;
+    @Mock private CallAudioCommunicationDeviceTracker mCommunicationDeviceTracker;
 
     @Override
     @Before
@@ -86,6 +97,59 @@
 
     @SmallTest
     @Test
+    public void testConnectLeftHearingAidWhenLeftIsActive() {
+        BluetoothRouteManager sm = setupStateMachine(
+                BluetoothRouteManager.AUDIO_OFF_STATE_NAME, HEARING_AID_DEVICE_LEFT);
+        sm.onActiveDeviceChanged(HEARING_AID_DEVICE_LEFT,
+            BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID);
+        when(mDeviceManager.connectAudio(anyString(), anyBoolean())).thenReturn(true);
+        when(mDeviceManager.isHearingAidSetAsCommunicationDevice()).thenReturn(true);
+
+        setupConnectedDevices(null, HEARING_AIDS, null, null, HEARING_AIDS, null);
+        when(mBluetoothHeadset.getAudioState(nullable(BluetoothDevice.class)))
+          .thenReturn(BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
+
+        executeRoutingAction(sm,
+            BluetoothRouteManager.NEW_DEVICE_CONNECTED, HEARING_AID_DEVICE_LEFT.getAddress());
+
+        executeRoutingAction(sm,
+            BluetoothRouteManager.CONNECT_BT, HEARING_AID_DEVICE_LEFT.getAddress());
+
+        assertEquals(BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX
+            + ":" + HEARING_AID_DEVICE_LEFT.getAddress(), sm.getCurrentState().getName());
+
+        sm.quitNow();
+    }
+
+    @SmallTest
+    @Test
+    public void testConnectRightHearingAidWhenLeftIsActive() {
+        BluetoothRouteManager sm = setupStateMachine(
+                BluetoothRouteManager.AUDIO_OFF_STATE_NAME, HEARING_AID_DEVICE_RIGHT);
+        sm.onActiveDeviceChanged(HEARING_AID_DEVICE_LEFT,
+            BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID);
+        when(mDeviceManager.connectAudio(anyString(), anyBoolean())).thenReturn(true);
+        when(mDeviceManager.isHearingAidSetAsCommunicationDevice()).thenReturn(true);
+
+
+        setupConnectedDevices(null, HEARING_AIDS, null, null, HEARING_AIDS, null);
+        when(mBluetoothHeadset.getAudioState(nullable(BluetoothDevice.class)))
+          .thenReturn(BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
+
+        executeRoutingAction(sm,
+            BluetoothRouteManager.NEW_DEVICE_CONNECTED, HEARING_AID_DEVICE_LEFT.getAddress());
+
+        executeRoutingAction(sm,
+            BluetoothRouteManager.CONNECT_BT, HEARING_AID_DEVICE_LEFT.getAddress());
+
+        assertEquals(BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX
+            + ":" + HEARING_AID_DEVICE_LEFT.getAddress(), sm.getCurrentState().getName());
+
+        sm.quitNow();
+    }
+
+    @SmallTest
+    @Test
     public void testConnectBtRetryWhileNotConnected() {
         BluetoothRouteManager sm = setupStateMachine(
                 BluetoothRouteManager.AUDIO_OFF_STATE_NAME, null);
@@ -112,15 +176,15 @@
         BluetoothRouteManager sm = setupStateMachine(
                 BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX, DEVICE1);
         setupConnectedDevices(new BluetoothDevice[]{DEVICE1},
-                new BluetoothDevice[]{HEARING_AID_DEVICE}, new BluetoothDevice[]{DEVICE2},
-                DEVICE1, HEARING_AID_DEVICE, DEVICE2);
+                HEARING_AIDS, new BluetoothDevice[]{DEVICE2},
+                DEVICE1,  HEARING_AIDS, DEVICE2);
         sm.onActiveDeviceChanged(DEVICE1, BluetoothDeviceManager.DEVICE_TYPE_HEADSET);
         sm.onActiveDeviceChanged(DEVICE2, BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO);
-        sm.onActiveDeviceChanged(HEARING_AID_DEVICE,
+        sm.onActiveDeviceChanged(HEARING_AID_DEVICE_LEFT,
                 BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID);
         executeRoutingAction(sm, BluetoothRouteManager.BT_AUDIO_LOST, DEVICE1.getAddress());
 
-        verifyConnectionAttempt(HEARING_AID_DEVICE, 0);
+        verifyConnectionAttempt(HEARING_AID_DEVICE_LEFT, 0);
         verifyConnectionAttempt(DEVICE1, 0);
         verifyConnectionAttempt(DEVICE2, 0);
         assertEquals(BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX
@@ -171,11 +235,52 @@
         sm.quitNow();
     }
 
+    @SmallTest
+    @Test
+    public void testSkipInactiveBtDeviceWhenEvaluateActualState() {
+        BluetoothRouteManager sm = setupStateMachine(
+                BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX, HEARING_AID_DEVICE_LEFT);
+        setupConnectedDevices(null, HEARING_AIDS,
+                null, null, HEARING_AIDS, null);
+        executeRoutingAction(sm, BluetoothRouteManager.BT_AUDIO_LOST,
+                HEARING_AID_DEVICE_LEFT.getAddress());
+        assertEquals(BluetoothRouteManager.AUDIO_OFF_STATE_NAME, sm.getCurrentState().getName());
+        sm.quitNow();
+    }
+
+    @SmallTest
+    @Test
+    public void testConnectBtWithoutAddress() {
+        when(mFeatureFlags.useActualAddressToEnterConnectingState()).thenReturn(true);
+        BluetoothRouteManager sm = setupStateMachine(
+                BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX, DEVICE1);
+        setupConnectedDevices(new BluetoothDevice[]{DEVICE1, DEVICE2}, null, null, null, null,
+                null);
+        when(mTimeoutsAdapter.getRetryBluetoothConnectAudioBackoffMillis(
+                nullable(ContentResolver.class))).thenReturn(0L);
+        when(mBluetoothHeadset.connectAudio()).thenReturn(BluetoothStatusCodes.ERROR_UNKNOWN);
+        executeRoutingAction(sm, BluetoothRouteManager.CONNECT_BT, null);
+        // Wait 3 times: the first connection attempt is accounted for in executeRoutingAction,
+        // so wait twice for the retry attempt, again to make sure there are only three attempts,
+        // and once more for good luck.
+        waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
+        waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
+        waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
+        waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
+        verifyConnectionAttempt(DEVICE1, 1);
+        assertEquals(BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX
+                        + ":" + DEVICE1.getAddress(),
+                sm.getCurrentState().getName());
+        sm.getHandler().removeMessages(BluetoothRouteManager.CONNECTION_TIMEOUT);
+        sm.quitNow();
+    }
+
     private BluetoothRouteManager setupStateMachine(String initialState,
             BluetoothDevice initialDevice) {
         resetMocks();
         BluetoothRouteManager sm = new BluetoothRouteManager(mContext,
-                new TelecomSystem.SyncRoot() { }, mDeviceManager, mTimeoutsAdapter);
+                new TelecomSystem.SyncRoot() { }, mDeviceManager,
+                mTimeoutsAdapter, mCommunicationDeviceTracker, mFeatureFlags);
         sm.setListener(mListener);
         sm.setInitialStateForTesting(initialState, initialDevice);
         waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
@@ -185,10 +290,11 @@
 
     private void setupConnectedDevices(BluetoothDevice[] hfpDevices,
             BluetoothDevice[] hearingAidDevices, BluetoothDevice[] leAudioDevices,
-            BluetoothDevice hfpActiveDevice, BluetoothDevice hearingAidActiveDevice,
+            BluetoothDevice hfpActiveDevice, BluetoothDevice[] hearingAidActiveDevices,
             BluetoothDevice leAudioDevice) {
         if (hfpDevices == null) hfpDevices = new BluetoothDevice[]{};
         if (hearingAidDevices == null) hearingAidDevices = new BluetoothDevice[]{};
+        if (hearingAidActiveDevices == null) hearingAidActiveDevices = new BluetoothDevice[]{};
         if (leAudioDevice == null) leAudioDevices = new BluetoothDevice[]{};
 
         when(mDeviceManager.getNumConnectedDevices()).thenReturn(
@@ -207,7 +313,7 @@
         when(mBluetoothHearingAid.getConnectedDevices())
                 .thenReturn(Arrays.asList(hearingAidDevices));
         when(mBluetoothAdapter.getActiveDevices(eq(BluetoothProfile.HEARING_AID)))
-                .thenReturn(Arrays.asList(hearingAidActiveDevice, null));
+                .thenReturn(Arrays.asList(hearingAidActiveDevices));
         when(mBluetoothAdapter.getActiveDevices(eq(BluetoothProfile.LE_AUDIO)))
                 .thenReturn(Arrays.asList(leAudioDevice, null));
     }
diff --git a/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java b/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java
index 5eecccc..65854af 100644
--- a/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java
+++ b/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java
@@ -28,6 +28,7 @@
 import android.test.suitebuilder.annotation.SmallTest;
 
 import com.android.internal.os.SomeArgs;
+import com.android.server.telecom.CallAudioCommunicationDeviceTracker;
 import com.android.server.telecom.TelecomSystem;
 import com.android.server.telecom.Timeouts;
 import com.android.server.telecom.bluetooth.BluetoothDeviceManager;
@@ -66,7 +67,7 @@
 public class BluetoothRouteTransitionTests extends TelecomTestCase {
     private enum ListenerUpdate {
         DEVICE_LIST_CHANGED, ACTIVE_DEVICE_PRESENT, ACTIVE_DEVICE_GONE,
-        AUDIO_CONNECTED, AUDIO_DISCONNECTED, UNEXPECTED_STATE_CHANGE
+        AUDIO_CONNECTING, AUDIO_CONNECTED, AUDIO_DISCONNECTED, UNEXPECTED_STATE_CHANGE
     }
 
     private static class BluetoothRouteTestParametersBuilder {
@@ -263,6 +264,7 @@
     @Mock private BluetoothLeAudio mBluetoothLeAudio;
     @Mock private Timeouts.Adapter mTimeoutsAdapter;
     @Mock private BluetoothRouteManager.BluetoothStateListener mListener;
+    @Mock private CallAudioCommunicationDeviceTracker mCommunicationDeviceTracker;
 
     @Override
     @Before
@@ -348,6 +350,9 @@
                 case ACTIVE_DEVICE_GONE:
                     verify(mListener).onBluetoothActiveDeviceGone();
                     break;
+                case AUDIO_CONNECTING:
+                    verify(mListener).onBluetoothAudioConnecting();
+                    break;
                 case AUDIO_CONNECTED:
                     verify(mListener).onBluetoothAudioConnected();
                     break;
@@ -413,7 +418,8 @@
         when(mTimeoutsAdapter.getBluetoothPendingTimeoutMillis(
                 nullable(ContentResolver.class))).thenReturn(100000L);
         BluetoothRouteManager sm = new BluetoothRouteManager(mContext,
-                new TelecomSystem.SyncRoot() { }, mDeviceManager, mTimeoutsAdapter);
+                new TelecomSystem.SyncRoot() { }, mDeviceManager,
+                mTimeoutsAdapter, mCommunicationDeviceTracker, mFeatureFlags);
         sm.setListener(mListener);
         sm.setInitialStateForTesting(initialState, initialDevice);
         waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
@@ -449,7 +455,7 @@
                 .setConnectedDevices(DEVICE2, DEVICE1)
                 .setActiveDevice(DEVICE1)
                 .setMessageType(BluetoothRouteManager.CONNECT_BT)
-                .setExpectedListenerUpdates(ListenerUpdate.AUDIO_CONNECTED)
+                .setExpectedListenerUpdates(ListenerUpdate.AUDIO_CONNECTING)
                 .setExpectedBluetoothInteraction(CONNECT)
                 .setExpectedConnectionDevice(DEVICE1)
                 .setExpectedFinalStateName(BluetoothRouteManager.AUDIO_CONNECTING_STATE_NAME_PREFIX
@@ -505,7 +511,7 @@
                 .setConnectedDevices(DEVICE2, DEVICE1, DEVICE3)
                 .setMessageType(BluetoothRouteManager.CONNECT_BT)
                 .setMessageDevice(DEVICE3)
-                .setExpectedListenerUpdates(ListenerUpdate.AUDIO_CONNECTED)
+                .setExpectedListenerUpdates(ListenerUpdate.AUDIO_CONNECTING)
                 .setExpectedBluetoothInteraction(CONNECT_SWITCH_DEVICE)
                 .setExpectedConnectionDevice(DEVICE3)
                 .setExpectedFinalStateName(BluetoothRouteManager.AUDIO_CONNECTING_STATE_NAME_PREFIX
@@ -519,7 +525,7 @@
                 .setConnectedDevices(DEVICE2, DEVICE1, DEVICE3)
                 .setMessageType(BluetoothRouteManager.CONNECT_BT)
                 .setMessageDevice(DEVICE3)
-                .setExpectedListenerUpdates(ListenerUpdate.AUDIO_CONNECTED)
+                .setExpectedListenerUpdates(ListenerUpdate.AUDIO_CONNECTING)
                 .setExpectedBluetoothInteraction(CONNECT_SWITCH_DEVICE)
                 .setExpectedConnectionDevice(DEVICE3)
                 .setExpectedFinalStateName(BluetoothRouteManager.AUDIO_CONNECTING_STATE_NAME_PREFIX
diff --git a/tests/src/com/android/server/telecom/tests/CallAnomalyWatchdogTest.java b/tests/src/com/android/server/telecom/tests/CallAnomalyWatchdogTest.java
index 93d1314..86d24f9 100644
--- a/tests/src/com/android/server/telecom/tests/CallAnomalyWatchdogTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAnomalyWatchdogTest.java
@@ -21,11 +21,14 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.ComponentName;
 import android.net.Uri;
+import android.os.UserHandle;
+import android.telecom.DisconnectCause;
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
 
@@ -37,6 +40,7 @@
 import com.android.server.telecom.CallsManager;
 import com.android.server.telecom.ClockProxy;
 import com.android.server.telecom.ConnectionServiceWrapper;
+import com.android.server.telecom.EmergencyCallDiagnosticLogger;
 import com.android.server.telecom.PhoneAccountRegistrar;
 import com.android.server.telecom.PhoneNumberUtilsAdapter;
 import com.android.server.telecom.TelecomSystem;
@@ -85,6 +89,8 @@
     @Mock private ConnectionServiceWrapper mMockConnectionService;
     @Mock private AnomalyReporterAdapter mAnomalyReporterAdapter;
 
+    @Mock private EmergencyCallDiagnosticLogger mMockEmergencyCallDiagnosticLogger;
+
     @Override
     @Before
     public void setUp() throws Exception {
@@ -116,8 +122,9 @@
         doReturn(new ComponentName(mContext, CallTest.class))
                 .when(mMockConnectionService).getComponentName();
         mCallAnomalyWatchdog = new CallAnomalyWatchdog(mTestScheduledExecutorService, mLock,
-                mTimeouts, mMockClockProxy);
+                mTimeouts, mMockClockProxy, mMockEmergencyCallDiagnosticLogger);
         mCallAnomalyWatchdog.setAnomalyReporterAdapter(mAnomalyReporterAdapter);
+        when(mMockCallsManager.getCurrentUserHandle()).thenReturn(UserHandle.CURRENT);
     }
 
     @Override
@@ -755,6 +762,90 @@
     }
 
     /**
+     * Emulate the case where a new incoming call is created but the connection fails for a known
+     * reason before being added to CallsManager. In this case, the watchdog should stop tracking
+     * the call and not trigger an anomaly report.
+     */
+    @Test
+    public void testIncomingCallCreatedButNotAddedNoAnomalyReport() {
+        //The call is created:
+        Call call = getCall();
+        call.setState(CallState.NEW, "foo");
+        call.setIsCreateConnectionComplete(false);
+        mCallAnomalyWatchdog.onStartCreateConnection(call);
+
+        //The connection fails before being added to CallsManager for a known reason:
+        call.handleCreateConnectionFailure(new DisconnectCause(DisconnectCause.CANCELED));
+
+        // Move the clock forward:
+        when(mMockClockProxy.elapsedRealtime()).
+                thenReturn(TEST_NON_VOIP_INTERMEDIATE_MILLIS + 1);
+        mTestScheduledExecutorService.advanceTime(TEST_NON_VOIP_INTERMEDIATE_MILLIS + 1);
+
+        //Ensure an anomaly report is not generated:
+        verify(mAnomalyReporterAdapter, never()).reportAnomaly(
+                CallAnomalyWatchdog.WATCHDOG_DISCONNECTED_STUCK_CALL_UUID,
+                CallAnomalyWatchdog.WATCHDOG_DISCONNECTED_STUCK_CALL_MSG);
+    }
+
+    /**
+     * Emulate the case where a new outgoing call is created but the connection fails for a known
+     * reason before being added to CallsManager. In this case, the watchdog should stop tracking
+     * the call and not trigger an anomaly report.
+     */
+    @Test
+    public void testOutgoingCallCreatedButNotAddedNoAnomalyReport() {
+        //The call is created:
+        Call call = getCall();
+        call.setCallDirection(Call.CALL_DIRECTION_OUTGOING);
+        call.setState(CallState.NEW, "foo");
+        call.setIsCreateConnectionComplete(false);
+        mCallAnomalyWatchdog.onStartCreateConnection(call);
+
+        //The connection fails before being added to CallsManager for a known reason.
+        call.handleCreateConnectionFailure(new DisconnectCause(DisconnectCause.CANCELED));
+
+        // Move the clock forward:
+        when(mMockClockProxy.elapsedRealtime()).
+                thenReturn(TEST_NON_VOIP_INTERMEDIATE_MILLIS + 1);
+        mTestScheduledExecutorService.advanceTime(TEST_NON_VOIP_INTERMEDIATE_MILLIS + 1);
+
+        //Ensure an anomaly report is not generated:
+        verify(mAnomalyReporterAdapter, never()).reportAnomaly(
+                CallAnomalyWatchdog.WATCHDOG_DISCONNECTED_STUCK_CALL_UUID,
+                CallAnomalyWatchdog.WATCHDOG_DISCONNECTED_STUCK_CALL_MSG);
+    }
+
+    /**
+     * Emulate the case where a new incoming call is created but the connection fails for a known
+     * reason before being added to CallsManager and CallsManager notifies the watchdog by invoking
+     * {@link CallsManager.CallsManagerListener#onCreateConnectionFailed(Call)}.
+     * In this case, the watchdog should stop tracking the call and not trigger an anomaly report.
+     */
+    @Test
+    public void testCallCreatedButNotAddedPreventsAnomalyReport() {
+        //The call is created:
+        Call call = getCall();
+        call.setState(CallState.NEW, "foo");
+        call.setIsCreateConnectionComplete(false);
+        mCallAnomalyWatchdog.onStartCreateConnection(call);
+
+        //Telecom cancels the connection before adding it to CallsManager:
+        mCallAnomalyWatchdog.onCreateConnectionFailed(call);
+
+        // Move the clock forward:
+        when(mMockClockProxy.elapsedRealtime()).
+                thenReturn(TEST_NON_VOIP_INTERMEDIATE_MILLIS + 1);
+        mTestScheduledExecutorService.advanceTime(TEST_NON_VOIP_INTERMEDIATE_MILLIS + 1);
+
+        //Ensure an anomaly report is not generated:
+        verify(mAnomalyReporterAdapter, never()).reportAnomaly(
+                CallAnomalyWatchdog.WATCHDOG_DISCONNECTED_STUCK_CALL_UUID,
+                CallAnomalyWatchdog.WATCHDOG_DISCONNECTED_STUCK_CALL_MSG);
+    }
+
+
+    /**
      * @return an instance of {@link Call} for testing purposes.
      */
     private Call getCall() {
@@ -773,6 +864,7 @@
                 false /* shouldAttachToExistingConnection*/,
                 false /* isConference */,
                 mMockClockProxy,
-                mMockToastProxy);
+                mMockToastProxy,
+                mFeatureFlags);
     }
 }
\ No newline at end of file
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioManagerTest.java b/tests/src/com/android/server/telecom/tests/CallAudioManagerTest.java
index 3d06ad0..0a75c3a 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioManagerTest.java
@@ -36,6 +36,7 @@
 import com.android.server.telecom.Ringer;
 import com.android.server.telecom.TelecomSystem;
 import com.android.server.telecom.bluetooth.BluetoothStateReceiver;
+import com.android.server.telecom.flags.FeatureFlags;
 
 import org.junit.After;
 import org.junit.Before;
@@ -59,6 +60,7 @@
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -78,6 +80,8 @@
     @Mock private BluetoothStateReceiver mBluetoothStateReceiver;
     @Mock private TelecomSystem.SyncRoot mLock;
 
+    @Mock private FeatureFlags mFlags;
+
     private CallAudioManager mCallAudioManager;
 
     @Override
@@ -87,12 +91,13 @@
         doAnswer((invocation) -> {
             InCallTonePlayer mockInCallTonePlayer = mock(InCallTonePlayer.class);
             doAnswer((invocation2) -> {
-                mCallAudioManager.setIsTonePlaying(true);
+                mCallAudioManager.setIsTonePlaying(invocation.getArgument(0), true);
                 return true;
             }).when(mockInCallTonePlayer).startTone();
             return mockInCallTonePlayer;
-        }).when(mPlayerFactory).createPlayer(anyInt());
+        }).when(mPlayerFactory).createPlayer(any(Call.class), anyInt());
         when(mCallsManager.getLock()).thenReturn(mLock);
+        when(mFlags.ensureAudioModeUpdatesOnForegroundCallChange()).thenReturn(true);
         mCallAudioManager = new CallAudioManager(
                 mCallAudioRouteStateMachine,
                 mCallsManager,
@@ -101,7 +106,8 @@
                 mRinger,
                 mRingbackPlayer,
                 mBluetoothStateReceiver,
-                mDtmfLocalTonePlayer);
+                mDtmfLocalTonePlayer,
+                mFlags);
     }
 
     @Override
@@ -204,7 +210,7 @@
         assertMessageArgEquality(correctArgs, captor.getValue());
 
         disconnectCall(call);
-        stopTone();
+        stopTone(call);
 
         mCallAudioManager.onCallRemoved(call);
         verifyProperCleanup();
@@ -241,7 +247,7 @@
         mCallAudioManager.onCallStateChanged(call, CallState.ANSWERED, CallState.ACTIVE);
 
         disconnectCall(call);
-        stopTone();
+        stopTone(call);
 
         mCallAudioManager.onCallRemoved(call);
         verifyProperCleanup();
@@ -277,25 +283,40 @@
         verify(mCallAudioModeStateMachine, times(2)).sendMessageWithArgs(
                 eq(CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL), captor.capture());
         assertMessageArgEquality(expectedArgs, captor.getValue());
-        verify(mCallAudioModeStateMachine, times(2)).sendMessageWithArgs(
-                anyInt(), any(CallAudioModeStateMachine.MessageArgs.class));
-
+        if (mFlags.ensureAudioModeUpdatesOnForegroundCallChange()) {
+            // Expect another invocation due to audio mode change signal.
+            verify(mCallAudioModeStateMachine, times(3)).sendMessageWithArgs(
+                    anyInt(), any(CallAudioModeStateMachine.MessageArgs.class));
+        } else {
+            verify(mCallAudioModeStateMachine, times(2)).sendMessageWithArgs(
+                    anyInt(), any(CallAudioModeStateMachine.MessageArgs.class));
+        }
 
         when(call.getState()).thenReturn(CallState.ACTIVE);
         mCallAudioManager.onCallStateChanged(call, CallState.DIALING, CallState.ACTIVE);
         verify(mCallAudioModeStateMachine, times(3)).sendMessageWithArgs(
                 eq(CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL), captor.capture());
         assertMessageArgEquality(expectedArgs, captor.getValue());
-        verify(mCallAudioModeStateMachine, times(3)).sendMessageWithArgs(
-                anyInt(), any(CallAudioModeStateMachine.MessageArgs.class));
-
+        if (mFlags.ensureAudioModeUpdatesOnForegroundCallChange()) {
+            verify(mCallAudioModeStateMachine, times(4)).sendMessageWithArgs(
+                    anyInt(), any(CallAudioModeStateMachine.MessageArgs.class));
+        } else {
+            verify(mCallAudioModeStateMachine, times(3)).sendMessageWithArgs(
+                    anyInt(), any(CallAudioModeStateMachine.MessageArgs.class));
+        }
         disconnectCall(call);
-        stopTone();
+        stopTone(call);
 
         mCallAudioManager.onCallRemoved(call);
         verifyProperCleanup();
     }
 
+    @Test
+    public void testSingleOutgoingCallWithoutAudioModeUpdateOnForegroundCallChange() {
+        when(mFlags.ensureAudioModeUpdatesOnForegroundCallChange()).thenReturn(false);
+        testSingleOutgoingCall();
+    }
+
     @MediumTest
     @Test
     public void testRingbackStartStop() {
@@ -327,8 +348,14 @@
         verify(mCallAudioModeStateMachine, times(2)).sendMessageWithArgs(
                 eq(CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL), captor.capture());
         assertMessageArgEquality(expectedArgs, captor.getValue());
-        verify(mCallAudioModeStateMachine, times(2)).sendMessageWithArgs(
-                anyInt(), any(CallAudioModeStateMachine.MessageArgs.class));
+        if (mFlags.ensureAudioModeUpdatesOnForegroundCallChange()) {
+            // Expect an extra time due to audio mode change signal
+            verify(mCallAudioModeStateMachine, times(3)).sendMessageWithArgs(
+                    anyInt(), any(CallAudioModeStateMachine.MessageArgs.class));
+        } else {
+            verify(mCallAudioModeStateMachine, times(2)).sendMessageWithArgs(
+                    anyInt(), any(CallAudioModeStateMachine.MessageArgs.class));
+        }
 
         // Ensure we started ringback.
         verify(mRingbackPlayer).startRingbackForCall(any(Call.class));
@@ -350,6 +377,12 @@
         verify(mRingbackPlayer, times(1)).startRingbackForCall(any(Call.class));
     }
 
+    @Test
+    public void testRingbackStartStopWithoutAudioModeUpdateOnForegroundCallChange() {
+        when(mFlags.ensureAudioModeUpdatesOnForegroundCallChange()).thenReturn(false);
+        testRingbackStartStop();
+    }
+
     @SmallTest
     @Test
     public void testNewCallGoesToAudioProcessing() {
@@ -495,7 +528,7 @@
 
         mCallAudioManager.onCallStateChanged(call, CallState.AUDIO_PROCESSING,
                 CallState.DISCONNECTED);
-        verify(mPlayerFactory, never()).createPlayer(anyInt());
+        verify(mPlayerFactory, never()).createPlayer(any(Call.class), anyInt());
         CallAudioModeStateMachine.MessageArgs expectedArgs2 = new Builder()
                 .setHasActiveOrDialingCalls(false)
                 .setHasRingingCalls(false)
@@ -526,7 +559,7 @@
 
         mCallAudioManager.onCallStateChanged(call, CallState.AUDIO_PROCESSING,
                 CallState.SIMULATED_RINGING);
-        verify(mPlayerFactory, never()).createPlayer(anyInt());
+        verify(mPlayerFactory, never()).createPlayer(any(Call.class), anyInt());
         CallAudioModeStateMachine.MessageArgs expectedArgs = new Builder()
                 .setHasActiveOrDialingCalls(false)
                 .setHasRingingCalls(true)
@@ -555,7 +588,7 @@
 
         mCallAudioManager.onCallStateChanged(call, CallState.AUDIO_PROCESSING,
                 CallState.ACTIVE);
-        verify(mPlayerFactory, never()).createPlayer(anyInt());
+        verify(mPlayerFactory, never()).createPlayer(any(Call.class), anyInt());
         CallAudioModeStateMachine.MessageArgs expectedArgs = new Builder()
                 .setHasActiveOrDialingCalls(true)
                 .setHasRingingCalls(false)
@@ -584,7 +617,7 @@
 
         mCallAudioManager.onCallStateChanged(call, CallState.SIMULATED_RINGING,
                 CallState.ACTIVE);
-        verify(mPlayerFactory, never()).createPlayer(anyInt());
+        verify(mPlayerFactory, never()).createPlayer(any(Call.class), anyInt());
         CallAudioModeStateMachine.MessageArgs expectedArgs = new Builder()
                 .setHasActiveOrDialingCalls(true)
                 .setHasRingingCalls(false)
@@ -643,7 +676,7 @@
 
         mCallAudioManager.onCallStateChanged(call, CallState.SIMULATED_RINGING,
                 CallState.DISCONNECTED);
-        verify(mPlayerFactory, never()).createPlayer(anyInt());
+        verify(mPlayerFactory, never()).createPlayer(any(Call.class), anyInt());
         CallAudioModeStateMachine.MessageArgs expectedArgs2 = new Builder()
                 .setHasActiveOrDialingCalls(false)
                 .setHasRingingCalls(false)
@@ -702,6 +735,77 @@
         assertFalse(captor.getValue().isStreaming);
     }
 
+    @SmallTest
+    @Test
+    public void testTriggerAudioManagerModeChange() {
+        if (!mFlags.ensureAudioModeUpdatesOnForegroundCallChange()) {
+            // Skip if the new behavior isn't in use.
+            return;
+        }
+        // Start with an incoming PSTN call
+        Call pstnCall = mock(Call.class);
+        when(pstnCall.getState()).thenReturn(CallState.RINGING);
+        when(pstnCall.getIsVoipAudioMode()).thenReturn(false);
+        ArgumentCaptor<CallAudioModeStateMachine.MessageArgs> captor = makeNewCaptor();
+
+        // Add the call
+        mCallAudioManager.onCallAdded(pstnCall);
+        verify(mCallAudioModeStateMachine).sendMessageWithArgs(
+                eq(CallAudioModeStateMachine.FOREGROUND_VOIP_MODE_CHANGE), captor.capture());
+        CallAudioModeStateMachine.MessageArgs expectedArgs =
+                new Builder()
+                        .setHasActiveOrDialingCalls(false)
+                        .setHasRingingCalls(true)
+                        .setHasHoldingCalls(false)
+                        .setIsTonePlaying(false)
+                        .setHasAudioProcessingCalls(false)
+                        .setForegroundCallIsVoip(false)
+                        .setSession(null)
+                        .setForegroundCallIsVoip(false)
+                        .build();
+        assertMessageArgEquality(expectedArgs, captor.getValue());
+        clearInvocations(mCallAudioModeStateMachine); // Avoid verifying for previous calls
+
+        // Make call active; don't expect there to be an audio mode transition.
+        when(pstnCall.getState()).thenReturn(CallState.ACTIVE);
+        mCallAudioManager.onCallStateChanged(pstnCall, CallState.RINGING, CallState.ACTIVE);
+        verify(mCallAudioModeStateMachine, never()).sendMessageWithArgs(
+                eq(CallAudioModeStateMachine.FOREGROUND_VOIP_MODE_CHANGE),
+                any(CallAudioModeStateMachine.MessageArgs.class));
+        clearInvocations(mCallAudioModeStateMachine); // Avoid verifying for previous calls
+
+        // Add a new Voip call in ringing state; this should not result in a direct audio mode
+        // change.
+        Call voipCall = mock(Call.class);
+        when(voipCall.getState()).thenReturn(CallState.RINGING);
+        when(voipCall.getIsVoipAudioMode()).thenReturn(true);
+        mCallAudioManager.onCallAdded(voipCall);
+        verify(mCallAudioModeStateMachine, never()).sendMessageWithArgs(
+                eq(CallAudioModeStateMachine.FOREGROUND_VOIP_MODE_CHANGE),
+                any(CallAudioModeStateMachine.MessageArgs.class));
+        clearInvocations(mCallAudioModeStateMachine); // Avoid verifying for previous calls
+
+        // Make voip call active and set the PSTN call to locally disconnecting; the new foreground
+        // call will be the voip call.
+        when(pstnCall.isLocallyDisconnecting()).thenReturn(true);
+        when(voipCall.getState()).thenReturn(CallState.ACTIVE);
+        mCallAudioManager.onCallStateChanged(voipCall, CallState.RINGING, CallState.ACTIVE);
+        verify(mCallAudioModeStateMachine).sendMessageWithArgs(
+                eq(CallAudioModeStateMachine.FOREGROUND_VOIP_MODE_CHANGE), captor.capture());
+        CallAudioModeStateMachine.MessageArgs expectedArgs2 =
+                new Builder()
+                        .setHasActiveOrDialingCalls(true)
+                        .setHasRingingCalls(false)
+                        .setHasHoldingCalls(false)
+                        .setIsTonePlaying(false)
+                        .setHasAudioProcessingCalls(false)
+                        .setForegroundCallIsVoip(false)
+                        .setSession(null)
+                        .setForegroundCallIsVoip(true)
+                        .build();
+        assertMessageArgEquality(expectedArgs2, captor.getValue());
+    }
+
     private Call createSimulatedRingingCall() {
         Call call = mock(Call.class);
         when(call.getState()).thenReturn(CallState.SIMULATED_RINGING);
@@ -765,7 +869,7 @@
                 "", "", "", ToneGenerator.TONE_PROP_PROMPT));
 
         mCallAudioManager.onCallStateChanged(call, CallState.ACTIVE, CallState.DISCONNECTED);
-        verify(mPlayerFactory).createPlayer(InCallTonePlayer.TONE_CALL_ENDED);
+        verify(mPlayerFactory).createPlayer(any(Call.class), eq(InCallTonePlayer.TONE_CALL_ENDED));
         correctArgs = new Builder()
                 .setHasActiveOrDialingCalls(false)
                 .setHasRingingCalls(false)
@@ -782,10 +886,10 @@
         assertMessageArgEquality(correctArgs, captor.getValue());
     }
 
-    private void stopTone() {
+    private void stopTone(Call call) {
         ArgumentCaptor<CallAudioModeStateMachine.MessageArgs> captor =
                 ArgumentCaptor.forClass(CallAudioModeStateMachine.MessageArgs.class);
-        mCallAudioManager.setIsTonePlaying(false);
+        mCallAudioManager.setIsTonePlaying(call, false);
         CallAudioModeStateMachine.MessageArgs correctArgs = new Builder()
                         .setHasActiveOrDialingCalls(false)
                         .setHasRingingCalls(false)
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioModeStateMachineTest.java b/tests/src/com/android/server/telecom/tests/CallAudioModeStateMachineTest.java
index 0f38ca5..cddf2ad 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioModeStateMachineTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioModeStateMachineTest.java
@@ -16,14 +16,30 @@
 
 package com.android.server.telecom.tests;
 
+import static com.android.server.telecom.CallAudioModeStateMachine.CALL_AUDIO_FOCUS_REQUEST;
+import static com.android.server.telecom.CallAudioModeStateMachine.RING_AUDIO_FOCUS_REQUEST;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.clearInvocations;
+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.media.AudioFocusRequest;
 import android.media.AudioManager;
 import android.os.HandlerThread;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import com.android.server.telecom.CallAudioCommunicationDeviceTracker;
 import com.android.server.telecom.CallAudioManager;
 import com.android.server.telecom.CallAudioModeStateMachine;
-import com.android.server.telecom.CallAudioRouteStateMachine;
 import com.android.server.telecom.CallAudioModeStateMachine.MessageArgs.Builder;
+import com.android.server.telecom.CallAudioRouteStateMachine;
 import com.android.server.telecom.SystemStateHelper;
 
 import org.junit.After;
@@ -31,18 +47,9 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 
-import static org.junit.Assert.assertEquals;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.nullable;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
 @RunWith(JUnit4.class)
 public class CallAudioModeStateMachineTest extends TelecomTestCase {
     private static final int TEST_TIMEOUT = 1000;
@@ -51,6 +58,7 @@
     @Mock private AudioManager mAudioManager;
     @Mock private CallAudioManager mCallAudioManager;
     @Mock private CallAudioRouteStateMachine mCallAudioRouteStateMachine;
+    @Mock private CallAudioCommunicationDeviceTracker mCommunicationDeviceTracker;
 
     private HandlerThread mTestThread;
 
@@ -60,8 +68,9 @@
         mTestThread = new HandlerThread("CallAudioModeStateMachineTest");
         mTestThread.start();
         super.setUp();
-        when(mCallAudioManager.getCallAudioRouteStateMachine())
+        when(mCallAudioManager.getCallAudioRouteAdapter())
                 .thenReturn(mCallAudioRouteStateMachine);
+        when(mFeatureFlags.telecomResolveHiddenDependencies()).thenReturn(false);
     }
 
     @Override
@@ -76,7 +85,7 @@
     @Test
     public void testNoFocusWhenRingerSilenced() throws Throwable {
         CallAudioModeStateMachine sm = new CallAudioModeStateMachine(mSystemStateHelper,
-                mAudioManager, mTestThread.getLooper());
+                mAudioManager, mTestThread.getLooper(), mFeatureFlags, mCommunicationDeviceTracker);
         sm.setCallAudioManager(mCallAudioManager);
         sm.sendMessage(CallAudioModeStateMachine.ABANDON_FOCUS_FOR_TESTING);
         waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
@@ -108,7 +117,7 @@
     @Test
     public void testSwitchToStreamingMode() {
         CallAudioModeStateMachine sm = new CallAudioModeStateMachine(mSystemStateHelper,
-                mAudioManager, mTestThread.getLooper());
+                mAudioManager, mTestThread.getLooper(), mFeatureFlags, mCommunicationDeviceTracker);
         sm.setCallAudioManager(mCallAudioManager);
         sm.sendMessage(CallAudioModeStateMachine.ABANDON_FOCUS_FOR_TESTING);
         waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
@@ -117,7 +126,7 @@
         when(mCallAudioManager.startRinging()).thenReturn(false);
 
         sm.sendMessage(CallAudioModeStateMachine.START_CALL_STREAMING, new Builder()
-                .setHasActiveOrDialingCalls(false)
+                .setHasActiveOrDialingCalls(true)
                 .setHasRingingCalls(false)
                 .setHasHoldingCalls(false)
                 .setIsTonePlaying(false)
@@ -131,14 +140,14 @@
         assertEquals(CallAudioModeStateMachine.STREAMING_STATE_NAME, sm.getCurrentStateName());
 
         verify(mAudioManager, never()).requestAudioFocusForCall(anyInt(), anyInt());
-        verify(mAudioManager).setMode(eq(AudioManager.MODE_CALL_REDIRECT));
+        verify(mAudioManager).setMode(eq(AudioManager.MODE_COMMUNICATION_REDIRECT));
     }
 
     @SmallTest
     @Test
     public void testExitStreamingMode() {
         CallAudioModeStateMachine sm = new CallAudioModeStateMachine(mSystemStateHelper,
-                mAudioManager, mTestThread.getLooper());
+                mAudioManager, mTestThread.getLooper(), mFeatureFlags, mCommunicationDeviceTracker);
         sm.setCallAudioManager(mCallAudioManager);
         sm.sendMessage(CallAudioModeStateMachine.ENTER_STREAMING_FOCUS_FOR_TESTING);
         waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
@@ -166,7 +175,7 @@
     @Test
     public void testNoRingWhenDeviceIsAtEar() {
         CallAudioModeStateMachine sm = new CallAudioModeStateMachine(mSystemStateHelper,
-                mAudioManager, mTestThread.getLooper());
+                mAudioManager, mTestThread.getLooper(), mFeatureFlags, mCommunicationDeviceTracker);
         sm.setCallAudioManager(mCallAudioManager);
         sm.sendMessage(CallAudioModeStateMachine.ABANDON_FOCUS_FOR_TESTING);
         sm.sendMessage(CallAudioModeStateMachine.NEW_HOLDING_CALL, new Builder()
@@ -202,7 +211,7 @@
     @Test
     public void testRegainFocusWhenHfpIsConnectedSilenced() throws Throwable {
         CallAudioModeStateMachine sm = new CallAudioModeStateMachine(mSystemStateHelper,
-                mAudioManager, mTestThread.getLooper());
+                mAudioManager, mTestThread.getLooper(), mFeatureFlags, mCommunicationDeviceTracker);
         sm.setCallAudioManager(mCallAudioManager);
         sm.sendMessage(CallAudioModeStateMachine.ABANDON_FOCUS_FOR_TESTING);
         waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
@@ -246,7 +255,7 @@
     @Test
     public void testDoNotRingTwiceWhenHfpConnected() {
         CallAudioModeStateMachine sm = new CallAudioModeStateMachine(mSystemStateHelper,
-                mAudioManager, mTestThread.getLooper());
+                mAudioManager, mTestThread.getLooper(), mFeatureFlags, mCommunicationDeviceTracker);
         sm.setCallAudioManager(mCallAudioManager);
         sm.sendMessage(CallAudioModeStateMachine.ABANDON_FOCUS_FOR_TESTING);
         waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
@@ -284,7 +293,7 @@
     @Test
     public void testStartRingingAfterHfpConnectedIfNotAlreadyPlaying() {
         CallAudioModeStateMachine sm = new CallAudioModeStateMachine(mSystemStateHelper,
-                mAudioManager, mTestThread.getLooper());
+                mAudioManager, mTestThread.getLooper(), mFeatureFlags, mCommunicationDeviceTracker);
         sm.setCallAudioManager(mCallAudioManager);
         sm.sendMessage(CallAudioModeStateMachine.ABANDON_FOCUS_FOR_TESTING);
         waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
@@ -318,7 +327,46 @@
         verify(mCallAudioManager, times(2)).startRinging();
     }
 
+    @SmallTest
+    @Test
+    public void testAudioFocusRequestWithResolveHiddenDependencies() {
+        CallAudioModeStateMachine sm = new CallAudioModeStateMachine(mSystemStateHelper,
+                mAudioManager, mTestThread.getLooper(), mFeatureFlags, mCommunicationDeviceTracker);
+        when(mFeatureFlags.telecomResolveHiddenDependencies()).thenReturn(true);
+        ArgumentCaptor<AudioFocusRequest> captor = ArgumentCaptor.forClass(AudioFocusRequest.class);
+        sm.setCallAudioManager(mCallAudioManager);
+
+        resetMocks();
+        when(mCallAudioManager.startRinging()).thenReturn(true);
+        when(mCallAudioManager.isRingtonePlaying()).thenReturn(false);
+
+        sm.sendMessage(CallAudioModeStateMachine.ENTER_RING_FOCUS_FOR_TESTING);
+        waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
+        verify(mAudioManager).requestAudioFocus(captor.capture());
+        assertTrue(areAudioFocusRequestsMatch(captor.getValue(), RING_AUDIO_FOCUS_REQUEST));
+
+        sm.sendMessage(CallAudioModeStateMachine.ENTER_CALL_FOCUS_FOR_TESTING);
+        waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
+        verify(mAudioManager, atLeast(1)).requestAudioFocus(captor.capture());
+        AudioFocusRequest request = captor.getValue();
+        assertTrue(areAudioFocusRequestsMatch(request, CALL_AUDIO_FOCUS_REQUEST));
+
+        sm.sendMessage(CallAudioModeStateMachine.ABANDON_FOCUS_FOR_TESTING);
+    }
+
     private void resetMocks() {
         clearInvocations(mCallAudioManager, mAudioManager);
     }
+
+    private boolean areAudioFocusRequestsMatch(AudioFocusRequest r1, AudioFocusRequest r2) {
+        if ((r1 == null) || (r2 == null)) {
+            return false;
+        }
+
+        if (r1.getFocusGain() != r2.getFocusGain()) {
+            return false;
+        }
+
+        return r1.getAudioAttributes().equals(r2.getAudioAttributes());
+    }
 }
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioModeTransitionTests.java b/tests/src/com/android/server/telecom/tests/CallAudioModeTransitionTests.java
index c7e5aa9..3690d5f 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioModeTransitionTests.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioModeTransitionTests.java
@@ -16,10 +16,15 @@
 
 package com.android.server.telecom.tests;
 
+import static com.android.server.telecom.CallAudioModeStateMachine.CALL_AUDIO_FOCUS_REQUEST;
+import static com.android.server.telecom.CallAudioModeStateMachine.RING_AUDIO_FOCUS_REQUEST;
+
+import android.media.AudioFocusRequest;
 import android.media.AudioManager;
 import android.os.HandlerThread;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import com.android.server.telecom.CallAudioCommunicationDeviceTracker;
 import com.android.server.telecom.CallAudioManager;
 import com.android.server.telecom.CallAudioModeStateMachine;
 import com.android.server.telecom.CallAudioModeStateMachine.MessageArgs;
@@ -37,6 +42,7 @@
 import java.util.List;
 
 import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.nullable;
@@ -103,6 +109,7 @@
     @Mock private SystemStateHelper mSystemStateHelper;
     @Mock private AudioManager mAudioManager;
     @Mock private CallAudioManager mCallAudioManager;
+    @Mock private CallAudioCommunicationDeviceTracker mCommunicationDeviceTracker;
     private final ModeTestParameters mParams;
     private HandlerThread mTestThread;
 
@@ -130,13 +137,14 @@
     @SmallTest
     public void modeTransitionTest() {
         CallAudioModeStateMachine sm = new CallAudioModeStateMachine(mSystemStateHelper,
-                mAudioManager, mTestThread.getLooper());
+                mAudioManager, mTestThread.getLooper(), mFeatureFlags, mCommunicationDeviceTracker);
         sm.setCallAudioManager(mCallAudioManager);
         sm.sendMessage(mParams.initialAudioState);
         waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
 
         resetMocks();
         when(mCallAudioManager.startRinging()).thenReturn(true);
+        when(mFeatureFlags.telecomResolveHiddenDependencies()).thenReturn(false);
         if (mParams.initialAudioState
                 == CallAudioModeStateMachine.ENTER_AUDIO_PROCESSING_FOCUS_FOR_TESTING) {
             when(mAudioManager.getMode())
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRoutePeripheralAdapterTest.java b/tests/src/com/android/server/telecom/tests/CallAudioRoutePeripheralAdapterTest.java
index dfe1483..2fc6ec6 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRoutePeripheralAdapterTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRoutePeripheralAdapterTest.java
@@ -26,6 +26,7 @@
 
 import android.test.suitebuilder.annotation.SmallTest;
 
+import com.android.server.telecom.AsyncRingtonePlayer;
 import com.android.server.telecom.CallAudioRoutePeripheralAdapter;
 import com.android.server.telecom.CallAudioRouteStateMachine;
 import com.android.server.telecom.DockManager;
@@ -47,6 +48,7 @@
     @Mock private BluetoothRouteManager mBluetoothRouteManager;
     @Mock private WiredHeadsetManager mWiredHeadsetManager;
     @Mock private DockManager mDockManager;
+    @Mock private AsyncRingtonePlayer mRingtonePlayer;
 
     @Override
     @Before
@@ -57,7 +59,8 @@
                 mCallAudioRouteStateMachine,
                 mBluetoothRouteManager,
                 mWiredHeadsetManager,
-                mDockManager);
+                mDockManager,
+                mRingtonePlayer);
     }
 
     @Override
@@ -126,6 +129,16 @@
         mAdapter.onBluetoothAudioConnected();
         verify(mCallAudioRouteStateMachine).sendMessageWithSessionInfo(
                 CallAudioRouteStateMachine.BT_AUDIO_CONNECTED);
+        verify(mRingtonePlayer).updateBtActiveState(true);
+    }
+
+    @SmallTest
+    @Test
+    public void testOnBluetoothAudioConnecting() {
+        mAdapter.onBluetoothAudioConnecting();
+        verify(mCallAudioRouteStateMachine).sendMessageWithSessionInfo(
+                CallAudioRouteStateMachine.BT_AUDIO_CONNECTED);
+        verify(mRingtonePlayer).updateBtActiveState(false);
     }
 
     @SmallTest
@@ -134,6 +147,7 @@
         mAdapter.onBluetoothAudioDisconnected();
         verify(mCallAudioRouteStateMachine).sendMessageWithSessionInfo(
                 CallAudioRouteStateMachine.BT_AUDIO_DISCONNECTED);
+        verify(mRingtonePlayer).updateBtActiveState(false);
     }
 
     @SmallTest
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
index f53c953..1fa14a5 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
@@ -16,50 +16,12 @@
 
 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.AudioDeviceInfo;
-import android.media.AudioManager;
-import android.media.IAudioService;
-import android.os.HandlerThread;
-import android.telecom.CallAudioState;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import com.android.server.telecom.bluetooth.BluetoothRouteManager;
-import com.android.server.telecom.Call;
-import com.android.server.telecom.CallAudioRouteStateMachine;
-import com.android.server.telecom.CallsManager;
-import com.android.server.telecom.ConnectionServiceWrapper;
-import com.android.server.telecom.CallAudioManager;
-import com.android.server.telecom.StatusBarNotifier;
-import com.android.server.telecom.TelecomSystem;
-import com.android.server.telecom.WiredHeadsetManager;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Matchers.any;
@@ -75,6 +37,49 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.bluetooth.BluetoothDevice;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.media.AudioDeviceInfo;
+import android.media.AudioManager;
+import android.media.IAudioService;
+import android.os.HandlerThread;
+import android.telecom.CallAudioState;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallAudioCommunicationDeviceTracker;
+import com.android.server.telecom.CallAudioManager;
+import com.android.server.telecom.CallAudioRouteStateMachine;
+import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.ConnectionServiceWrapper;
+import com.android.server.telecom.StatusBarNotifier;
+import com.android.server.telecom.TelecomSystem;
+import com.android.server.telecom.WiredHeadsetManager;
+import com.android.server.telecom.bluetooth.BluetoothRouteManager;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
 @RunWith(JUnit4.class)
 public class CallAudioRouteStateMachineTest extends TelecomTestCase {
 
@@ -94,12 +99,14 @@
     @Mock Call fakeSelfManagedCall;
     @Mock Call fakeCall;
     @Mock CallAudioManager mockCallAudioManager;
+    @Mock BluetoothDevice mockWatchDevice;
 
     private CallAudioManager.AudioServiceFactory mAudioServiceFactory;
     private static final int TEST_TIMEOUT = 500;
     private AudioManager mockAudioManager;
     private final TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() { };
     private HandlerThread mThreadHandler;
+    CallAudioCommunicationDeviceTracker mCommunicationDeviceTracker;
 
     @Override
     @Before
@@ -110,6 +117,8 @@
         mThreadHandler.start();
         mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
         mockAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+        mCommunicationDeviceTracker = new CallAudioCommunicationDeviceTracker(mContext);
+        mCommunicationDeviceTracker.setBluetoothRouteManager(mockBluetoothRouteManager);
 
         mAudioServiceFactory = new CallAudioManager.AudioServiceFactory() {
             @Override
@@ -129,9 +138,12 @@
         when(fakeSelfManagedCall.isAlive()).thenReturn(true);
         when(fakeSelfManagedCall.getSupportedAudioRoutes()).thenReturn(CallAudioState.ROUTE_ALL);
         when(fakeSelfManagedCall.isSelfManaged()).thenReturn(true);
+        when(mFeatureFlags.transitRouteBeforeAudioDisconnectBt()).thenReturn(false);
 
         doNothing().when(mockConnectionServiceWrapper).onCallAudioStateChanged(any(Call.class),
                 any(CallAudioState.class));
+        when(mFeatureFlags.ignoreAutoRouteToWatchDevice()).thenReturn(false);
+        when(mFeatureFlags.callAudioCommunicationDeviceRefactor()).thenReturn(false);
     }
 
     @Override
@@ -153,7 +165,10 @@
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 CallAudioRouteStateMachine.EARPIECE_AUTO_DETECT,
-                mThreadHandler.getLooper());
+                mThreadHandler.getLooper(),
+                Runnable::run /** do async stuff sync for test purposes */,
+                mCommunicationDeviceTracker,
+                mFeatureFlags);
 
         // Since we don't know if we're on a platform with an earpiece or not, all we can do
         // is ensure the stateMachine construction didn't fail.  But at least we exercised the
@@ -172,7 +187,10 @@
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 CallAudioRouteStateMachine.EARPIECE_AUTO_DETECT,
-                mThreadHandler.getLooper());
+                mThreadHandler.getLooper(),
+                Runnable::run /** do async stuff sync for test purposes */,
+                mCommunicationDeviceTracker,
+                mFeatureFlags);
         stateMachine.setCallAudioManager(mockCallAudioManager);
 
         Set<Call> trackedCalls = new HashSet<>(Arrays.asList(fakeCall, fakeSelfManagedCall));
@@ -195,8 +213,8 @@
         stateMachine.sendMessageWithSessionInfo(
                 CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE);
 
-        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
-        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+        waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
+        waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
 
         // assert expected end state
         assertEquals(stateMachine.getCurrentCallAudioState().getRoute(),
@@ -206,6 +224,112 @@
                 .onCallAudioStateChanged(any(), any());
     }
 
+    @SmallTest
+    @Test
+    public void testSystemAudioStateIsNotUpdatedFlagOff() {
+        CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
+                mContext,
+                mockCallsManager,
+                mockBluetoothRouteManager,
+                mockWiredHeadsetManager,
+                mockStatusBarNotifier,
+                mAudioServiceFactory,
+                CallAudioRouteStateMachine.EARPIECE_AUTO_DETECT,
+                mThreadHandler.getLooper(),
+                Runnable::run /** do async stuff sync for test purposes */,
+                mCommunicationDeviceTracker,
+                mFeatureFlags);
+        stateMachine.setCallAudioManager(mockCallAudioManager);
+
+        Set<Call> trackedCalls = new HashSet<>(Arrays.asList(fakeCall, fakeSelfManagedCall));
+        when(mockCallsManager.getTrackedCalls()).thenReturn(trackedCalls);
+        when(mFeatureFlags.availableRoutesNeverUpdatedAfterSetSystemAudioState()).thenReturn(false);
+
+        // start state --> ROUTE_EARPIECE
+        CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_EARPIECE,
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER);
+        stateMachine.initialize(initState);
+
+        stateMachine.setCallAudioManager(mockCallAudioManager);
+
+        assertEquals(stateMachine.getCurrentCallAudioState().getRoute(),
+                CallAudioRouteStateMachine.ROUTE_EARPIECE);
+
+        // ROUTE_EARPIECE  --> ROUTE_SPEAKER
+        stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_SPEAKER,
+                CallAudioRouteStateMachine.SPEAKER_ON);
+
+        stateMachine.sendMessageWithSessionInfo(
+                CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE);
+
+        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+
+        CallAudioState expectedCallAudioState = stateMachine.getLastKnownCallAudioState();
+
+        // assert expected end state
+        assertEquals(stateMachine.getCurrentCallAudioState().getRoute(),
+                CallAudioRouteStateMachine.ROUTE_SPEAKER);
+        // should update the audio route on all tracked calls ...
+        verify(mockConnectionServiceWrapper, times(trackedCalls.size()))
+                .onCallAudioStateChanged(any(), any());
+
+        assertNotEquals(expectedCallAudioState, stateMachine.getCurrentCallAudioState());
+    }
+
+    @SmallTest
+    @Test
+    public void testSystemAudioStateIsUpdatedFlagOn() {
+        CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
+                mContext,
+                mockCallsManager,
+                mockBluetoothRouteManager,
+                mockWiredHeadsetManager,
+                mockStatusBarNotifier,
+                mAudioServiceFactory,
+                CallAudioRouteStateMachine.EARPIECE_AUTO_DETECT,
+                mThreadHandler.getLooper(),
+                Runnable::run /** do async stuff sync for test purposes */,
+                mCommunicationDeviceTracker,
+                mFeatureFlags);
+        stateMachine.setCallAudioManager(mockCallAudioManager);
+
+        Set<Call> trackedCalls = new HashSet<>(Arrays.asList(fakeCall, fakeSelfManagedCall));
+        when(mockCallsManager.getTrackedCalls()).thenReturn(trackedCalls);
+        when(mFeatureFlags.availableRoutesNeverUpdatedAfterSetSystemAudioState()).thenReturn(true);
+
+        // start state --> ROUTE_EARPIECE
+        CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_EARPIECE,
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER);
+        stateMachine.initialize(initState);
+
+        stateMachine.setCallAudioManager(mockCallAudioManager);
+
+        assertEquals(stateMachine.getCurrentCallAudioState().getRoute(),
+                CallAudioRouteStateMachine.ROUTE_EARPIECE);
+
+        // ROUTE_EARPIECE  --> ROUTE_SPEAKER
+        stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_SPEAKER,
+                CallAudioRouteStateMachine.SPEAKER_ON);
+
+        stateMachine.sendMessageWithSessionInfo(
+                CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE);
+
+        waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
+        waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
+
+        CallAudioState expectedCallAudioState = stateMachine.getLastKnownCallAudioState();
+
+        // assert expected end state
+        assertEquals(stateMachine.getCurrentCallAudioState().getRoute(),
+                CallAudioRouteStateMachine.ROUTE_SPEAKER);
+        // should update the audio route on all tracked calls ...
+        verify(mockConnectionServiceWrapper, times(trackedCalls.size()))
+                .onCallAudioStateChanged(any(), any());
+
+        assertEquals(expectedCallAudioState, stateMachine.getCurrentCallAudioState());
+    }
+
     @MediumTest
     @Test
     public void testStreamRingMuteChange() {
@@ -217,7 +341,10 @@
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
-                mThreadHandler.getLooper());
+                mThreadHandler.getLooper(),
+                Runnable::run /** do async stuff sync for test purposes */,
+                mCommunicationDeviceTracker,
+                mFeatureFlags);
         stateMachine.setCallAudioManager(mockCallAudioManager);
         CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_SPEAKER,
                 CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER);
@@ -247,6 +374,7 @@
             foundValid = true;
         }
         assertTrue(foundValid);
+        verify(mockBluetoothRouteManager, timeout(1000L)).getBluetoothAudioConnectedDevice();
     }
 
     @MediumTest
@@ -260,7 +388,10 @@
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
-                mThreadHandler.getLooper());
+                mThreadHandler.getLooper(),
+                Runnable::run /** do async stuff sync for test purposes */,
+                mCommunicationDeviceTracker,
+                mFeatureFlags);
 
         when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
         when(mockBluetoothRouteManager.isBluetoothAvailable()).thenReturn(true);
@@ -283,14 +414,14 @@
         CallAudioState expectedMiddleState = new CallAudioState(false,
                 CallAudioState.ROUTE_WIRED_HEADSET,
                 CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_SPEAKER);
-        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
-        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+        waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
+        waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
         verifyNewSystemCallAudioState(initState, expectedMiddleState);
         resetMocks();
 
         stateMachine.sendMessageWithSessionInfo(
                 CallAudioRouteStateMachine.DISCONNECT_WIRED_HEADSET);
-        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+        waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
         verifyNewSystemCallAudioState(expectedMiddleState, initState);
     }
 
@@ -305,7 +436,10 @@
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
-                mThreadHandler.getLooper());
+                mThreadHandler.getLooper(),
+                Runnable::run /** do async stuff sync for test purposes */,
+                mCommunicationDeviceTracker,
+                mFeatureFlags);
         stateMachine.setCallAudioManager(mockCallAudioManager);
 
         when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
@@ -326,7 +460,7 @@
                 CallAudioState.ROUTE_EARPIECE,
                 CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH);
 
-        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+        waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
         verifyNewSystemCallAudioState(initState, expectedEndState);
         resetMocks();
         stateMachine.sendMessageWithSessionInfo(
@@ -334,7 +468,7 @@
         stateMachine.sendMessageWithSessionInfo(
                 CallAudioRouteStateMachine.BT_ACTIVE_DEVICE_PRESENT);
 
-        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+        waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
         assertEquals(expectedEndState, stateMachine.getCurrentCallAudioState());
     }
 
@@ -349,7 +483,10 @@
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
-                mThreadHandler.getLooper());
+                mThreadHandler.getLooper(),
+                Runnable::run /** do async stuff sync for test purposes */,
+                mCommunicationDeviceTracker,
+                mFeatureFlags);
         stateMachine.setCallAudioManager(mockCallAudioManager);
         Collection<BluetoothDevice> availableDevices = Collections.singleton(bluetoothDevice1);
 
@@ -374,12 +511,12 @@
                 CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH,
                 null, availableDevices);
 
-        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+        waitForHandlerAction(stateMachine.getAdapterHandler(), 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);
+        waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
+        waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
         resetMocks();
 
         // Now, switch back to BT explicitly
@@ -397,9 +534,9 @@
                 CallAudioState.ROUTE_BLUETOOTH,
                 CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH,
                 bluetoothDevice1, availableDevices);
-        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+        waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
         // second wait needed for the BT_AUDIO_CONNECTED message
-        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+        waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
         verifyNewSystemCallAudioState(expectedMidState, expectedEndState);
 
         stateMachine.sendMessageWithSessionInfo(
@@ -409,9 +546,9 @@
         stateMachine.sendMessageWithSessionInfo(
                 CallAudioRouteStateMachine.BT_ACTIVE_DEVICE_PRESENT);
 
-        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+        waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
         // second wait needed for the BT_AUDIO_CONNECTED message
-        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+        waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
         // Verify that we're still on bluetooth.
         assertEquals(expectedEndState, stateMachine.getCurrentCallAudioState());
     }
@@ -427,7 +564,10 @@
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
-                mThreadHandler.getLooper());
+                mThreadHandler.getLooper(),
+                Runnable::run /** do async stuff sync for test purposes */,
+                mCommunicationDeviceTracker,
+                mFeatureFlags);
         stateMachine.setCallAudioManager(mockCallAudioManager);
 
         when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
@@ -442,13 +582,13 @@
 
         stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
                 CallAudioRouteStateMachine.RINGING_FOCUS);
-        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+        waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
 
         verify(mockBluetoothRouteManager, never()).connectBluetoothAudio(nullable(String.class));
 
         stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
                 CallAudioRouteStateMachine.ACTIVE_FOCUS);
-        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+        waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
         verify(mockBluetoothRouteManager, times(1)).connectBluetoothAudio(nullable(String.class));
     }
 
@@ -463,7 +603,10 @@
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
-                mThreadHandler.getLooper());
+                mThreadHandler.getLooper(),
+                Runnable::run /** do async stuff sync for test purposes */,
+                mCommunicationDeviceTracker,
+                mFeatureFlags);
         stateMachine.setCallAudioManager(mockCallAudioManager);
         setInBandRing(false);
         when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
@@ -477,7 +620,7 @@
                 CallAudioRouteStateMachine.RINGING_FOCUS);
         // Wait for the state machine to finish transiting to ActiveEarpiece before hooking up
         // bluetooth mocks
-        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+        waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
 
         when(mockBluetoothRouteManager.isBluetoothAvailable()).thenReturn(true);
         when(mockBluetoothRouteManager.getConnectedDevices())
@@ -486,7 +629,7 @@
                 CallAudioRouteStateMachine.BLUETOOTH_DEVICE_LIST_CHANGED);
         stateMachine.sendMessageWithSessionInfo(
                 CallAudioRouteStateMachine.BT_ACTIVE_DEVICE_PRESENT);
-        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+        waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
 
         verify(mockBluetoothRouteManager, never()).connectBluetoothAudio(null);
         CallAudioState expectedEndState = new CallAudioState(false,
@@ -497,14 +640,17 @@
 
         stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
                 CallAudioRouteStateMachine.ACTIVE_FOCUS);
-        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+        waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
         verify(mockBluetoothRouteManager, times(1)).connectBluetoothAudio(null);
 
         when(mockBluetoothRouteManager.getBluetoothAudioConnectedDevice())
                 .thenReturn(bluetoothDevice1);
         stateMachine.sendMessage(CallAudioRouteStateMachine.BT_AUDIO_CONNECTED);
-        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
-        verify(mockCallAudioManager, times(1)).onRingerModeChange();
+        waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
+        // It is possible that this will be called twice from ActiveBluetoothRoute#enter. The extra
+        // call to setBluetoothOn will trigger BT_AUDIO_CONNECTED, which also ends up invoking
+        // CallAudioManager#onRingerModeChange.
+        verify(mockCallAudioManager, atLeastOnce()).onRingerModeChange();
     }
 
     @SmallTest
@@ -518,7 +664,10 @@
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
-                mThreadHandler.getLooper());
+                mThreadHandler.getLooper(),
+                Runnable::run /** do async stuff sync for test purposes */,
+                mCommunicationDeviceTracker,
+                mFeatureFlags);
         stateMachine.setCallAudioManager(mockCallAudioManager);
         List<BluetoothDevice> availableDevices =
                 Arrays.asList(bluetoothDevice1, bluetoothDevice2, bluetoothDevice3);
@@ -544,17 +693,80 @@
 
         stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.USER_SWITCH_BLUETOOTH,
                 0, bluetoothDevice2.getAddress());
-        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+        waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
 
         verify(mockBluetoothRouteManager).connectBluetoothAudio(bluetoothDevice2.getAddress());
-        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+        waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
         CallAudioState expectedEndState = new CallAudioState(false,
                 CallAudioState.ROUTE_BLUETOOTH,
                 CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH,
                 bluetoothDevice2,
                 availableDevices);
 
-        verifyNewSystemCallAudioState(initState, expectedEndState);
+        assertEquals(expectedEndState, stateMachine.getCurrentCallAudioState());
+    }
+
+    @SmallTest
+    @Test
+    public void testCallDisconnectedWhenAudioRoutedToBluetooth() {
+        when(mFeatureFlags.callAudioCommunicationDeviceRefactor()).thenReturn(true);
+        CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
+                mContext,
+                mockCallsManager,
+                mockBluetoothRouteManager,
+                mockWiredHeadsetManager,
+                mockStatusBarNotifier,
+                mAudioServiceFactory,
+                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
+                mThreadHandler.getLooper(),
+                Runnable::run /** do async stuff sync for test purposes */,
+                mCommunicationDeviceTracker,
+                mFeatureFlags);
+        stateMachine.setCallAudioManager(mockCallAudioManager);
+        List<BluetoothDevice> availableDevices = Arrays.asList(bluetoothDevice1);
+
+        when(mockAudioManager.isSpeakerphoneOn()).thenReturn(false);
+        when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
+        when(mockBluetoothRouteManager.isBluetoothAvailable()).thenReturn(true);
+        when(mockBluetoothRouteManager.getConnectedDevices()).thenReturn(availableDevices);
+        when(mockBluetoothRouteManager.isInbandRingingEnabled()).thenReturn(true);
+        when(mFeatureFlags.transitRouteBeforeAudioDisconnectBt()).thenReturn(true);
+        doAnswer(invocation -> {
+            when(mockBluetoothRouteManager.getBluetoothAudioConnectedDevice())
+                    .thenReturn(bluetoothDevice1);
+            stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.BT_AUDIO_CONNECTED);
+            return null;
+        }).when(mockBluetoothRouteManager).connectBluetoothAudio(bluetoothDevice1.getAddress());
+        doAnswer(invocation -> {
+            when(mockBluetoothRouteManager.getBluetoothAudioConnectedDevice())
+                    .thenReturn(bluetoothDevice1);
+            stateMachine.sendMessageWithSessionInfo(
+                    CallAudioRouteStateMachine.BT_AUDIO_DISCONNECTED);
+            return null;
+        }).when(mockBluetoothRouteManager).disconnectAudio();
+
+        CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH,
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH, null,
+                availableDevices);
+        stateMachine.initialize(initState);
+
+        stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
+                CallAudioRouteStateMachine.ACTIVE_FOCUS);
+        waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
+
+        stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
+                CallAudioRouteStateMachine.NO_FOCUS, bluetoothDevice1.getAddress());
+        waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
+
+        verify(mockBluetoothRouteManager).disconnectAudio();
+        waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
+        CallAudioState expectedEndState = new CallAudioState(false,
+                CallAudioState.ROUTE_BLUETOOTH,
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH,
+                bluetoothDevice1,
+                availableDevices);
+
+        assertEquals(expectedEndState, stateMachine.getCurrentCallAudioState());
     }
 
     @SmallTest
@@ -568,7 +780,10 @@
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
-                mThreadHandler.getLooper());
+                mThreadHandler.getLooper(),
+                Runnable::run /** do async stuff sync for test purposes */,
+                mCommunicationDeviceTracker,
+                mFeatureFlags);
         stateMachine.setCallAudioManager(mockCallAudioManager);
         when(mockAudioManager.isSpeakerphoneOn()).thenReturn(false);
         CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_SPEAKER,
@@ -577,13 +792,13 @@
 
         // Raise a dock connect event.
         stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.CONNECT_DOCK);
-        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+        waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
         assertTrue(!stateMachine.isInActiveState());
         verify(mockAudioManager, never()).setSpeakerphoneOn(eq(true));
 
         // Raise a dock disconnect event.
         stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.DISCONNECT_DOCK);
-        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+        waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
         assertTrue(!stateMachine.isInActiveState());
         verify(mockAudioManager, never()).setSpeakerphoneOn(eq(false));
     }
@@ -599,7 +814,10 @@
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
-                mThreadHandler.getLooper());
+                mThreadHandler.getLooper(),
+                Runnable::run /** do async stuff sync for test purposes */,
+                mCommunicationDeviceTracker,
+                mFeatureFlags);
         stateMachine.setCallAudioManager(mockCallAudioManager);
 
         when(mockAudioManager.isSpeakerphoneOn()).thenReturn(false);
@@ -611,7 +829,7 @@
         // Switch to active, pretending that a call came in.
         stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
                 CallAudioRouteStateMachine.ACTIVE_FOCUS);
-        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+        waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
 
         // Make sure that we've successfully switched to the active speaker route and that we've
         // called setSpeakerOn
@@ -633,7 +851,10 @@
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
-                mThreadHandler.getLooper());
+                mThreadHandler.getLooper(),
+                Runnable::run /** do async stuff sync for test purposes */,
+                mCommunicationDeviceTracker,
+                mFeatureFlags);
         stateMachine.setCallAudioManager(mockCallAudioManager);
         List<BluetoothDevice> availableDevices =
                 Arrays.asList(bluetoothDevice1, bluetoothDevice2);
@@ -655,7 +876,7 @@
         // Switch to active, pretending that a call came in.
         stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
                 CallAudioRouteStateMachine.ACTIVE_FOCUS);
-        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+        waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
 
         // Make sure that we've successfully switched to the active BT route and that we've
         // called connectAudio on the right device.
@@ -666,6 +887,112 @@
 
     @SmallTest
     @Test
+    public void testSetAndClearEarpieceCommunicationDevice() {
+        when(mFeatureFlags.callAudioCommunicationDeviceRefactor()).thenReturn(true);
+        CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
+                mContext,
+                mockCallsManager,
+                mockBluetoothRouteManager,
+                mockWiredHeadsetManager,
+                mockStatusBarNotifier,
+                mAudioServiceFactory,
+                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
+                mThreadHandler.getLooper(),
+                Runnable::run /** do async stuff sync for test purposes */,
+                mCommunicationDeviceTracker,
+                mFeatureFlags);
+        stateMachine.setCallAudioManager(mockCallAudioManager);
+
+        AudioDeviceInfo earpiece = mock(AudioDeviceInfo.class);
+        when(earpiece.getType()).thenReturn(AudioDeviceInfo.TYPE_BUILTIN_EARPIECE);
+        when(earpiece.getAddress()).thenReturn("");
+        List<AudioDeviceInfo> devices = new ArrayList<>();
+        devices.add(earpiece);
+
+        when(mockAudioManager.getAvailableCommunicationDevices())
+                .thenReturn(devices);
+        when(mockAudioManager.setCommunicationDevice(eq(earpiece)))
+                .thenReturn(true);
+        when(mockAudioManager.getCommunicationDevice()).thenReturn(earpiece);
+
+        CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_EARPIECE,
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER |
+                        CallAudioState.ROUTE_WIRED_HEADSET);
+        stateMachine.initialize(initState);
+
+        // Switch to active
+        stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
+                CallAudioRouteStateMachine.ACTIVE_FOCUS);
+        waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
+
+        // Make sure that we've successfully switched to the active earpiece and that we set the
+        // communication device.
+        assertTrue(stateMachine.isInActiveState());
+        ArgumentCaptor<AudioDeviceInfo> infoArgumentCaptor = ArgumentCaptor.forClass(
+                AudioDeviceInfo.class);
+        verify(mockAudioManager).setCommunicationDevice(infoArgumentCaptor.capture());
+        assertEquals(AudioDeviceInfo.TYPE_BUILTIN_EARPIECE,
+                infoArgumentCaptor.getValue().getType());
+
+        // Route earpiece to speaker
+        stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_SPEAKER,
+                CallAudioRouteStateMachine.SPEAKER_ON);
+        waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
+
+        // Assert that communication device was cleared
+        verify(mockAudioManager).clearCommunicationDevice();
+    }
+
+    @SmallTest
+    @Test
+    public void testSetAndClearWiredHeadsetCommunicationDevice() {
+        when(mFeatureFlags.callAudioCommunicationDeviceRefactor()).thenReturn(true);
+        verifySetAndClearHeadsetCommunicationDevice(AudioDeviceInfo.TYPE_WIRED_HEADSET);
+    }
+
+    @SmallTest
+    @Test
+    public void testSetAndClearUsbHeadsetCommunicationDevice() {
+        when(mFeatureFlags.callAudioCommunicationDeviceRefactor()).thenReturn(true);
+        verifySetAndClearHeadsetCommunicationDevice(AudioDeviceInfo.TYPE_USB_HEADSET);
+    }
+
+    @SmallTest
+    @Test
+    public void testActiveFocusRouteSwitchFromQuiescentBluetooth() {
+        when(mFeatureFlags.callAudioCommunicationDeviceRefactor()).thenReturn(true);
+        CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
+                mContext,
+                mockCallsManager,
+                mockBluetoothRouteManager,
+                mockWiredHeadsetManager,
+                mockStatusBarNotifier,
+                mAudioServiceFactory,
+                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
+                mThreadHandler.getLooper(),
+                Runnable::run /** do async stuff sync for test purposes */,
+                mCommunicationDeviceTracker,
+                mFeatureFlags);
+        stateMachine.setCallAudioManager(mockCallAudioManager);
+
+        // Start the route in quiescent and ensure that a switch to ACTIVE_FOCUS transitions to
+        // the corresponding active route even when there aren't any active BT devices available.
+        CallAudioState initState = new CallAudioState(false,
+                CallAudioState.ROUTE_BLUETOOTH,
+                CallAudioState.ROUTE_BLUETOOTH | CallAudioState.ROUTE_EARPIECE);
+        stateMachine.initialize(initState);
+
+        // Switch to active
+        stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
+                CallAudioRouteStateMachine.ACTIVE_FOCUS);
+        waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
+
+        // Make sure that we've successfully switched to the active route on BT
+        assertTrue(stateMachine.isInActiveState());
+    }
+
+    @SmallTest
+    @Test
     public void testInitializationWithEarpieceNoHeadsetNoBluetooth() {
         CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_EARPIECE,
                 CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER);
@@ -748,7 +1075,10 @@
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
-                mThreadHandler.getLooper());
+                mThreadHandler.getLooper(),
+                Runnable::run /** do async stuff sync for test purposes */,
+                mCommunicationDeviceTracker,
+                mFeatureFlags);
         stateMachine.initialize();
         assertEquals(expectedState, stateMachine.getCurrentCallAudioState());
     }
@@ -764,7 +1094,10 @@
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
-                mThreadHandler.getLooper());
+                mThreadHandler.getLooper(),
+                Runnable::run /** do async stuff sync for test purposes */,
+                mCommunicationDeviceTracker,
+                mFeatureFlags);
         stateMachine.setCallAudioManager(mockCallAudioManager);
 
         CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_EARPIECE,
@@ -775,15 +1108,206 @@
         CallAudioState expectedEndState = new CallAudioState(false,
                 CallAudioState.ROUTE_STREAMING, CallAudioState.ROUTE_STREAMING);
 
-        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+        waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
         verifyNewSystemCallAudioState(initState, expectedEndState);
         resetMocks();
         stateMachine.sendMessageWithSessionInfo(
                 CallAudioRouteStateMachine.STREAMING_FORCE_DISABLED);
-        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+        waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
         assertEquals(initState, stateMachine.getCurrentCallAudioState());
     }
 
+    @SmallTest
+    @Test
+    public void testIgnoreSpeakerOffMessage() {
+        when(mockBluetoothRouteManager.isInbandRingingEnabled()).thenReturn(true);
+        when(mockBluetoothRouteManager.getBluetoothAudioConnectedDevice())
+                .thenReturn(bluetoothDevice1);
+        when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(true);
+        CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
+                mContext,
+                mockCallsManager,
+                mockBluetoothRouteManager,
+                mockWiredHeadsetManager,
+                mockStatusBarNotifier,
+                mAudioServiceFactory,
+                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
+                mThreadHandler.getLooper(),
+                Runnable::run /** do async stuff sync for test purposes */,
+                mCommunicationDeviceTracker,
+                mFeatureFlags);
+        stateMachine.setCallAudioManager(mockCallAudioManager);
+
+        CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_SPEAKER,
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER
+                        | CallAudioState.ROUTE_BLUETOOTH);
+        stateMachine.initialize(initState);
+
+        doAnswer(
+                (address) -> {
+                    stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SPEAKER_OFF);
+                    stateMachine.sendMessageDelayed(CallAudioRouteStateMachine.BT_AUDIO_CONNECTED,
+                            5000L);
+                    return null;
+        }).when(mockBluetoothRouteManager).connectBluetoothAudio(anyString());
+        stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
+                CallAudioRouteStateMachine.ACTIVE_FOCUS);
+        stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.USER_SWITCH_BLUETOOTH);
+
+        CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_SPEAKER,
+                CallAudioState.ROUTE_SPEAKER | CallAudioState.ROUTE_BLUETOOTH
+                        | CallAudioState.ROUTE_EARPIECE);
+        assertEquals(expectedState, stateMachine.getCurrentCallAudioState());
+    }
+
+    @MediumTest
+    @Test
+    public void testIgnoreImplicitBTSwitchWhenDeviceIsWatch() {
+        when(mFeatureFlags.ignoreAutoRouteToWatchDevice()).thenReturn(true);
+        when(mFeatureFlags.callAudioCommunicationDeviceRefactor()).thenReturn(true);
+        CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
+                mContext,
+                mockCallsManager,
+                mockBluetoothRouteManager,
+                mockWiredHeadsetManager,
+                mockStatusBarNotifier,
+                mAudioServiceFactory,
+                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
+                mThreadHandler.getLooper(),
+                Runnable::run /** do async stuff sync for test purposes */,
+                mCommunicationDeviceTracker,
+                mFeatureFlags);
+        stateMachine.setCallAudioManager(mockCallAudioManager);
+
+        AudioDeviceInfo headset = mock(AudioDeviceInfo.class);
+        when(headset.getType()).thenReturn(AudioDeviceInfo.TYPE_WIRED_HEADSET);
+        when(headset.getAddress()).thenReturn("");
+        List<AudioDeviceInfo> devices = new ArrayList<>();
+        devices.add(headset);
+
+        when(mockAudioManager.getAvailableCommunicationDevices())
+                .thenReturn(devices);
+        when(mockAudioManager.setCommunicationDevice(eq(headset)))
+                .thenReturn(true);
+        when(mockAudioManager.getCommunicationDevice()).thenReturn(headset);
+
+        CallAudioState initState = new CallAudioState(false,
+                CallAudioState.ROUTE_WIRED_HEADSET, CallAudioState.ROUTE_WIRED_HEADSET
+                | CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH);
+        stateMachine.initialize(initState);
+
+        // Switch to active
+        stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
+                CallAudioRouteStateMachine.ACTIVE_FOCUS);
+        waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
+
+        // Make sure that we've successfully switched to the active headset.
+        assertTrue(stateMachine.isInActiveState());
+        ArgumentCaptor<AudioDeviceInfo> infoArgumentCaptor = ArgumentCaptor.forClass(
+                AudioDeviceInfo.class);
+        verify(mockAudioManager).setCommunicationDevice(infoArgumentCaptor.capture());
+        assertEquals(AudioDeviceInfo.TYPE_WIRED_HEADSET, infoArgumentCaptor.getValue().getType());
+
+        // Set up watch device as only available BT device.
+        Collection<BluetoothDevice> availableDevices = Collections.singleton(mockWatchDevice);
+
+        when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
+        when(mockBluetoothRouteManager.isBluetoothAvailable()).thenReturn(true);
+        when(mockBluetoothRouteManager.getConnectedDevices()).thenReturn(availableDevices);
+        when(mockBluetoothRouteManager.isWatch(any(BluetoothDevice.class))).thenReturn(true);
+
+        // Disconnect wired headset to force switch to BT (verify that we ignore the implicit switch
+        // to BT when the watch is the only connected device and that we move into the next
+        // available route.
+        stateMachine.sendMessageWithSessionInfo(
+                CallAudioRouteStateMachine.DISCONNECT_WIRED_HEADSET);
+        waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
+        CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_EARPIECE,
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH,
+                null, availableDevices);
+        assertEquals(expectedState, stateMachine.getCurrentCallAudioState());
+    }
+
+    @SmallTest
+    @Test
+    public void testQuiescentBluetoothRouteResetMute() {
+        when(mFeatureFlags.resetMuteWhenEnteringQuiescentBtRoute()).thenReturn(true);
+        CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
+                mContext,
+                mockCallsManager,
+                mockBluetoothRouteManager,
+                mockWiredHeadsetManager,
+                mockStatusBarNotifier,
+                mAudioServiceFactory,
+                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
+                mThreadHandler.getLooper(),
+                Runnable::run /** do async stuff sync for test purposes */,
+                mCommunicationDeviceTracker,
+                mFeatureFlags);
+        stateMachine.setCallAudioManager(mockCallAudioManager);
+
+        CallAudioState initState = new CallAudioState(false,
+                CallAudioState.ROUTE_BLUETOOTH, CallAudioState.ROUTE_SPEAKER
+                | CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH);
+        stateMachine.initialize(initState);
+
+        // Switch to active and mute
+        stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
+                CallAudioRouteStateMachine.ACTIVE_FOCUS);
+        stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.BT_AUDIO_CONNECTED);
+        waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
+        assertTrue(stateMachine.isInActiveState());
+
+        stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.MUTE_ON);
+        waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
+        CallAudioState expectedState = new CallAudioState(true,
+                CallAudioState.ROUTE_BLUETOOTH, CallAudioState.ROUTE_SPEAKER
+                | CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH);
+        assertEquals(expectedState, stateMachine.getCurrentCallAudioState());
+
+        stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
+                CallAudioRouteStateMachine.NO_FOCUS);
+        waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
+
+        expectedState = new CallAudioState(false,
+                CallAudioState.ROUTE_BLUETOOTH, CallAudioState.ROUTE_SPEAKER
+                | CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH);
+        // TODO: Re-enable this part of the test; this is now failing because we have to
+        // revert ag/23783145.
+        // assertEquals(expectedState, stateMachine.getCurrentCallAudioState());
+    }
+
+    @SmallTest
+    @Test
+    public void testSupportRouteMaskUpdateWhenBtAudioConnected() {
+        when(mFeatureFlags.updateRouteMaskWhenBtConnected()).thenReturn(true);
+        CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
+                mContext,
+                mockCallsManager,
+                mockBluetoothRouteManager,
+                mockWiredHeadsetManager,
+                mockStatusBarNotifier,
+                mAudioServiceFactory,
+                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
+                mThreadHandler.getLooper(),
+                Runnable::run /** do async stuff sync for test purposes */,
+                mCommunicationDeviceTracker,
+                mFeatureFlags);
+        stateMachine.setCallAudioManager(mockCallAudioManager);
+
+        CallAudioState initState = new CallAudioState(false,
+                CallAudioState.ROUTE_EARPIECE,
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER);
+        stateMachine.initialize(initState);
+
+        stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.BT_AUDIO_CONNECTED);
+        waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
+        CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH,
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER
+                        | CallAudioState.ROUTE_BLUETOOTH);
+        assertEquals(expectedState, stateMachine.getCurrentCallAudioState());
+    }
+
     private void initializationTestHelper(CallAudioState expectedState,
             int earpieceControl) {
         when(mockWiredHeadsetManager.isPluggedIn()).thenReturn(
@@ -801,7 +1325,10 @@
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 earpieceControl,
-                mThreadHandler.getLooper());
+                mThreadHandler.getLooper(),
+                Runnable::run /** do async stuff sync for test purposes */,
+                mCommunicationDeviceTracker,
+                mFeatureFlags);
         stateMachine.initialize();
         assertEquals(expectedState, stateMachine.getCurrentCallAudioState());
     }
@@ -841,4 +1368,59 @@
         doNothing().when(mockConnectionServiceWrapper).onCallAudioStateChanged(any(Call.class),
                 any(CallAudioState.class));
     }
+
+    private void verifySetAndClearHeadsetCommunicationDevice(int audioType) {
+        CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
+                mContext,
+                mockCallsManager,
+                mockBluetoothRouteManager,
+                mockWiredHeadsetManager,
+                mockStatusBarNotifier,
+                mAudioServiceFactory,
+                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
+                mThreadHandler.getLooper(),
+                Runnable::run /** do async stuff sync for test purposes */,
+                mCommunicationDeviceTracker,
+                mFeatureFlags);
+        stateMachine.setCallAudioManager(mockCallAudioManager);
+
+        AudioDeviceInfo headset = mock(AudioDeviceInfo.class);
+        when(headset.getType()).thenReturn(audioType);
+        when(headset.getAddress()).thenReturn("");
+        List<AudioDeviceInfo> devices = new ArrayList<>();
+        devices.add(headset);
+
+        when(mockAudioManager.getAvailableCommunicationDevices())
+                .thenReturn(devices);
+        when(mockAudioManager.setCommunicationDevice(eq(headset)))
+                .thenReturn(true);
+        when(mockAudioManager.getCommunicationDevice()).thenReturn(headset);
+
+        CallAudioState initState = new CallAudioState(false,
+                CallAudioState.ROUTE_WIRED_HEADSET,
+                CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_EARPIECE);
+        stateMachine.initialize(initState);
+
+        // Switch to active
+        stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
+                CallAudioRouteStateMachine.ACTIVE_FOCUS);
+        waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
+
+        // Make sure that we've successfully switched to the active headset and that we set the
+        // communication device.
+        assertTrue(stateMachine.isInActiveState());
+        ArgumentCaptor<AudioDeviceInfo> infoArgumentCaptor = ArgumentCaptor.forClass(
+                AudioDeviceInfo.class);
+        verify(mockAudioManager).setCommunicationDevice(infoArgumentCaptor.capture());
+        assertEquals(audioType, infoArgumentCaptor.getValue().getType());
+
+        // Route out of headset route
+        stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
+                CallAudioRouteStateMachine.ACTIVE_FOCUS);
+        stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.USER_SWITCH_EARPIECE);
+        waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
+
+        // Assert that communication device was cleared
+        verify(mockAudioManager).clearCommunicationDevice();
+    }
 }
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java
index 5caa432..25c4e9f 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java
@@ -20,6 +20,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.ArgumentMatchers.same;
+import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
@@ -40,6 +41,7 @@
 import android.test.suitebuilder.annotation.SmallTest;
 
 import com.android.server.telecom.Call;
+import com.android.server.telecom.CallAudioCommunicationDeviceTracker;
 import com.android.server.telecom.CallAudioManager;
 import com.android.server.telecom.CallAudioRouteStateMachine;
 import com.android.server.telecom.CallsManager;
@@ -155,6 +157,7 @@
     @Mock StatusBarNotifier mockStatusBarNotifier;
     @Mock Call fakeCall;
     @Mock CallAudioManager mockCallAudioManager;
+    private CallAudioCommunicationDeviceTracker mCommunicationDeviceTracker;
     private CallAudioManager.AudioServiceFactory mAudioServiceFactory;
     private static final int TEST_TIMEOUT = 500;
     private AudioManager mockAudioManager;
@@ -174,6 +177,8 @@
         mHandlerThread.start();
         mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
         mockAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+        mCommunicationDeviceTracker = new CallAudioCommunicationDeviceTracker(mContext);
+        mCommunicationDeviceTracker.setBluetoothRouteManager(mockBluetoothRouteManager);
 
         mAudioServiceFactory = new CallAudioManager.AudioServiceFactory() {
             @Override
@@ -269,7 +274,10 @@
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 mParams.earpieceControl,
-                mHandlerThread.getLooper());
+                mHandlerThread.getLooper(),
+                Runnable::run /** do async stuff sync for test purposes */,
+                mCommunicationDeviceTracker,
+                mFeatureFlags);
         stateMachine.setCallAudioManager(mockCallAudioManager);
 
         setupMocksForParams(stateMachine, mParams);
@@ -287,17 +295,17 @@
         if (mParams.initialRoute == CallAudioState.ROUTE_BLUETOOTH) {
             stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.BT_AUDIO_CONNECTED);
         }
-        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+        waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
 
         // Clear invocations on mocks to discard stuff from initialization
         clearInvocations();
 
         sendActionToStateMachine(stateMachine);
 
-        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
-        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+        waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
+        waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
 
-        Handler h = stateMachine.getHandler();
+        Handler h = stateMachine.getAdapterHandler();
         waitForHandlerAction(h, TEST_TIMEOUT);
         stateMachine.quitStateMachine();
 
@@ -310,7 +318,7 @@
                 break;
             case ON:
                 if (mParams.expectedBluetoothDevice == null) {
-                    verify(mockBluetoothRouteManager).connectBluetoothAudio(null);
+                    verify(mockBluetoothRouteManager, atLeastOnce()).connectBluetoothAudio(null);
                 } else {
                     verify(mockBluetoothRouteManager).connectBluetoothAudio(
                             mParams.expectedBluetoothDevice.getAddress());
@@ -365,7 +373,10 @@
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 mParams.earpieceControl,
-                mHandlerThread.getLooper());
+                mHandlerThread.getLooper(),
+                Runnable::run /** do async stuff sync for test purposes */,
+                mCommunicationDeviceTracker,
+                mFeatureFlags);
         stateMachine.setCallAudioManager(mockCallAudioManager);
 
         // Set up bluetooth and speakerphone state
@@ -386,8 +397,8 @@
         // Omit the focus-getting statement
         sendActionToStateMachine(stateMachine);
 
-        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
-        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+        waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
+        waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
 
         stateMachine.quitStateMachine();
 
diff --git a/tests/src/com/android/server/telecom/tests/CallEndpointControllerTest.java b/tests/src/com/android/server/telecom/tests/CallEndpointControllerTest.java
index f4008aa..9101a19 100644
--- a/tests/src/com/android/server/telecom/tests/CallEndpointControllerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallEndpointControllerTest.java
@@ -17,6 +17,7 @@
 package com.android.server.telecom.tests;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
@@ -50,7 +51,9 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 @RunWith(JUnit4.class)
 public class CallEndpointControllerTest extends TelecomTestCase {
@@ -81,6 +84,9 @@
             availableBluetooth1);
     private static final CallAudioState audioState7 = new CallAudioState(false,
             CallAudioState.ROUTE_STREAMING, CallAudioState.ROUTE_ALL, null, availableBluetooth1);
+    private static final CallAudioState audioState8 = new CallAudioState(false,
+            CallAudioState.ROUTE_EARPIECE, CallAudioState.ROUTE_ALL, bluetoothDevice1,
+            availableBluetooth2);
 
     private CallEndpointController mCallEndpointController;
 
@@ -177,6 +183,74 @@
         verify(mConnectionService, never()).onMuteStateChanged(any(), anyBoolean());
     }
 
+    /**
+     * Ensure that {@link CallAudioManager#setAudioRoute(int, String)} is invoked when the user
+     * requests to switch to a bluetooth CallEndpoint.  This is an edge case where bluetooth is not
+     * the current CallEndpoint but the CallAudioState shows the bluetooth device is
+     * active/available.
+     */
+    @Test
+    public void testSwitchFromEarpieceToBluetooth() {
+        // simulate an audio state where the EARPIECE is active but a bluetooth device is active.
+        mCallEndpointController.onCallAudioStateChanged(null, audioState8 /* Ear but BT active */);
+        CallEndpoint btEndpoint = mCallEndpointController.getAvailableEndpoints().stream()
+                .filter(e -> e.getEndpointType() == CallEndpoint.TYPE_BLUETOOTH)
+                .toList().get(0); // get the only available BT endpoint
+
+        // verify the CallEndpointController shows EARPIECE active + BT endpoint is active device
+        assertEquals(CallEndpoint.TYPE_EARPIECE,
+                mCallEndpointController.getCurrentCallEndpoint().getEndpointType());
+        assertNotNull(btEndpoint);
+
+        // request an endpoint change from earpiece to the bluetooth
+        doReturn(audioState8).when(mCallAudioManager).getCallAudioState();
+        mCallEndpointController.requestCallEndpointChange(btEndpoint, mResultReceiver);
+
+        // verify the transaction was successful and CallAudioManager#setAudioRoute was called
+        verify(mResultReceiver, never()).send(eq(CallEndpoint.ENDPOINT_OPERATION_FAILED), any());
+        verify(mCallAudioManager, times(1)).setAudioRoute(eq(CallAudioState.ROUTE_BLUETOOTH),
+                eq(bluetoothDevice1.getAddress()));
+    }
+
+
+    /**
+     * Ensure that {@link CallAudioManager#setAudioRoute(int, String)} is invoked when the user
+     * requests to switch to from one bluetooth device to another.
+     */
+    @Test
+    public void testBtDeviceSwitch() {
+        // bluetoothDevice1 should start as active and bluetoothDevice2 is available
+        mCallEndpointController.onCallAudioStateChanged(null, audioState2 /* BT active D1 */);
+        CallEndpoint currentEndpoint = mCallEndpointController.getCurrentCallEndpoint();
+        List<CallEndpoint> btEndpoints = mCallEndpointController.getAvailableEndpoints().stream()
+                .filter(e -> e.getEndpointType() == CallEndpoint.TYPE_BLUETOOTH)
+                .toList(); // get the only available BT endpoint
+
+        // verify the initial state of the test
+        assertEquals(2, btEndpoints.size());
+        assertEquals(CallEndpoint.TYPE_BLUETOOTH, currentEndpoint.getEndpointType());
+
+        CallEndpoint otherBluetoothEndpoint = null;
+        for (CallEndpoint e : btEndpoints) {
+            if (!e.equals(currentEndpoint)) {
+                otherBluetoothEndpoint = e;
+            }
+        }
+
+        assertNotNull(otherBluetoothEndpoint);
+        assertNotEquals(currentEndpoint, otherBluetoothEndpoint);
+
+        // request an endpoint change from BT D1 --> BT D2
+        doReturn(audioState2).when(mCallAudioManager).getCallAudioState();
+        mCallEndpointController.requestCallEndpointChange(otherBluetoothEndpoint, mResultReceiver);
+
+        // verify the transaction was successful and CallAudioManager#setAudioRoute was called
+        verify(mResultReceiver, never()).send(eq(CallEndpoint.ENDPOINT_OPERATION_FAILED), any());
+        verify(mCallAudioManager, times(1))
+                .setAudioRoute(eq(CallAudioState.ROUTE_BLUETOOTH),
+                        eq(bluetoothDevice2.getAddress()));
+    }
+
     @Test
     public void testAvailableEndpointChanged() throws Exception {
         mCallEndpointController.onCallAudioStateChanged(audioState1, audioState6);
diff --git a/tests/src/com/android/server/telecom/tests/CallExtrasTest.java b/tests/src/com/android/server/telecom/tests/CallExtrasTest.java
index 926d740..cf44cfe 100644
--- a/tests/src/com/android/server/telecom/tests/CallExtrasTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallExtrasTest.java
@@ -370,7 +370,7 @@
     @LargeTest
     @Test
     public void testConferenceSetExtras() throws Exception {
-        ParcelableCall call = makeConferenceCall();
+        ParcelableCall call = makeConferenceCall(null, null);
         String conferenceId = call.getId();
 
         Conference conference = mConnectionServiceFixtureA.mLatestConference;
@@ -414,7 +414,7 @@
     @FlakyTest(bugId = 117751305)
     @Test
     public void testConferenceExtraOperations() throws Exception {
-        ParcelableCall call = makeConferenceCall();
+        ParcelableCall call = makeConferenceCall(null, null);
         String conferenceId = call.getId();
         Conference conference = mConnectionServiceFixtureA.mLatestConference;
         assertNotNull(conference);
@@ -450,7 +450,7 @@
     @LargeTest
     @Test
     public void testConferenceICS() throws Exception {
-        ParcelableCall call = makeConferenceCall();
+        ParcelableCall call = makeConferenceCall(null, null);
         String conferenceId = call.getId();
         Conference conference = mConnectionServiceFixtureA.mLatestConference;
 
diff --git a/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java b/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java
index b9f5667..c09d138 100644
--- a/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java
@@ -23,6 +23,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
@@ -36,6 +37,7 @@
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
 import android.content.res.Resources;
 import android.location.Country;
@@ -65,6 +67,7 @@
 import androidx.test.filters.FlakyTest;
 
 import com.android.server.telecom.Analytics;
+import com.android.server.telecom.AnomalyReporterAdapter;
 import com.android.server.telecom.Call;
 import com.android.server.telecom.CallLogManager;
 import com.android.server.telecom.CallState;
@@ -72,6 +75,7 @@
 import com.android.server.telecom.MissedCallNotifier;
 import com.android.server.telecom.PhoneAccountRegistrar;
 import com.android.server.telecom.TelephonyUtil;
+import com.android.server.telecom.flags.FeatureFlags;
 
 import org.junit.After;
 import org.junit.Before;
@@ -123,6 +127,11 @@
     PhoneAccountRegistrar mMockPhoneAccountRegistrar;
     @Mock
     MissedCallNotifier mMissedCallNotifier;
+    @Mock
+    AnomalyReporterAdapter mAnomalyReporterAdapter;
+
+    @Mock
+    FeatureFlags mFeatureFlags;
 
     @Override
     @Before
@@ -130,7 +139,7 @@
         super.setUp();
         mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
         mCallLogManager = new CallLogManager(mContext, mMockPhoneAccountRegistrar,
-                mMissedCallNotifier);
+                mMissedCallNotifier, mAnomalyReporterAdapter, mFeatureFlags);
         mDefaultAccountHandle = new PhoneAccountHandle(
                 new ComponentName("com.android.server.telecom.tests", "CallLogManagerTest"),
                 TEST_PHONE_ACCOUNT_ID,
@@ -181,6 +190,9 @@
         when(userManager.getUserInfo(eq(CURRENT_USER_ID))).thenReturn(userInfo);
         when(userManager.getUserInfo(eq(OTHER_USER_ID))).thenReturn(otherUserInfo);
         when(userManager.getUserInfo(eq(MANAGED_USER_ID))).thenReturn(managedProfileUserInfo);
+        PackageManager packageManager = mContext.getPackageManager();
+        when(packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)).thenReturn(false);
+        when(mFeatureFlags.telecomLogExternalWearableCalls()).thenReturn(false);
     }
 
     @Override
@@ -357,6 +369,7 @@
                 VIA_NUMBER_STRING, // viaNumber
                 null
         );
+        when(mFeatureFlags.addCallUriForMissedCalls()).thenReturn(true);
         mCallLogManager.onCallStateChanged(fakeIncomingCall, CallState.ACTIVE,
                 CallState.DISCONNECTED);
         ContentValues insertedValues = verifyInsertionWithCapture(CURRENT_USER_ID);
@@ -366,7 +379,7 @@
 
     @MediumTest
     @Test
-    public void testLogCallDirectionMissed() {
+    public void testLogCallDirectionMissedAddCallUriForMissedCallsFlagOff() {
         when(mMockPhoneAccountRegistrar.getPhoneAccountUnchecked(any(PhoneAccountHandle.class)))
                 .thenReturn(makeFakePhoneAccount(mDefaultAccountHandle, CURRENT_USER_ID));
         Call fakeMissedCall = makeFakeCall(
@@ -382,6 +395,7 @@
                 VIA_NUMBER_STRING, // viaNumber
                 null
         );
+        when(mFeatureFlags.addCallUriForMissedCalls()).thenReturn(false);
 
         mCallLogManager.onCallStateChanged(fakeMissedCall, CallState.ACTIVE,
                 CallState.DISCONNECTED);
@@ -390,7 +404,39 @@
                 Integer.valueOf(CallLog.Calls.MISSED_TYPE));
         // Timeout needed because showMissedCallNotification is called from onPostExecute.
         verify(mMissedCallNotifier, timeout(TEST_TIMEOUT_MILLIS))
-                .showMissedCallNotification(any(MissedCallNotifier.CallInfo.class));
+                .showMissedCallNotification(any(MissedCallNotifier.CallInfo.class),
+                        /* uri= */ eq(null));
+    }
+
+    @MediumTest
+    @Test
+    public void testLogCallDirectionMissedAddCallUriForMissedCallsFlagOn() {
+        when(mMockPhoneAccountRegistrar.getPhoneAccountUnchecked(any(PhoneAccountHandle.class)))
+                .thenReturn(makeFakePhoneAccount(mDefaultAccountHandle, CURRENT_USER_ID));
+        Call fakeMissedCall = makeFakeCall(
+                DisconnectCause.MISSED, // disconnectCauseCode
+                false, // isConference
+                true, // isIncoming
+                1L, // creationTimeMillis
+                1000L, // ageMillis
+                TEL_PHONEHANDLE, // callHandle
+                mDefaultAccountHandle, // phoneAccountHandle
+                NO_VIDEO_STATE, // callVideoState
+                POST_DIAL_STRING, // postDialDigits
+                VIA_NUMBER_STRING, // viaNumber
+                null
+        );
+        when(mFeatureFlags.addCallUriForMissedCalls()).thenReturn(true);
+
+        mCallLogManager.onCallStateChanged(fakeMissedCall, CallState.ACTIVE,
+                CallState.DISCONNECTED);
+        ContentValues insertedValues = verifyInsertionWithCapture(CURRENT_USER_ID);
+        assertEquals(insertedValues.getAsInteger(CallLog.Calls.TYPE),
+                Integer.valueOf(CallLog.Calls.MISSED_TYPE));
+        // Timeout needed because showMissedCallNotification is called from onPostExecute.
+        verify(mMissedCallNotifier, timeout(TEST_TIMEOUT_MILLIS))
+                .showMissedCallNotification(any(MissedCallNotifier.CallInfo.class),
+                        /* uri= */ any(Uri.class));
     }
 
     @MediumTest
@@ -788,6 +834,34 @@
         assertEquals(1, insertedValues.getAsInteger(Calls.IS_READ).intValue());
     }
 
+    @Test
+    public void testLogCallWhenExternalCallOnWatch() {
+        when(mMockPhoneAccountRegistrar.getPhoneAccountUnchecked(any(PhoneAccountHandle.class)))
+                .thenReturn(makeFakePhoneAccount(mDefaultAccountHandle, CURRENT_USER_ID));
+        PackageManager packageManager = mContext.getPackageManager();
+        when(packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)).thenReturn(true);
+        when(mFeatureFlags.telecomLogExternalWearableCalls()).thenReturn(true);
+        Call fakeMissedCall = makeFakeCall(
+                DisconnectCause.REJECTED, // 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
+                null
+        );
+        when(fakeMissedCall.isExternalCall()).thenReturn(true);
+
+        mCallLogManager.onCallStateChanged(fakeMissedCall, CallState.ACTIVE,
+                CallState.DISCONNECTED);
+        verifyInsertionWithCapture(CURRENT_USER_ID);
+    }
+
+
     @SmallTest
     @Test
     public void testCountryIso_setCache() {
@@ -893,6 +967,56 @@
 
     @SmallTest
     @Test
+    public void testDoNotLogCallExtra() {
+        when(mFeatureFlags.telecomSkipLogBasedOnExtra()).thenReturn(true);
+        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)
+        );
+        Bundle extras = new Bundle();
+        extras.putBoolean(TelecomManager.EXTRA_DO_NOT_LOG_CALL, true);
+        when(fakeCall.getExtras()).thenReturn(extras);
+
+        assertFalse(mCallLogManager.shouldLogDisconnectedCall(fakeCall, CallState.DISCONNECTED,
+                false /* isCanceled */));
+    }
+
+    @SmallTest
+    @Test
+    public void testIgnoresDoNotLogCallExtra_whenFlagDisabled() {
+        when(mFeatureFlags.telecomSkipLogBasedOnExtra()).thenReturn(false);
+        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)
+        );
+        Bundle extras = new Bundle();
+        extras.putBoolean(TelecomManager.EXTRA_DO_NOT_LOG_CALL, true);
+        when(fakeCall.getExtras()).thenReturn(extras);
+
+        assertTrue(mCallLogManager.shouldLogDisconnectedCall(fakeCall, CallState.DISCONNECTED,
+                false /* isCanceled */));
+    }
+
+    @SmallTest
+    @Test
     public void testDoNotLogConferenceWithChildren() {
         Call fakeCall = makeFakeCall(
                 DisconnectCause.LOCAL, // disconnectCauseCode
@@ -1062,7 +1186,7 @@
         when(fakeCall.getVideoStateHistory()).thenReturn(callVideoState);
         when(fakeCall.getPostDialDigits()).thenReturn(postDialDigits);
         when(fakeCall.getViaNumber()).thenReturn(viaNumber);
-        when(fakeCall.getInitiatingUser()).thenReturn(initiatingUser);
+        when(fakeCall.getAssociatedUser()).thenReturn(initiatingUser);
         when(fakeCall.getCallDataUsage()).thenReturn(callDataUsage);
         when(fakeCall.isEmergencyCall()).thenReturn(
                 phoneAccountHandle.equals(EMERGENCY_ACCT_HANDLE));
diff --git a/tests/src/com/android/server/telecom/tests/CallRedirectionProcessorTest.java b/tests/src/com/android/server/telecom/tests/CallRedirectionProcessorTest.java
index 51d5960..01446d1 100644
--- a/tests/src/com/android/server/telecom/tests/CallRedirectionProcessorTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallRedirectionProcessorTest.java
@@ -354,4 +354,28 @@
         assertEquals(REDIRECTED_GATEWAY_NUMBER_WITH_POST_DIAL,
                 gatewayInfoArgumentCaptor.getValue().getGatewayAddress());
     }
+
+    @Test
+    public void testUnbindOnNullBind() throws Exception {
+        startProcessWithNoGateWayInfo();
+        // To make sure tests are not flaky, clean all the previous handler messages
+        waitForHandlerAction(mProcessor.getHandler(), HANDLER_TIMEOUT_DELAY);
+        enableUserDefinedCallRedirectionService();
+        disableCarrierCallRedirectionService();
+
+        mProcessor.performCallRedirection(UserHandle.CURRENT);
+
+        // Capture the binder
+        ArgumentCaptor<ServiceConnection> serviceConnectionCaptor = ArgumentCaptor.forClass(
+                ServiceConnection.class);
+        // Verify binding occurred
+        verify(mContext, times(1)).bindServiceAsUser(any(Intent.class),
+                serviceConnectionCaptor.capture(), anyInt(), eq(UserHandle.CURRENT));
+        // Simulate null return from onBind
+        serviceConnectionCaptor.getValue().onNullBinding(USER_DEFINED_SERVICE_TEST_COMPONENT_NAME);
+
+        // Verify service was unbound
+        verify(mContext, times(1)).
+                unbindService(any(ServiceConnection.class));
+    }
 }
diff --git a/tests/src/com/android/server/telecom/tests/CallScreeningServiceFilterTest.java b/tests/src/com/android/server/telecom/tests/CallScreeningServiceFilterTest.java
index d95a0e2..4d8d497 100644
--- a/tests/src/com/android/server/telecom/tests/CallScreeningServiceFilterTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallScreeningServiceFilterTest.java
@@ -129,7 +129,7 @@
 
         when(mCallsManager.getCurrentUserHandle()).thenReturn(UserHandle.CURRENT);
         when(mCall.getId()).thenReturn(CALL_ID);
-        when(mCall.getUserHandleFromTargetPhoneAccount()).
+        when(mCall.getAssociatedUser()).
                 thenReturn(PA_HANDLE.getUserHandle());
         when(mContext.getPackageManager()).thenReturn(mPackageManager);
         when(mContext.getSystemService(TelecomManager.class))
diff --git a/tests/src/com/android/server/telecom/tests/CallTest.java b/tests/src/com/android/server/telecom/tests/CallTest.java
index 6b817d8..7a77374 100644
--- a/tests/src/com/android/server/telecom/tests/CallTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallTest.java
@@ -21,29 +21,25 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 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.argThat;
 import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.isA;
-import static org.mockito.Mockito.atLeastOnce;
 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.times;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
 
 import android.content.ComponentName;
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.graphics.Bitmap;
 import android.graphics.drawable.ColorDrawable;
 import android.net.Uri;
 import android.os.Bundle;
-import android.os.Parcel;
+import android.os.UserHandle;
+import android.telecom.CallAttributes;
 import android.telecom.CallerInfo;
 import android.telecom.Connection;
 import android.telecom.DisconnectCause;
@@ -56,12 +52,10 @@
 import android.telecom.VideoProfile;
 import android.telephony.CallQuality;
 import android.test.suitebuilder.annotation.SmallTest;
-import android.util.Log;
 import android.widget.Toast;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
-import com.android.internal.telecom.IVideoProvider;
 import com.android.server.telecom.Call;
 import com.android.server.telecom.CallIdMapper;
 import com.android.server.telecom.CallState;
@@ -69,8 +63,6 @@
 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;
@@ -126,6 +118,7 @@
         doReturn(new ComponentName(mContext, CallTest.class))
                 .when(mMockConnectionService).getComponentName();
         doReturn(mMockToast).when(mMockToastProxy).makeText(any(), anyInt(), anyInt());
+        doReturn(UserHandle.CURRENT).when(mMockCallsManager).getCurrentUserHandle();
     }
 
     @After
@@ -209,7 +202,8 @@
                 false /* shouldAttachToExistingConnection*/,
                 false /* isConference */,
                 mMockClockProxy,
-                mMockToastProxy);
+                mMockToastProxy,
+                mFeatureFlags);
 
         // To start with connection creation isn't complete.
         assertFalse(call.isCreateConnectionComplete());
@@ -347,7 +341,8 @@
                 false /* shouldAttachToExistingConnection*/,
                 true /* isConference */,
                 mMockClockProxy,
-                mMockToastProxy);
+                mMockToastProxy,
+                mFeatureFlags);
 
         assertFalse(call.wasDndCheckComputedForCall());
         assertFalse(call.isCallSuppressedByDoNotDisturb());
@@ -373,7 +368,8 @@
                 false /* shouldAttachToExistingConnection*/,
                 true /* isConference */,
                 mMockClockProxy,
-                mMockToastProxy);
+                mMockToastProxy,
+                mFeatureFlags);
 
         assertNull(call.getConnectionServiceWrapper());
         assertFalse(call.isTransactionalCall());
@@ -403,7 +399,8 @@
                 false /* shouldAttachToExistingConnection*/,
                 true /* isConference */,
                 mMockClockProxy,
-                mMockToastProxy);
+                mMockToastProxy,
+                mFeatureFlags);
 
         // setup
         call.setIsTransactionalCall(true);
@@ -640,8 +637,40 @@
         verify(listener).onFailedUnknownCall(unknownCall);
     }
 
+    /**
+     * ensure a Call object does not throw an NPE when the CallingPackageIdentity is not set and
+     * the correct values are returned when set
+     */
     @Test
     @SmallTest
+    public void testCallingPackageIdentity() {
+        final int packageUid = 123;
+        final int packagePid = 1;
+
+        Call call = createCall("1");
+
+        // assert default values for a Calls CallingPackageIdentity are -1 unless set via the setter
+        assertEquals(-1, call.getCallingPackageIdentity().mCallingPackageUid);
+        assertEquals(-1, call.getCallingPackageIdentity().mCallingPackagePid);
+
+        // set the Call objects CallingPackageIdentity via the setter and a bundle
+        Bundle extras = new Bundle();
+        extras.putInt(CallAttributes.CALLER_UID_KEY, packageUid);
+        extras.putInt(CallAttributes.CALLER_PID_KEY, packagePid);
+        // assert that the setter removed the extras
+        assertEquals(packageUid, extras.getInt(CallAttributes.CALLER_UID_KEY));
+        assertEquals(packagePid, extras.getInt(CallAttributes.CALLER_PID_KEY));
+        call.setCallingPackageIdentity(extras);
+        // assert that the setter removed the extras
+        assertEquals(0, extras.getInt(CallAttributes.CALLER_UID_KEY));
+        assertEquals(0, extras.getInt(CallAttributes.CALLER_PID_KEY));
+        // assert the properties are fetched correctly
+        assertEquals(packageUid, call.getCallingPackageIdentity().mCallingPackageUid);
+        assertEquals(packagePid, call.getCallingPackageIdentity().mCallingPackagePid);
+    }
+
+        @Test
+    @SmallTest
     public void testOnConnectionEventNotifiesListener() {
         Call.Listener listener = mock(Call.Listener.class);
         Call call = createCall("1");
@@ -705,6 +734,52 @@
                 }));
     }
 
+    @Test
+    @SmallTest
+    public void testExcludesInCallServiceFromDoNotLogCallExtra() {
+        Call call = createCall("any");
+        Bundle extra = new Bundle();
+        extra.putBoolean(TelecomManager.EXTRA_DO_NOT_LOG_CALL, true);
+
+        call.putInCallServiceExtras(extra, "packageName");
+
+        assertFalse(call.getExtras().containsKey(TelecomManager.EXTRA_DO_NOT_LOG_CALL));
+    }
+
+    @Test
+    @SmallTest
+    public void testExcludesConnectionServiceWithoutModifyStatePermissionFromDoNotLogCallExtra() {
+        PackageManager packageManager = mContext.getPackageManager();
+        Bundle extra = new Bundle();
+        extra.putBoolean(TelecomManager.EXTRA_DO_NOT_LOG_CALL, true);
+        String packageName = SIM_1_HANDLE.getComponentName().getPackageName();
+        doReturn(PackageManager.PERMISSION_DENIED)
+                .when(packageManager)
+                .checkPermission(android.Manifest.permission.MODIFY_PHONE_STATE, packageName);
+        Call call = createCall("any");
+
+        call.putConnectionServiceExtras(extra);
+
+        assertFalse(call.getExtras().containsKey(TelecomManager.EXTRA_DO_NOT_LOG_CALL));
+    }
+
+    @Test
+    @SmallTest
+    public void testDoesNotExcludeConnectionServiceWithModifyStatePermissionFromDoNotLogCallExtra() {
+        String packageName = SIM_1_HANDLE.getComponentName().getPackageName();
+        Bundle extra = new Bundle();
+        extra.putBoolean(TelecomManager.EXTRA_DO_NOT_LOG_CALL, true);
+        PackageManager packageManager = mContext.getPackageManager();
+        doReturn(PackageManager.PERMISSION_GRANTED)
+                .when(packageManager)
+                .checkPermission(android.Manifest.permission.MODIFY_PHONE_STATE, packageName);
+        Call call = createCall("any");
+
+        call.putConnectionServiceExtras(extra);
+
+        assertTrue(call.getExtras().containsKey(TelecomManager.EXTRA_DO_NOT_LOG_CALL));
+    }
+
     private Call createCall(String id) {
         return createCall(id, Call.CALL_DIRECTION_UNDEFINED);
     }
@@ -725,6 +800,7 @@
                 false,
                 false,
                 mMockClockProxy,
-                mMockToastProxy);
+                mMockToastProxy,
+                mFeatureFlags);
     }
 }
diff --git a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
index 7544dc9..6e0e660 100644
--- a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
@@ -16,9 +16,9 @@
 
 package com.android.server.telecom.tests;
 
+import static android.provider.CallLog.Calls.USER_MISSED_NOT_RUNNING;
 import static junit.framework.Assert.assertNotNull;
 import static junit.framework.TestCase.fail;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
@@ -41,6 +41,7 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
+import static java.lang.Thread.sleep;
 
 import android.Manifest;
 import android.content.ComponentName;
@@ -52,38 +53,46 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.Looper;
 import android.os.OutcomeReceiver;
 import android.os.Process;
 import android.os.ResultReceiver;
 import android.os.SystemClock;
 import android.os.UserHandle;
-import android.telecom.CallerInfo;
+import android.os.UserManager;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.provider.BlockedNumberContract;
+import android.telecom.CallException;
 import android.telecom.CallScreeningService;
+import android.telecom.CallerInfo;
 import android.telecom.Connection;
 import android.telecom.DisconnectCause;
 import android.telecom.GatewayInfo;
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
-import android.telecom.CallException;
 import android.telecom.VideoProfile;
+import android.telephony.CarrierConfigManager;
+import android.telephony.PhoneCapability;
 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.internal.telecom.IConnectionService;
 import com.android.server.telecom.AnomalyReporterAdapter;
 import com.android.server.telecom.AsyncRingtonePlayer;
 import com.android.server.telecom.Call;
 import com.android.server.telecom.CallAnomalyWatchdog;
+import com.android.server.telecom.CallAudioCommunicationDeviceTracker;
 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.CallEndpointController;
 import com.android.server.telecom.CallEndpointControllerFactory;
-import com.android.server.telecom.CallDiagnosticServiceController;
 import com.android.server.telecom.CallState;
 import com.android.server.telecom.CallerInfoLookupHelper;
 import com.android.server.telecom.CallsManager;
@@ -91,7 +100,9 @@
 import com.android.server.telecom.ConnectionServiceFocusManager;
 import com.android.server.telecom.ConnectionServiceFocusManager.ConnectionServiceFocusManagerFactory;
 import com.android.server.telecom.ConnectionServiceWrapper;
+import com.android.server.telecom.CreateConnectionResponse;
 import com.android.server.telecom.DefaultDialerCache;
+import com.android.server.telecom.EmergencyCallDiagnosticLogger;
 import com.android.server.telecom.EmergencyCallHelper;
 import com.android.server.telecom.HandoverState;
 import com.android.server.telecom.HeadsetMediaButton;
@@ -114,13 +125,20 @@
 import com.android.server.telecom.WiredHeadsetManager;
 import com.android.server.telecom.bluetooth.BluetoothRouteManager;
 import com.android.server.telecom.bluetooth.BluetoothStateReceiver;
+import com.android.server.telecom.callfiltering.BlockedNumbersAdapter;
 import com.android.server.telecom.callfiltering.CallFilteringResult;
+import com.android.server.telecom.callfiltering.IncomingCallFilterGraph;
+import com.android.server.telecom.flags.FeatureFlags;
+import com.android.server.telecom.flags.Flags;
 import com.android.server.telecom.ui.AudioProcessingNotification;
+import com.android.server.telecom.ui.CallStreamingNotification;
 import com.android.server.telecom.ui.DisconnectedCallNotifier;
 import com.android.server.telecom.ui.ToastFactory;
+import com.android.server.telecom.voip.TransactionManager;
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -135,17 +153,18 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
-import java.util.UUID;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Executor;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 
 @RunWith(JUnit4.class)
 public class CallsManagerTest extends TelecomTestCase {
     private static final int TEST_TIMEOUT = 5000;  // milliseconds
+    private static final long STATE_TIMEOUT = 5000L;
     private static final int SECONDARY_USER_ID = 12;
+    private static final UserHandle TEST_USER_HANDLE = UserHandle.of(123);
+    private static final String TEST_PACKAGE_NAME = "GoogleMeet";
     private static final PhoneAccountHandle SIM_1_HANDLE = new PhoneAccountHandle(
             ComponentName.unflattenFromString("com.foo/.Blah"), "Sim1");
     private static final PhoneAccountHandle SIM_1_HANDLE_SECONDARY = new PhoneAccountHandle(
@@ -161,6 +180,12 @@
             ComponentName.unflattenFromString("com.voip/.Stuff"), "Voip1");
     private static final PhoneAccountHandle SELF_MANAGED_HANDLE = new PhoneAccountHandle(
             ComponentName.unflattenFromString("com.baz/.Self"), "Self");
+    private static final PhoneAccountHandle SELF_MANAGED_2_HANDLE = new PhoneAccountHandle(
+            ComponentName.unflattenFromString("com.baz/.Self2"), "Self2");
+    private static final PhoneAccountHandle WORK_HANDLE = new PhoneAccountHandle(
+            ComponentName.unflattenFromString("com.foo/.Blah"), "work", new UserHandle(10));
+    private static final PhoneAccountHandle SELF_MANAGED_W_CUSTOM_HANDLE = new PhoneAccountHandle(
+            new ComponentName(TEST_PACKAGE_NAME, "class"), "1", TEST_USER_HANDLE);
     private static final PhoneAccount SIM_1_ACCOUNT = new PhoneAccount.Builder(SIM_1_HANDLE, "Sim1")
             .setCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION
                     | PhoneAccount.CAPABILITY_CALL_PROVIDER
@@ -185,6 +210,24 @@
             .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)
             .setIsEnabled(true)
             .build();
+    private static final PhoneAccount SELF_MANAGED_2_ACCOUNT = new PhoneAccount.Builder(
+            SELF_MANAGED_2_HANDLE, "Self2")
+            .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)
+            .setIsEnabled(true)
+            .build();
+    private static final PhoneAccount WORK_ACCOUNT = new PhoneAccount.Builder(
+            WORK_HANDLE, "work")
+            .setCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION
+                    | PhoneAccount.CAPABILITY_CALL_PROVIDER
+                    | PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS)
+            .setIsEnabled(true)
+            .build();
+    private static final PhoneAccount SM_W_DIFFERENT_PACKAGE_AND_USER = new PhoneAccount.Builder(
+            SELF_MANAGED_W_CUSTOM_HANDLE, "Self")
+            .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)
+            .setIsEnabled(true)
+            .build();
+
     private static final Uri TEST_ADDRESS = Uri.parse("tel:555-1212");
     private static final Uri TEST_ADDRESS2 = Uri.parse("tel:555-1213");
     private static final Uri TEST_ADDRESS3 = Uri.parse("tel:555-1214");
@@ -235,9 +278,19 @@
     @Mock private ToastFactory mToastFactory;
     @Mock private Toast mToast;
     @Mock private CallAnomalyWatchdog mCallAnomalyWatchdog;
+
+    @Mock private EmergencyCallDiagnosticLogger mEmergencyCallDiagnosticLogger;
     @Mock private AnomalyReporterAdapter mAnomalyReporterAdapter;
     @Mock private Ringer.AccessibilityManagerAdapter mAccessibilityManagerAdapter;
+    @Mock private BlockedNumbersAdapter mBlockedNumbersAdapter;
+    @Mock private PhoneCapability mPhoneCapability;
+    @Mock private CallAudioCommunicationDeviceTracker mCommunicationDeviceTracker;
+    @Mock private CallStreamingNotification mCallStreamingNotification;
+    @Mock private FeatureFlags mFeatureFlags;
 
+    @Mock private IncomingCallFilterGraph mIncomingCallFilterGraph;
+    @Mock private IConnectionService mIConnectionService;
+    @Rule public SetFlagsRule mSetRlagsRule = new SetFlagsRule();
     private CallsManager mCallsManager;
 
     @Override
@@ -256,8 +309,8 @@
         when(mCallEndpointControllerFactory.create(any(), any(), any())).thenReturn(
                 mCallEndpointController);
         when(mCallAudioRouteStateMachineFactory.create(any(), any(), any(), any(), any(), any(),
-                anyInt())).thenReturn(mCallAudioRouteStateMachine);
-        when(mCallAudioModeStateMachineFactory.create(any(), any()))
+                anyInt(), any(), any(), any())).thenReturn(mCallAudioRouteStateMachine);
+        when(mCallAudioModeStateMachineFactory.create(any(), any(), any(), any()))
                 .thenReturn(mCallAudioModeStateMachine);
         when(mClockProxy.currentTimeMillis()).thenReturn(System.currentTimeMillis());
         when(mClockProxy.elapsedRealtime()).thenReturn(SystemClock.elapsedRealtime());
@@ -267,6 +320,9 @@
                 .thenReturn(mDisconnectedCallNotifier);
         when(mTimeoutsAdapter.getCallDiagnosticServiceTimeoutMillis(any(ContentResolver.class)))
                 .thenReturn(2000L);
+        when(mTimeoutsAdapter.getNonVoipCallTransitoryStateTimeoutMillis())
+                .thenReturn(STATE_TIMEOUT);
+        when(mClockProxy.elapsedRealtime()).thenReturn(0L);
         mCallsManager = new CallsManager(
                 mComponentContextFixture.getTestDouble().getApplicationContext(),
                 mLock,
@@ -299,9 +355,18 @@
                 mToastFactory,
                 mCallEndpointControllerFactory,
                 mCallAnomalyWatchdog,
+                mAccessibilityManagerAdapter,
                 // Just do async tasks synchronously to support testing.
                 command -> command.run(),
-                mAccessibilityManagerAdapter);
+                // For call audio tasks
+                command -> command.run(),
+                mBlockedNumbersAdapter,
+                TransactionManager.getTestInstance(),
+                mEmergencyCallDiagnosticLogger,
+                mCommunicationDeviceTracker,
+                mCallStreamingNotification,
+                mFeatureFlags,
+                (call, listener, context, timeoutsAdapter, lock) -> mIncomingCallFilterGraph);
 
         when(mPhoneAccountRegistrar.getPhoneAccount(
                 eq(SELF_MANAGED_HANDLE), any())).thenReturn(SELF_MANAGED_ACCOUNT);
@@ -309,13 +374,21 @@
                 eq(SIM_1_HANDLE), any())).thenReturn(SIM_1_ACCOUNT);
         when(mPhoneAccountRegistrar.getPhoneAccount(
                 eq(SIM_2_HANDLE), any())).thenReturn(SIM_2_ACCOUNT);
+        when(mPhoneAccountRegistrar.getPhoneAccount(
+                eq(WORK_HANDLE), any())).thenReturn(WORK_ACCOUNT);
         when(mToastFactory.makeText(any(), anyInt(), anyInt())).thenReturn(mToast);
         when(mToastFactory.makeText(any(), any(), anyInt())).thenReturn(mToast);
+        when(mIConnectionService.asBinder()).thenReturn(mock(IBinder.class));
+
+        mComponentContextFixture.addConnectionService(
+                SIM_1_ACCOUNT.getAccountHandle().getComponentName(), mIConnectionService);
     }
 
     @Override
     @After
     public void tearDown() throws Exception {
+        mComponentContextFixture.removeConnectionService(
+                SIM_1_ACCOUNT.getAccountHandle().getComponentName(), mIConnectionService);
         super.tearDown();
     }
 
@@ -326,6 +399,27 @@
         assertEquals(0, mCallsManager.constructPossiblePhoneAccounts(null, null, false, false).size());
     }
 
+    private Call constructOngoingCall(String callId, PhoneAccountHandle phoneAccountHandle) {
+        Call ongoingCall = new Call(
+                callId,
+                mContext,
+                mCallsManager,
+                mLock,
+                null /* ConnectionServiceRepository */,
+                mPhoneNumberUtilsAdapter,
+                TEST_ADDRESS,
+                null /* GatewayInfo */,
+                null /* connectionManagerPhoneAccountHandle */,
+                phoneAccountHandle,
+                Call.CALL_DIRECTION_INCOMING,
+                false /* shouldAttachToExistingConnection*/,
+                false /* isConference */,
+                mClockProxy,
+                mToastFactory,
+                mFeatureFlags);
+        ongoingCall.setState(CallState.ACTIVE, "just cuz");
+        return ongoingCall;
+    }
     /**
      * Verify behavior for multisim devices where we want to ensure that the active sim is used for
      * placing a new call.
@@ -336,23 +430,7 @@
     public void testConstructPossiblePhoneAccountsMultiSimActive() throws Exception {
         setupMsimAccounts();
 
-        Call ongoingCall = new Call(
-                "1", /* callId */
-                mContext,
-                mCallsManager,
-                mLock,
-                null /* ConnectionServiceRepository */,
-                mPhoneNumberUtilsAdapter,
-                TEST_ADDRESS,
-                null /* GatewayInfo */,
-                null /* connectionManagerPhoneAccountHandle */,
-                SIM_2_HANDLE,
-                Call.CALL_DIRECTION_INCOMING,
-                false /* shouldAttachToExistingConnection*/,
-                false /* isConference */,
-                mClockProxy,
-                mToastFactory);
-        ongoingCall.setState(CallState.ACTIVE, "just cuz");
+        Call ongoingCall = constructOngoingCall("1", SIM_2_HANDLE);
         mCallsManager.addCall(ongoingCall);
 
         List<PhoneAccountHandle> phoneAccountHandles = mCallsManager.constructPossiblePhoneAccounts(
@@ -375,6 +453,47 @@
         assertEquals(2, phoneAccountHandles.size());
     }
 
+    /**
+     * For DSDA-enabled multisim devices with an ongoing call, verify that both SIMs'
+     * PhoneAccountHandles are constructed while placing a new call.
+     * @throws Exception
+     */
+    @MediumTest
+    @Test
+    public void testConstructPossiblePhoneAccountsMultiSimActive_dsdaCallingPossible() throws
+            Exception {
+        setupMsimAccounts();
+        setMaxActiveVoiceSubscriptions(2);
+
+        Call ongoingCall = constructOngoingCall("1", SIM_2_HANDLE);
+        mCallsManager.addCall(ongoingCall);
+
+        List<PhoneAccountHandle> phoneAccountHandles = mCallsManager.constructPossiblePhoneAccounts(
+                TEST_ADDRESS, null, false, false);
+        assertEquals(2, phoneAccountHandles.size());
+    }
+
+    /**
+     * For DSDA-enabled multisim devices with an ongoing call, verify that only the active SIMs'
+     * PhoneAccountHandle is constructed while placing an emergency call.
+     * @throws Exception
+     */
+    @MediumTest
+    @Test
+    public void testConstructPossiblePhoneAccountsMultiSimActive_dsdaCallingPossible_emergencyCall()
+            throws Exception {
+        setupMsimAccounts();
+        setMaxActiveVoiceSubscriptions(2);
+
+        Call ongoingCall = constructOngoingCall("1", SIM_2_HANDLE);
+        mCallsManager.addCall(ongoingCall);
+
+        List<PhoneAccountHandle> phoneAccountHandles = mCallsManager.constructPossiblePhoneAccounts(
+                TEST_ADDRESS, null, false, true /* isEmergency */);
+        assertEquals(1, phoneAccountHandles.size());
+        assertEquals(SIM_2_HANDLE, phoneAccountHandles.get(0));
+    }
+
     private void setupCallerInfoLookupHelper() {
         doAnswer(invocation -> {
             Uri handle = invocation.getArgument(0);
@@ -632,6 +751,54 @@
         verify(heldCall).unhold(any());
     }
 
+    /**
+     * Ensures we don't auto-unhold a call from a different app when we locally disconnect a call.
+     */
+    @SmallTest
+    @Test
+    public void testDontUnholdCallsBetweenConnectionServices() {
+        // GIVEN a CallsManager with ongoing call
+        Call ongoingCall = addSpyCall(SIM_1_HANDLE, CallState.ACTIVE);
+        when(ongoingCall.isDisconnectHandledViaFuture()).thenReturn(false);
+        doReturn(true).when(ongoingCall).can(Connection.CAPABILITY_HOLD);
+        doReturn(true).when(ongoingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
+        when(mConnectionSvrFocusMgr.getCurrentFocusCall()).thenReturn(ongoingCall);
+
+        // and a held call which has different ConnectionService
+        Call heldCall = addSpyCall(VOIP_1_HANDLE, CallState.ON_HOLD);
+
+        // Disconnect and cleanup the active ongoing call.
+        mCallsManager.disconnectCall(ongoingCall);
+        mCallsManager.markCallAsRemoved(ongoingCall);
+
+        // Should not unhold the held call since its in another app.
+        verify(heldCall, never()).unhold();
+    }
+
+    /**
+     * Ensures we do auto-unhold a call from the same app when we locally disconnect a call.
+     */
+    @SmallTest
+    @Test
+    public void testUnholdCallWhenDisconnectingInSameApp() {
+        // GIVEN a CallsManager with ongoing call
+        Call ongoingCall = addSpyCall(SIM_1_HANDLE, CallState.ACTIVE);
+        when(ongoingCall.isDisconnectHandledViaFuture()).thenReturn(false);
+        doReturn(true).when(ongoingCall).can(Connection.CAPABILITY_HOLD);
+        doReturn(true).when(ongoingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
+        when(mConnectionSvrFocusMgr.getCurrentFocusCall()).thenReturn(ongoingCall);
+
+        // and a held call which has same ConnectionService
+        Call heldCall = addSpyCall(SIM_1_HANDLE, CallState.ON_HOLD);
+
+        // Disconnect and cleanup the active ongoing call.
+        mCallsManager.disconnectCall(ongoingCall);
+        mCallsManager.markCallAsRemoved(ongoingCall);
+
+        // Should auto-unhold the held call since its in the same app.
+        verify(heldCall).unhold();
+    }
+
     @SmallTest
     @Test
     public void testUnholdCallWhenOngoingEmergCallCanNotBeHeldAndFromDifferentConnectionService() {
@@ -710,7 +877,7 @@
         mCallsManager.answerCall(incomingCall, VideoProfile.STATE_AUDIO_ONLY);
 
         // THEN the ongoing call is held and the focus request for incoming call is sent
-        verify(ongoingCall).hold();
+        verify(ongoingCall).hold(anyString());
         verifyFocusRequestAndExecuteCallback(incomingCall);
 
         // and the incoming call is answered.
@@ -820,6 +987,64 @@
 
     @SmallTest
     @Test
+    public void testAnswerThirdCallWhenTwoCallsOnDifferentSims_disconnectsHeldCall() {
+        // Given an ongoing call on SIM1
+        Call ongoingCall = addSpyCall(SIM_1_HANDLE, CallState.ACTIVE);
+        doReturn(true).when(ongoingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
+        doReturn(CallState.ACTIVE).when(ongoingCall).getState();
+        when(mConnectionSvrFocusMgr.getCurrentFocusCall()).thenReturn(ongoingCall);
+
+        // And a held call on SIM2, which belongs to the same ConnectionService
+        Call heldCall = addSpyCall(SIM_2_HANDLE, CallState.ON_HOLD);
+        doReturn(CallState.ON_HOLD).when(heldCall).getState();
+
+        // on answering an incoming call on SIM1, which belongs to the same ConnectionService
+        Call incomingCall = addSpyCall(SIM_1_HANDLE, CallState.RINGING);
+        mCallsManager.answerCall(incomingCall, VideoProfile.STATE_AUDIO_ONLY);
+
+        // THEN the previous held call is disconnected
+        verify(heldCall).disconnect();
+
+        // and the ongoing call is held
+        verify(ongoingCall).hold();
+
+        // and the focus request is sent
+        verifyFocusRequestAndExecuteCallback(incomingCall);
+        // and the incoming call is answered
+        verify(incomingCall).answer(VideoProfile.STATE_AUDIO_ONLY);
+    }
+
+    @SmallTest
+    @Test
+    public void testAnswerThirdCallDifferentSimWhenTwoCallsOnSameSim_disconnectsHeldCall() {
+        // Given an ongoing call on SIM1
+        Call ongoingCall = addSpyCall(SIM_1_HANDLE, CallState.ACTIVE);
+        doReturn(true).when(ongoingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
+        doReturn(CallState.ACTIVE).when(ongoingCall).getState();
+        when(mConnectionSvrFocusMgr.getCurrentFocusCall()).thenReturn(ongoingCall);
+
+        // And a held call on SIM1
+        Call heldCall = addSpyCall(SIM_1_HANDLE, CallState.ON_HOLD);
+        doReturn(CallState.ON_HOLD).when(heldCall).getState();
+
+        // on answering an incoming call on SIM2, which belongs to the same ConnectionService
+        Call incomingCall = addSpyCall(SIM_2_HANDLE, CallState.RINGING);
+        mCallsManager.answerCall(incomingCall, VideoProfile.STATE_AUDIO_ONLY);
+
+        // THEN the previous held call is disconnected
+        verify(heldCall).disconnect();
+
+        // and the ongoing call is held
+        verify(ongoingCall).hold();
+
+        // and the focus request is sent
+        verifyFocusRequestAndExecuteCallback(incomingCall);
+        // and the incoming call is answered
+        verify(incomingCall).answer(VideoProfile.STATE_AUDIO_ONLY);
+    }
+
+    @SmallTest
+    @Test
     public void testAnswerCallWhenNoOngoingCallExisted() {
         // GIVEN a CallsManager with no ongoing call.
 
@@ -918,7 +1143,7 @@
         mCallsManager.markCallAsActive(newCall);
 
         // THEN the ongoing call is held
-        verify(ongoingCall).hold();
+        verify(ongoingCall).hold(anyString());
         verifyFocusRequestAndExecuteCallback(newCall);
 
         // and the new call is active
@@ -1118,8 +1343,9 @@
 
     @SmallTest
     @Test
-    public void testNoFilteringOfCallsWhenPhoneAccountRequestsSkipped() {
+    public void testDndFilterAppliesOfCallsWhenPhoneAccountRequestsSkipped() {
         // GIVEN an incoming call which is from a PhoneAccount that requested to skip filtering.
+        when(mFeatureFlags.skipFilterPhoneAccountPerformDndFilter()).thenReturn(true);
         Call incomingCall = addSpyCall(SIM_1_HANDLE, CallState.NEW);
         Bundle extras = new Bundle();
         extras.putBoolean(PhoneAccount.EXTRA_SKIP_CALL_FILTERING, true);
@@ -1139,7 +1365,35 @@
         // WHEN the incoming call is successfully added.
         mCallsManager.onSuccessfulIncomingCall(incomingCall);
 
-        // THEN the incoming call is not using call filtering
+        // THEN the incoming call is still applying Dnd filter.
+        verify(incomingCall).setIsUsingCallFiltering(eq(true));
+    }
+
+    @SmallTest
+    @Test
+    public void testNoFilterAppliesOfCallsWhenFlagNotEnabled() {
+        // Flag is not enabled.
+        when(mFeatureFlags.skipFilterPhoneAccountPerformDndFilter()).thenReturn(false);
+        Call incomingCall = addSpyCall(SIM_1_HANDLE, CallState.NEW);
+        Bundle extras = new Bundle();
+        extras.putBoolean(PhoneAccount.EXTRA_SKIP_CALL_FILTERING, true);
+        PhoneAccount skipRequestedAccount = new PhoneAccount.Builder(SIM_2_HANDLE, "Skipper")
+                .setCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION
+                        | PhoneAccount.CAPABILITY_CALL_PROVIDER)
+                .setExtras(extras)
+                .setIsEnabled(true)
+                .build();
+        when(mPhoneAccountRegistrar.getPhoneAccountUnchecked(SIM_1_HANDLE))
+                .thenReturn(skipRequestedAccount);
+        doReturn(false).when(incomingCall).can(Connection.CAPABILITY_HOLD);
+        doReturn(false).when(incomingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
+        doReturn(false).when(incomingCall).isSelfManaged();
+        doReturn(true).when(incomingCall).setState(anyInt(), any());
+
+        // WHEN the incoming call is successfully added.
+        mCallsManager.onSuccessfulIncomingCall(incomingCall);
+
+        // THEN the incoming call is not applying filter.
         verify(incomingCall).setIsUsingCallFiltering(eq(false));
     }
 
@@ -1459,6 +1713,8 @@
                 .thenReturn(false);
         newCall.setHandle(TEST_ADDRESS, TelecomManager.PRESENTATION_ALLOWED);
 
+        // Make sure enough time has passed that we'd drop the connecting call.
+        when(mClockProxy.elapsedRealtime()).thenReturn(STATE_TIMEOUT + 10L);
         assertTrue(mCallsManager.makeRoomForOutgoingCall(newCall));
         verify(mAnomalyReporterAdapter).reportAnomaly(
                 CallsManager.LIVE_CALL_STUCK_CONNECTING_ERROR_UUID,
@@ -1466,6 +1722,26 @@
         verify(ongoingCall).disconnect(anyLong(), anyString());
     }
 
+    /**
+     * Verifies that we won't auto-disconnect an outgoing CONNECTING call unless it has timed out.
+     */
+    @SmallTest
+    @Test
+    public void testDontDisconnectConnectingCallWhenNotTimedOut() {
+        mCallsManager.setAnomalyReporterAdapter(mAnomalyReporterAdapter);
+        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(TEST_ADDRESS, TelecomManager.PRESENTATION_ALLOWED);
+
+        // Make sure it has been a short time so we don't try to disconnect the call
+        when(mClockProxy.elapsedRealtime()).thenReturn(STATE_TIMEOUT / 2);
+        assertFalse(mCallsManager.makeRoomForOutgoingCall(newCall));
+        verify(ongoingCall, never()).disconnect(anyLong(), anyString());
+    }
+
     @SmallTest
     @Test
     public void testMakeRoomForEmergencyCallHasOutgoingCall() {
@@ -1539,6 +1815,7 @@
         Call ongoingCall = addSpyCall(SIM_2_HANDLE, CallState.CONNECTING);
         Call newCall = createCall(SIM_1_HANDLE, CallState.NEW);
 
+        when(mClockProxy.elapsedRealtime()).thenReturn(STATE_TIMEOUT + 10L);
         assertTrue(mCallsManager.makeRoomForOutgoingCall(newCall));
         verify(ongoingCall).disconnect(anyLong(), anyString());
     }
@@ -1548,10 +1825,61 @@
     public void testMakeRoomForOutgoingCallForSameCall() {
         addSpyCall(SIM_2_HANDLE, CallState.CONNECTING);
         Call ongoingCall2 = addSpyCall();
-
+        when(mClockProxy.elapsedRealtime()).thenReturn(STATE_TIMEOUT + 10L);
         assertTrue(mCallsManager.makeRoomForOutgoingCall(ongoingCall2));
     }
 
+    /**
+     * Test where a VoIP app adds another new call and has one active already; ensure we hold the
+     * active call.  This assumes same connection service in the same app.
+     */
+    @SmallTest
+    @Test
+    public void testMakeRoomForOutgoingCallForSameVoipApp() {
+        Call activeCall = addSpyCall(SELF_MANAGED_HANDLE, null /* connMgr */,
+                CallState.ACTIVE, Connection.CAPABILITY_HOLD | Connection.CAPABILITY_SUPPORT_HOLD,
+                0 /* properties */);
+        Call newDialingCall = createCall(SELF_MANAGED_HANDLE, CallState.DIALING);
+        newDialingCall.setConnectionProperties(Connection.CAPABILITY_HOLD
+                        | Connection.CAPABILITY_SUPPORT_HOLD);
+        assertTrue(mCallsManager.makeRoomForOutgoingCall(newDialingCall));
+        verify(activeCall).hold(anyString());
+    }
+
+    /**
+     * Test where a VoIP app adds another new call and has one active already; ensure we hold the
+     * active call.  This assumes different connection services in the same app.
+     */
+    @SmallTest
+    @Test
+    public void testMakeRoomForOutgoingCallForSameVoipAppDifferentConnectionService() {
+        Call activeCall = addSpyCall(SELF_MANAGED_HANDLE, null /* connMgr */,
+                CallState.ACTIVE, Connection.CAPABILITY_HOLD | Connection.CAPABILITY_SUPPORT_HOLD,
+                0 /* properties */);
+        Call newDialingCall = createCall(SELF_MANAGED_2_HANDLE, CallState.DIALING);
+        newDialingCall.setConnectionProperties(Connection.CAPABILITY_HOLD
+                | Connection.CAPABILITY_SUPPORT_HOLD);
+        assertTrue(mCallsManager.makeRoomForOutgoingCall(newDialingCall));
+        verify(activeCall).hold(anyString());
+    }
+
+    /**
+     * Test where a VoIP app adds another new call and has one active already; ensure we hold the
+     * active call.  This assumes different connection services in the same app.
+     */
+    @SmallTest
+    @Test
+    public void testMakeRoomForOutgoingCallForSameNonVoipApp() {
+        Call activeCall = addSpyCall(SIM_1_HANDLE, null /* connMgr */,
+                CallState.ACTIVE, Connection.CAPABILITY_HOLD | Connection.CAPABILITY_SUPPORT_HOLD,
+                0 /* properties */);
+        Call newDialingCall = createCall(SIM_1_HANDLE, CallState.DIALING);
+        newDialingCall.setConnectionProperties(Connection.CAPABILITY_HOLD
+                | Connection.CAPABILITY_SUPPORT_HOLD);
+        assertTrue(mCallsManager.makeRoomForOutgoingCall(newDialingCall));
+        verify(activeCall, never()).hold(anyString());
+    }
+
     @SmallTest
     @Test
     public void testMakeRoomForOutgoingCallHasOutgoingCallSelectingAccount() {
@@ -2000,6 +2328,79 @@
                 SELF_MANAGED_HANDLE.getUserHandle()));
     }
 
+    /**
+     * Emulate the case where a new incoming call is created but the connection fails for a known
+     * reason before being added to CallsManager. In this case, the listeners should be notified
+     * properly.
+     */
+    @Test
+    public void testIncomingCallCreatedButNotAddedNotifyListener() {
+        //The call is created and a listener is added:
+        Call incomingCall = createCall(SIM_2_HANDLE, null, CallState.NEW);
+        CallsManager.CallsManagerListener listener = mock(CallsManager.CallsManagerListener.class);
+        mCallsManager.addListener(listener);
+
+        //The connection fails before being added to CallsManager for a known reason:
+        incomingCall.handleCreateConnectionFailure(new DisconnectCause(DisconnectCause.CANCELED));
+
+        //Ensure the listener is notified properly:
+        verify(listener).onCreateConnectionFailed(incomingCall);
+    }
+
+    /**
+     * Emulate the case where a new incoming call is created but the connection fails for a known
+     * reason after being added to CallsManager. Since the call was added to CallsManager, the
+     * listeners should not be notified via onCreateConnectionFailed().
+     */
+    @Test
+    public void testIncomingCallCreatedAndAddedDoNotNotifyListener() {
+        //The call is created and a listener is added:
+        Call incomingCall = createCall(SIM_2_HANDLE, null, CallState.NEW);
+        CallsManager.CallsManagerListener listener = mock(CallsManager.CallsManagerListener.class);
+        mCallsManager.addListener(listener);
+
+        //The call is added to CallsManager:
+        mCallsManager.addCall(incomingCall);
+
+        //The connection fails after being added to CallsManager for a known reason:
+        incomingCall.handleCreateConnectionFailure(new DisconnectCause(DisconnectCause.CANCELED));
+
+        //Since the call was added to CallsManager, onCreateConnectionFailed shouldn't be invoked:
+        verify(listener, never()).onCreateConnectionFailed(incomingCall);
+    }
+
+    /**
+     * Emulate the case where a new outgoing call is created but is aborted before being added to
+     * CallsManager since there are no available phone accounts. In this case, the listeners
+     * should be notified properly.
+     */
+    @Test
+    public void testAbortOutgoingCallNoPhoneAccountsNotifyListeners() throws Exception {
+        // Setup a new outgoing call and add a listener
+        Call newCall = addSpyCall(CallState.NEW);
+        CallsManager.CallsManagerListener listener = mock(CallsManager.CallsManagerListener.class);
+        mCallsManager.addListener(listener);
+
+        // Ensure contact info lookup succeeds but do not set the phone account info
+        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));
+
+        // Start the outgoing call
+        CompletableFuture<Call> callFuture = mCallsManager.startOutgoingCall(
+                newCall.getHandle(), newCall.getTargetPhoneAccount(), new Bundle(),
+                UserHandle.CURRENT, new Intent(), "com.test.stuff");
+        Call result = callFuture.get(TEST_TIMEOUT, TimeUnit.MILLISECONDS);
+
+        //Ensure the listener is notified properly:
+        verify(listener).onCreateConnectionFailed(any());
+        assertNull(result);
+    }
+
     @Test
     public void testIsInSelfManagedCallOnlySelfManaged() {
         Call selfManagedCall = createCall(SELF_MANAGED_HANDLE, CallState.ACTIVE);
@@ -2049,7 +2450,7 @@
 
         mCallsManager.onCallFilteringComplete(callSpy, result, false /* timeout */);
         verify(mMissedCallNotifier).showMissedCallNotification(
-                any(MissedCallNotifier.CallInfo.class));
+                any(MissedCallNotifier.CallInfo.class), /* uri= */ eq(null));
     }
 
     @Test
@@ -2146,13 +2547,144 @@
     public void testPostCallPackageNameSetOnSuccessfulOutgoingCall() throws Exception {
         Call outgoingCall = addSpyCall(CallState.NEW);
         when(mCallsManager.getRoleManagerAdapter().getDefaultCallScreeningApp(
-                outgoingCall.getUserHandleFromTargetPhoneAccount()))
+                outgoingCall.getAssociatedUser()))
                 .thenReturn(DEFAULT_CALL_SCREENING_APP);
         assertNull(outgoingCall.getPostCallPackageName());
         mCallsManager.onSuccessfulOutgoingCall(outgoingCall, CallState.CONNECTING);
         assertEquals(DEFAULT_CALL_SCREENING_APP, outgoingCall.getPostCallPackageName());
     }
 
+    /**
+     * Verify the only call state set from calling onSuccessfulOutgoingCall is CallState.DIALING.
+     */
+    @SmallTest
+    @Test
+    public void testOutgoingCallStateIsSetToAPreviousStateAndIgnored() {
+        when(mFeatureFlags.fixAudioFlickerForOutgoingCalls()).thenReturn(true);
+        Call outgoingCall = addSpyCall(CallState.CONNECTING);
+        mCallsManager.onSuccessfulOutgoingCall(outgoingCall, CallState.NEW);
+        verify(outgoingCall, never()).setState(eq(CallState.NEW), any());
+        verify(outgoingCall, times(1)).setState(eq(CallState.DIALING), any());
+    }
+
+    /**
+     * Verify a ConnectionService can start the call in the active state and avoid the dialing state
+     */
+    @SmallTest
+    @Test
+    public void testOutgoingCallStateCanAvoidDialingAndGoStraightToActive() {
+        when(mFeatureFlags.fixAudioFlickerForOutgoingCalls()).thenReturn(true);
+        Call outgoingCall = addSpyCall(CallState.CONNECTING);
+        mCallsManager.onSuccessfulOutgoingCall(outgoingCall, CallState.ACTIVE);
+        verify(outgoingCall, never()).setState(eq(CallState.DIALING), any());
+        verify(outgoingCall, times(1)).setState(eq(CallState.ACTIVE), any());
+    }
+
+    @SmallTest
+    @Test
+    public void testRejectIncomingCallOnPAHInactive_SecondaryUser() throws Exception {
+        ConnectionServiceWrapper service = mock(ConnectionServiceWrapper.class);
+        doReturn(WORK_HANDLE.getComponentName()).when(service).getComponentName();
+        mCallsManager.addConnectionServiceRepositoryCache(WORK_HANDLE.getComponentName(),
+                WORK_HANDLE.getUserHandle(), service);
+
+        UserManager um = mContext.getSystemService(UserManager.class);
+        when(um.isUserAdmin(anyInt())).thenReturn(false);
+        when(um.isQuietModeEnabled(eq(WORK_HANDLE.getUserHandle()))).thenReturn(false);
+        when(mPhoneAccountRegistrar.getPhoneAccountUnchecked(eq(WORK_HANDLE)))
+                .thenReturn(WORK_ACCOUNT);
+        Call newCall = mCallsManager.processIncomingCallIntent(
+                WORK_HANDLE, new Bundle(), false);
+
+        verify(service, timeout(TEST_TIMEOUT)).createConnectionFailed(any());
+        assertFalse(newCall.isInECBM());
+        assertEquals(USER_MISSED_NOT_RUNNING, newCall.getMissedReason());
+    }
+
+    @SmallTest
+    @Test
+    public void testRejectIncomingCallOnPAHInactive_ProfilePaused() throws Exception {
+        ConnectionServiceWrapper service = mock(ConnectionServiceWrapper.class);
+        doReturn(WORK_HANDLE.getComponentName()).when(service).getComponentName();
+        mCallsManager.addConnectionServiceRepositoryCache(WORK_HANDLE.getComponentName(),
+                WORK_HANDLE.getUserHandle(), service);
+
+        UserManager um = mContext.getSystemService(UserManager.class);
+        when(um.isUserAdmin(anyInt())).thenReturn(true);
+        when(um.isQuietModeEnabled(eq(WORK_HANDLE.getUserHandle()))).thenReturn(true);
+        when(mPhoneAccountRegistrar.getPhoneAccountUnchecked(eq(WORK_HANDLE)))
+                .thenReturn(WORK_ACCOUNT);
+        Call newCall = mCallsManager.processIncomingCallIntent(
+                WORK_HANDLE, new Bundle(), false);
+
+        verify(service, timeout(TEST_TIMEOUT)).createConnectionFailed(any());
+        assertFalse(newCall.isInECBM());
+        assertEquals(USER_MISSED_NOT_RUNNING, newCall.getMissedReason());
+    }
+
+    @SmallTest
+    @Test
+    public void testAcceptIncomingCallOnPAHInactiveAndECBMActive() throws Exception {
+        ConnectionServiceWrapper service = mock(ConnectionServiceWrapper.class);
+        doReturn(SIM_2_HANDLE.getComponentName()).when(service).getComponentName();
+        mCallsManager.addConnectionServiceRepositoryCache(SIM_2_HANDLE.getComponentName(),
+                SIM_2_HANDLE.getUserHandle(), service);
+
+        when(mEmergencyCallHelper.isLastOutgoingEmergencyCallPAH(eq(SIM_2_HANDLE)))
+                .thenReturn(true);
+        UserManager um = mContext.getSystemService(UserManager.class);
+        when(um.isQuietModeEnabled(eq(SIM_2_HANDLE.getUserHandle()))).thenReturn(true);
+        Call newCall = mCallsManager.processIncomingCallIntent(
+                SIM_2_HANDLE, new Bundle(), false);
+
+        assertTrue(newCall.isInECBM());
+        verify(service, timeout(TEST_TIMEOUT).times(0)).createConnectionFailed(any());
+    }
+
+    @SmallTest
+    @Test
+    public void testAcceptIncomingCallOnPAHInactiveAndECBMActive_SecondaryUser() throws Exception {
+        ConnectionServiceWrapper service = mock(ConnectionServiceWrapper.class);
+        doReturn(WORK_HANDLE.getComponentName()).when(service).getComponentName();
+        mCallsManager.addConnectionServiceRepositoryCache(SIM_2_HANDLE.getComponentName(),
+                WORK_HANDLE.getUserHandle(), service);
+
+        when(mEmergencyCallHelper.isLastOutgoingEmergencyCallPAH(eq(WORK_HANDLE)))
+                .thenReturn(true);
+        UserManager um = mContext.getSystemService(UserManager.class);
+        when(um.isUserAdmin(anyInt())).thenReturn(false);
+        when(um.isQuietModeEnabled(eq(WORK_HANDLE.getUserHandle()))).thenReturn(false);
+        when(mPhoneAccountRegistrar.getPhoneAccountUnchecked(eq(WORK_HANDLE)))
+                .thenReturn(WORK_ACCOUNT);
+        Call newCall = mCallsManager.processIncomingCallIntent(
+                WORK_HANDLE, new Bundle(), false);
+
+        assertTrue(newCall.isInECBM());
+        verify(service, timeout(TEST_TIMEOUT).times(0)).createConnectionFailed(any());
+    }
+
+    @SmallTest
+    @Test
+    public void testAcceptIncomingEmergencyCallOnPAHInactive() throws Exception {
+        ConnectionServiceWrapper service = mock(ConnectionServiceWrapper.class);
+        doReturn(SIM_2_HANDLE.getComponentName()).when(service).getComponentName();
+        mCallsManager.addConnectionServiceRepositoryCache(SIM_2_HANDLE.getComponentName(),
+                SIM_2_HANDLE.getUserHandle(), service);
+
+        Bundle extras = new Bundle();
+        extras.putParcelable(TelecomManager.EXTRA_INCOMING_CALL_ADDRESS, TEST_ADDRESS);
+        TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
+        UserManager um = mContext.getSystemService(UserManager.class);
+        when(um.isQuietModeEnabled(eq(SIM_2_HANDLE.getUserHandle()))).thenReturn(true);
+        when(tm.isEmergencyNumber(any(String.class))).thenReturn(true);
+        Call newCall = mCallsManager.processIncomingCallIntent(
+                SIM_2_HANDLE, extras, false);
+
+        assertFalse(newCall.isInECBM());
+        assertTrue(newCall.isEmergencyCall());
+        verify(service, timeout(TEST_TIMEOUT).times(0)).createConnectionFailed(any());
+    }
+
     public class LatchedOutcomeReceiver implements OutcomeReceiver<Boolean,
             CallException> {
         CountDownLatch mCountDownLatch;
@@ -2283,8 +2815,9 @@
     public void testQueryCurrentLocationCheckOnReceiveResult() throws Exception {
         ConnectionServiceWrapper service = new ConnectionServiceWrapper(
                 new ComponentName(mContext.getPackageName(),
-                        mContext.getPackageName().getClass().getName()),
-                null, mPhoneAccountRegistrar, mCallsManager, mContext, mLock, null);
+                        mContext.getPackageName().getClass().getName()), null,
+                        mPhoneAccountRegistrar, mCallsManager, mContext, mLock, null,
+                        mFeatureFlags);
 
         CompletableFuture<String> resultFuture = new CompletableFuture<>();
         try {
@@ -2304,6 +2837,37 @@
         assertTrue(result.contains("onReceiveResult"));
     }
 
+    @Test
+    public void testConnectionServiceCreateConnectionTimeout() throws Exception {
+        mSetRlagsRule.enableFlags(Flags.FLAG_UNBIND_TIMEOUT_CONNECTIONS);
+        ConnectionServiceWrapper service = new ConnectionServiceWrapper(
+                SIM_1_ACCOUNT.getAccountHandle().getComponentName(), null,
+                mPhoneAccountRegistrar, mCallsManager, mContext, mLock, null,
+                mFeatureFlags);
+        TestScheduledExecutorService scheduledExecutorService = new TestScheduledExecutorService();
+        service.setScheduledExecutorService(scheduledExecutorService);
+        Call call = addSpyCall();
+        service.addCall(call);
+        when(call.isCreateConnectionComplete()).thenReturn(false);
+        CreateConnectionResponse response = mock(CreateConnectionResponse.class);
+
+        service.createConnection(call, response);
+        waitUntilConditionIsTrueOrTimeout(new Condition() {
+            @Override
+            public Object expected() {
+                return true;
+            }
+
+            @Override
+            public Object actual() {
+                return scheduledExecutorService.isRunnableScheduledAtTime(15000L);
+            }
+        }, 5000L, "Expected job failed to schedule");
+        scheduledExecutorService.advanceTime(15000L);
+        verify(response).handleCreateConnectionFailure(
+                eq(new DisconnectCause(DisconnectCause.ERROR)));
+    }
+
     @SmallTest
     @Test
     public void testOnFailedOutgoingCallUnholdsCallAfterLocallyDisconnect() {
@@ -2528,7 +3092,6 @@
         Call call = addSpyCall(CONNECTION_MGR_1_HANDLE, CallState.NEW);
         when(call.getHandoverDestinationCall()).thenReturn(destinationCall);
         when(call.getHandoverState()).thenReturn(HandoverState.HANDOVER_FROM_STARTED);
-
         mCallsManager.createActionSetCallStateAndPerformAction(
                 call, CallState.DISCONNECTED, "");
 
@@ -2637,6 +3200,167 @@
         assertTrue(mCallsManager.hasSelfManagedCalls());
     }
 
+    /**
+     * Verifies when {@link CallsManager} receives a carrier config change it will trigger an
+     * update of the emergency call notification.
+     * Note: this test mocks out {@link BlockedNumbersAdapter} so does not actually test posting of
+     * the notification.  Notification posting in the actual implementation is covered by
+     * {@link BlockedNumbersUtilTests}.
+     */
+    @SmallTest
+    @Test
+    public void testUpdateEmergencyCallNotificationOnCarrierConfigChange() {
+        when(mBlockedNumbersAdapter.shouldShowEmergencyCallNotification(any(Context.class)))
+                .thenReturn(true);
+        mComponentContextFixture.getBroadcastReceivers().forEach(c -> c.onReceive(mContext,
+                new Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)));
+        verify(mBlockedNumbersAdapter).updateEmergencyCallNotification(any(Context.class),
+                eq(true));
+
+        when(mBlockedNumbersAdapter.shouldShowEmergencyCallNotification(any(Context.class)))
+                .thenReturn(false);
+        mComponentContextFixture.getBroadcastReceivers().forEach(c -> c.onReceive(mContext,
+                new Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)));
+        verify(mBlockedNumbersAdapter).updateEmergencyCallNotification(any(Context.class),
+                eq(false));
+    }
+
+    /**
+     * Verifies when {@link CallsManager} receives a signal from the blocked number provider that
+     * the call blocking enabled state changes, it will trigger an update of the emergency call
+     * notification.
+     * Note: this test mocks out {@link BlockedNumbersAdapter} so does not actually test posting of
+     * the notification.  Notification posting in the actual implementation is covered by
+     * {@link BlockedNumbersUtilTests}.
+     */
+    @SmallTest
+    @Test
+    public void testUpdateEmergencyCallNotificationOnNotificationVisibilityChange() {
+        when(mBlockedNumbersAdapter.shouldShowEmergencyCallNotification(any(Context.class)))
+                .thenReturn(true);
+        mComponentContextFixture.getBroadcastReceivers().forEach(c -> c.onReceive(mContext,
+                new Intent(
+                        BlockedNumberContract.SystemContract
+                                .ACTION_BLOCK_SUPPRESSION_STATE_CHANGED)));
+        verify(mBlockedNumbersAdapter).updateEmergencyCallNotification(any(Context.class),
+                eq(true));
+
+        when(mBlockedNumbersAdapter.shouldShowEmergencyCallNotification(any(Context.class)))
+                .thenReturn(false);
+        mComponentContextFixture.getBroadcastReceivers().forEach(c -> c.onReceive(mContext,
+                new Intent(
+                        BlockedNumberContract.SystemContract
+                                .ACTION_BLOCK_SUPPRESSION_STATE_CHANGED)));
+        verify(mBlockedNumbersAdapter).updateEmergencyCallNotification(any(Context.class),
+                eq(false));
+    }
+
+    /**
+     * Verify CallsManager#isInSelfManagedCall(packageName, userHandle) returns true when
+     * CallsManager is first made aware of the incoming call in processIncomingCallIntent.
+     */
+    @SmallTest
+    @Test
+    public void testAddNewIncomingCall_IsInSelfManagedCall() {
+        // GIVEN
+        assertEquals(0, mCallsManager.getSelfManagedCallsBeingSetup().size());
+        assertFalse(mCallsManager.isInSelfManagedCall(TEST_PACKAGE_NAME, TEST_USER_HANDLE));
+
+        // WHEN
+        when(mPhoneAccountRegistrar.getPhoneAccountUnchecked(any()))
+                .thenReturn(SM_W_DIFFERENT_PACKAGE_AND_USER);
+        UserManager um = mContext.getSystemService(UserManager.class);
+        when(um.isUserAdmin(eq(mCallsManager.getCurrentUserHandle().getIdentifier())))
+                .thenReturn(true);
+
+        // THEN
+        mCallsManager.processIncomingCallIntent(SELF_MANAGED_W_CUSTOM_HANDLE, new Bundle(), false);
+
+        assertEquals(1, mCallsManager.getSelfManagedCallsBeingSetup().size());
+        assertTrue(mCallsManager.isInSelfManagedCall(TEST_PACKAGE_NAME, TEST_USER_HANDLE));
+        assertEquals(0, mCallsManager.getCalls().size());
+    }
+
+    /**
+     * Verify CallsManager#isInSelfManagedCall(packageName, userHandle) returns true when
+     * CallsManager is first made aware of the outgoing call in StartOutgoingCall.
+     */
+    @SmallTest
+    @Test
+    public void testStartOutgoing_IsInSelfManagedCall() {
+        // GIVEN
+        assertEquals(0, mCallsManager.getSelfManagedCallsBeingSetup().size());
+        assertFalse(mCallsManager.isInSelfManagedCall(TEST_PACKAGE_NAME, TEST_USER_HANDLE));
+
+        // WHEN
+        when(mPhoneAccountRegistrar.getPhoneAccount(any(), any()))
+                .thenReturn(SM_W_DIFFERENT_PACKAGE_AND_USER);
+        // 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(
+                SELF_MANAGED_W_CUSTOM_HANDLE);
+        when(mPhoneAccountRegistrar.getCallCapablePhoneAccounts(any(), anyBoolean(),
+                any(), anyInt(), anyInt(), anyBoolean())).thenReturn(
+                new ArrayList<>(List.of(SELF_MANAGED_W_CUSTOM_HANDLE)));
+
+        // THEN
+        mCallsManager.startOutgoingCall(TEST_ADDRESS, SELF_MANAGED_W_CUSTOM_HANDLE, new Bundle(),
+                TEST_USER_HANDLE, new Intent(), TEST_PACKAGE_NAME);
+
+        assertEquals(1, mCallsManager.getSelfManagedCallsBeingSetup().size());
+        assertTrue(mCallsManager.isInSelfManagedCall(TEST_PACKAGE_NAME, TEST_USER_HANDLE));
+        assertEquals(0, mCallsManager.getCalls().size());
+    }
+
+    /**
+     * Verify SelfManagedCallsBeingSetup is being cleaned up in CallsManager#addCall and
+     * CallsManager#removeCall.  This ensures no memory leaks.
+     */
+    @SmallTest
+    @Test
+    public void testCallsBeingSetupCleanup() {
+        Call spyCall = addSpyCall();
+        assertEquals(0, mCallsManager.getSelfManagedCallsBeingSetup().size());
+        // verify CallsManager#removeCall removes the call from SelfManagedCallsBeingSetup
+        mCallsManager.addCallBeingSetup(spyCall);
+        mCallsManager.removeCall(spyCall);
+        assertEquals(0, mCallsManager.getSelfManagedCallsBeingSetup().size());
+        // verify CallsManager#addCall removes the call from SelfManagedCallsBeingSetup
+        mCallsManager.addCallBeingSetup(spyCall);
+        mCallsManager.addCall(spyCall);
+        assertEquals(0, mCallsManager.getSelfManagedCallsBeingSetup().size());
+    }
+
+    /**
+     * Verify isInSelfManagedCall returns false if there is a self-managed call, but it is for a
+     * different package and user
+     */
+    @SmallTest
+    @Test
+    public void testIsInSelfManagedCall_PackageUserQueryIsWorkingAsIntended() {
+        // start an active call
+        Call randomCall = createSpyCall(SELF_MANAGED_HANDLE, CallState.ACTIVE);
+        mCallsManager.addCallBeingSetup(randomCall);
+        assertEquals(1, mCallsManager.getSelfManagedCallsBeingSetup().size());
+        // query isInSelfManagedCall for a package that is NOT in a call;  expect false
+        assertFalse(mCallsManager.isInSelfManagedCall(TEST_PACKAGE_NAME, TEST_USER_HANDLE));
+        // start another call
+        Call targetCall = addSpyCall(SELF_MANAGED_W_CUSTOM_HANDLE, CallState.DIALING);
+        when(targetCall.getTargetPhoneAccount()).thenReturn(SELF_MANAGED_W_CUSTOM_HANDLE);
+        when(targetCall.isSelfManaged()).thenReturn(true);
+        mCallsManager.addCallBeingSetup(targetCall);
+        // query isInSelfManagedCall for a package that is in a call
+        assertTrue(mCallsManager.isInSelfManagedCall(TEST_PACKAGE_NAME, TEST_USER_HANDLE));
+    }
+
+
     private Call addSpyCall() {
         return addSpyCall(SIM_2_HANDLE, CallState.ACTIVE);
     }
@@ -2707,8 +3431,13 @@
                 false /* shouldAttachToExistingConnection*/,
                 false /* isConference */,
                 mClockProxy,
-                mToastFactory);
+                mToastFactory,
+                mFeatureFlags);
         ongoingCall.setState(initialState, "just cuz");
+        if (targetPhoneAccount == SELF_MANAGED_HANDLE
+                || targetPhoneAccount == SELF_MANAGED_2_HANDLE) {
+            ongoingCall.setIsSelfManaged(true);
+        }
         return ongoingCall;
     }
 
@@ -2729,4 +3458,25 @@
         when(mPhoneAccountRegistrar.getSimPhoneAccountsOfCurrentUser()).thenReturn(
                 new ArrayList<>(Arrays.asList(SIM_1_HANDLE, SIM_2_HANDLE)));
     }
+
+    private void setMaxActiveVoiceSubscriptions(int num) {
+        TelephonyManager mockTelephonyManager = mComponentContextFixture.getTelephonyManager();
+        when(mockTelephonyManager.getPhoneCapability()).thenReturn(mPhoneCapability);
+        when(mPhoneCapability.getMaxActiveVoiceSubscriptions()).thenReturn(num);
+    }
+
+    private void waitUntilConditionIsTrueOrTimeout(Condition condition, long timeout,
+                                                   String description) throws InterruptedException {
+        final long start = System.currentTimeMillis();
+        while (!condition.expected().equals(condition.actual())
+                && System.currentTimeMillis() - start < timeout) {
+            sleep(50);
+        }
+        assertEquals(description, condition.expected(), condition.actual());
+    }
+
+    protected interface Condition {
+        Object expected();
+        Object actual();
+    }
 }
diff --git a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
index e3a1471..c732720 100644
--- a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
+++ b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
@@ -16,6 +16,7 @@
 
 package com.android.server.telecom.tests;
 
+import com.android.server.telecom.flags.FeatureFlags;
 import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.Multimap;
 
@@ -27,6 +28,8 @@
 import org.mockito.stubbing.Answer;
 
 import android.Manifest;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.app.AppOpsManager;
 import android.app.NotificationManager;
 import android.app.StatusBarManager;
@@ -55,9 +58,14 @@
 import android.location.LocationManager;
 import android.media.AudioDeviceInfo;
 import android.media.AudioManager;
+import android.net.Uri;
+import android.os.BugreportManager;
 import android.os.Bundle;
+import android.os.DropBoxManager;
 import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.IInterface;
+import android.os.Looper;
 import android.os.PersistableBundle;
 import android.os.Process;
 import android.os.UserHandle;
@@ -77,8 +85,10 @@
 import android.util.DisplayMetrics;
 import android.view.accessibility.AccessibilityManager;
 
+import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.IOException;
+import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
@@ -110,6 +120,7 @@
  * property points to an application context implementing all the nontrivial functionality.
  */
 public class ComponentContextFixture implements TestFixture<Context> {
+    private HandlerThread mHandlerThread;
 
     public class FakeApplicationContext extends MockContext {
         @Override
@@ -275,8 +286,12 @@
                 return Context.NOTIFICATION_SERVICE;
             } else if (svcClass == AccessibilityManager.class) {
                 return Context.ACCESSIBILITY_SERVICE;
+            } else if (svcClass == DropBoxManager.class) {
+                return Context.DROPBOX_SERVICE;
+            } else if (svcClass == BugreportManager.class) {
+                return Context.BUGREPORT_SERVICE;
             }
-            throw new UnsupportedOperationException();
+            throw new UnsupportedOperationException(svcClass.getName());
         }
 
         @Override
@@ -305,6 +320,15 @@
         }
 
         @Override
+        public Looper getMainLooper() {
+            if (mHandlerThread == null) {
+                mHandlerThread = new HandlerThread(this.getClass().getSimpleName());
+                mHandlerThread.start();
+            }
+            return mHandlerThread.getLooper();
+        }
+
+        @Override
         public ContentResolver getContentResolver() {
             return new ContentResolver(mApplicationContextSpy) {
                 @Override
@@ -344,30 +368,34 @@
 
         @Override
         public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
-            // TODO -- this is called by WiredHeadsetManager!!!
+            mBroadcastReceivers.add(receiver);
             return null;
         }
 
         @Override
         public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, int flags) {
+            mBroadcastReceivers.add(receiver);
             return null;
         }
 
         @Override
         public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
                 String broadcastPermission, Handler scheduler) {
+            mBroadcastReceivers.add(receiver);
             return null;
         }
 
         @Override
         public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
                 String broadcastPermission, Handler scheduler, int flags) {
+            mBroadcastReceivers.add(receiver);
             return null;
         }
 
         @Override
         public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle handle,
                 IntentFilter filter, String broadcastPermission, Handler scheduler) {
+            mBroadcastReceivers.add(receiver);
             return null;
         }
 
@@ -387,12 +415,23 @@
         }
 
         @Override
+        public void sendBroadcastAsUser(Intent intent, UserHandle user, String receiverPermission) {
+            // Override so that this can be verified via spy.
+        }
+
+        @Override
         public void sendBroadcastAsUser(Intent intent, UserHandle user, String receiverPermission,
                 Bundle options) {
             // Override so that this can be verified via spy.
         }
 
         @Override
+        public void sendBroadcastAsUser(Intent intent, UserHandle user, String receiverPermission,
+                int appOp) {
+            // Override so that this can be verified via spy.
+        }
+
+        @Override
         public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user,
                 String receiverPermission, BroadcastReceiver resultReceiver, Handler scheduler,
                 int initialCode, String initialData, Bundle initialExtras) {
@@ -591,11 +630,13 @@
             mock(PermissionCheckerManager.class);
     private final PermissionInfo mPermissionInfo = mock(PermissionInfo.class);
     private final SensorPrivacyManager mSensorPrivacyManager = mock(SensorPrivacyManager.class);
+    private final List<BroadcastReceiver> mBroadcastReceivers = new ArrayList<>();
 
     private TelecomManager mTelecomManager = mock(TelecomManager.class);
 
-    public ComponentContextFixture() {
+    public ComponentContextFixture(FeatureFlags featureFlags) {
         MockitoAnnotations.initMocks(this);
+        when(featureFlags.telecomResolveHiddenDependencies()).thenReturn(true);
         when(mResources.getConfiguration()).thenReturn(mResourceConfiguration);
         when(mResources.getString(anyInt())).thenReturn("");
         when(mResources.getStringArray(anyInt())).thenReturn(new String[0]);
@@ -678,7 +719,7 @@
             }
         }).when(mAppOpsManager).checkPackage(anyInt(), anyString());
 
-        when(mNotificationManager.matchesCallFilter(any(Bundle.class))).thenReturn(true);
+        when(mNotificationManager.matchesCallFilter(any(Uri.class))).thenReturn(true);
 
         when(mCarrierConfigManager.getConfig()).thenReturn(new PersistableBundle());
         when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(new PersistableBundle());
@@ -712,6 +753,14 @@
         mServiceInfoByComponentName.put(componentName, serviceInfo);
     }
 
+    public void removeConnectionService(
+            ComponentName componentName,
+            IConnectionService service)
+            throws Exception {
+        removeService(ConnectionService.SERVICE_INTERFACE, componentName, service);
+        mServiceInfoByComponentName.remove(componentName);
+    }
+
     public void addInCallService(
             ComponentName componentName,
             IInCallService service,
@@ -733,6 +782,8 @@
                 componentName.getPackageName() });
         when(mPackageManager.checkPermission(eq(Manifest.permission.CONTROL_INCALL_EXPERIENCE),
                 eq(componentName.getPackageName()))).thenReturn(PackageManager.PERMISSION_GRANTED);
+        when(mPackageManager.checkPermission(eq(Manifest.permission.INTERACT_ACROSS_USERS),
+                eq(componentName.getPackageName()))).thenReturn(PackageManager.PERMISSION_GRANTED);
         when(mPermissionCheckerManager.checkPermission(
                 eq(Manifest.permission.CONTROL_INCALL_EXPERIENCE),
                 any(AttributionSourceState.class), anyString(), anyBoolean(), anyBoolean(),
@@ -771,6 +822,11 @@
         when(mResources.getStringArray(eq(id))).thenReturn(value);
     }
 
+    public void putRawResource(int id, String content) {
+        when(mResources.openRawResource(id))
+                .thenReturn(new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8)));
+    }
+
     public void setTelecomManager(TelecomManager telecomManager) {
         mTelecomManager = telecomManager;
     }
@@ -795,12 +851,22 @@
         return mNotificationManager;
     }
 
+    public List<BroadcastReceiver> getBroadcastReceivers() {
+        return mBroadcastReceivers;
+    }
+
     private void addService(String action, ComponentName name, IInterface service) {
         mComponentNamesByAction.put(action, name);
         mServiceByComponentName.put(name, service);
         mComponentNameByService.put(service, name);
     }
 
+    private void removeService(String action, ComponentName name, IInterface service) {
+        mComponentNamesByAction.remove(action, name);
+        mServiceByComponentName.remove(name);
+        mComponentNameByService.remove(service);
+    }
+
     private List<ResolveInfo> doQueryIntentServices(Intent intent, int flags) {
         List<ResolveInfo> result = new ArrayList<>();
         for (ComponentName componentName : mComponentNamesByAction.get(intent.getAction())) {
diff --git a/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java b/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
index c8da78c..0927b80 100755
--- a/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
+++ b/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
@@ -69,6 +69,9 @@
     static int INVALID_VIDEO_STATE = -1;
     public CountDownLatch mExtrasLock = new CountDownLatch(1);
     static int NOT_SPECIFIED = 0;
+    public static final String STATUS_HINTS_EXTRA = "updateStatusHints";
+    public static final PhoneAccountHandle TEST_PHONE_ACCOUNT_HANDLE =
+            new PhoneAccountHandle(new ComponentName("pkg", "cls"), "test");
 
     /**
      * Implementation of ConnectionService that performs no-ops for tasks normally meant for
@@ -103,6 +106,11 @@
             if (mProperties != NOT_SPECIFIED) {
                 fakeConnection.setConnectionProperties(mProperties);
             }
+            // Testing for StatusHints image icon cross user access
+            if (request.getExtras() != null) {
+                fakeConnection.setStatusHints(
+                        request.getExtras().getParcelable(STATUS_HINTS_EXTRA));
+            }
 
             return fakeConnection;
         }
@@ -119,6 +127,11 @@
             if (mProperties != NOT_SPECIFIED) {
                 fakeConnection.setConnectionProperties(mProperties);
             }
+            // Testing for StatusHints image icon cross user access
+            if (request.getExtras() != null) {
+                fakeConnection.setStatusHints(
+                        request.getExtras().getParcelable(STATUS_HINTS_EXTRA));
+            }
             return fakeConnection;
         }
 
@@ -135,6 +148,12 @@
                 Conference fakeConference = new FakeConference();
                 fakeConference.addConnection(cxn1);
                 fakeConference.addConnection(cxn2);
+                if (cxn1.getStatusHints() != null || cxn2.getStatusHints() != null) {
+                    // For testing purposes, pick one of the status hints that isn't null.
+                    StatusHints statusHints = cxn1.getStatusHints() != null
+                            ? cxn1.getStatusHints() : cxn2.getStatusHints();
+                    fakeConference.setStatusHints(statusHints);
+                }
                 mLatestConference = fakeConference;
                 addConference(fakeConference);
             } else {
@@ -179,7 +198,7 @@
 
     public class FakeConference extends Conference {
         public FakeConference() {
-            super(null);
+            super(TEST_PHONE_ACCOUNT_HANDLE);
             setConnectionCapabilities(
                     Connection.CAPABILITY_SUPPORT_HOLD
                             | Connection.CAPABILITY_HOLD
@@ -507,6 +526,7 @@
 
     public String mLatestConnectionId;
     public Connection mLatestConnection;
+    public ParcelableConnection mLatestParcelableConnection;
     public Conference mLatestConference;
     public final Set<IConnectionServiceAdapter> mConnectionServiceAdapters = new HashSet<>();
     public final Map<String, ConnectionInfo> mConnectionById = new HashMap<>();
@@ -751,7 +771,7 @@
     }
 
     private ParcelableConnection parcelable(ConnectionInfo c) {
-        return new ParcelableConnection(
+        mLatestParcelableConnection = new ParcelableConnection(
                 c.request.getAccountHandle(),
                 c.state,
                 c.capabilities,
@@ -772,5 +792,6 @@
                 c.conferenceableConnectionIds,
                 c.extras,
                 c.callerNumberVerificationStatus);
+        return mLatestParcelableConnection;
     }
 }
diff --git a/tests/src/com/android/server/telecom/tests/CreateConnectionProcessorTest.java b/tests/src/com/android/server/telecom/tests/CreateConnectionProcessorTest.java
index f977ddb..c356b8f 100644
--- a/tests/src/com/android/server/telecom/tests/CreateConnectionProcessorTest.java
+++ b/tests/src/com/android/server/telecom/tests/CreateConnectionProcessorTest.java
@@ -38,6 +38,7 @@
 import com.android.server.telecom.CreateConnectionProcessor;
 import com.android.server.telecom.CreateConnectionResponse;
 import com.android.server.telecom.PhoneAccountRegistrar;
+import com.android.server.telecom.flags.FeatureFlags;
 
 import org.junit.After;
 import org.junit.Before;
@@ -123,7 +124,7 @@
 
         mTestCreateConnectionProcessor = new CreateConnectionProcessor(mMockCall,
                 mMockConnectionServiceRepository, mMockCreateConnectionResponse,
-                mMockAccountRegistrar, mContext);
+                mMockAccountRegistrar, mContext, mFeatureFlags);
 
         mAccountToSub = new HashMap<>();
         phoneAccounts = new ArrayList<>();
@@ -144,7 +145,7 @@
                 });
         when(mMockAccountRegistrar.getAllPhoneAccounts(any(UserHandle.class), anyBoolean()))
                 .thenReturn(phoneAccounts);
-        when(mMockCall.getUserHandleFromTargetPhoneAccount()).
+        when(mMockCall.getAssociatedUser()).
                 thenReturn(Binder.getCallingUserHandle());
     }
 
@@ -517,6 +518,43 @@
     }
 
     /**
+     * Ensure that the call goes out on the PhoneAccount for the incoming call and not the
+     * Telephony preferred emergency account.
+     */
+    @SmallTest
+    @Test
+    public void testMTEmergencyCallMultiSimUserPreferred() throws Exception {
+        when(mMockCall.isEmergencyCall()).thenReturn(true);
+        when(mMockCall.isTestEmergencyCall()).thenReturn(false);
+        when(mMockCall.isIncoming()).thenReturn(true);
+        ConnectionServiceWrapper service = makeConnectionServiceWrapper();
+        PhoneAccount emergencyPhoneAccount1 = makeEmergencyPhoneAccount("tel_emer1", 0, null);
+        mapToSubSlot(emergencyPhoneAccount1, 1 /*subId*/, 0 /*slotId*/);
+        setTargetPhoneAccount(mMockCall, emergencyPhoneAccount1.getAccountHandle());
+        phoneAccounts.add(emergencyPhoneAccount1);
+        // Make this the user preferred phone account
+        setTargetPhoneAccount(mMockCall, emergencyPhoneAccount1.getAccountHandle());
+        PhoneAccount emergencyPhoneAccount2 = makeEmergencyPhoneAccount("tel_emer2",
+                PhoneAccount.CAPABILITY_EMERGENCY_PREFERRED, null);
+        mapToSubSlot(emergencyPhoneAccount2, 2 /*subId*/, 1 /*slotId*/);
+        phoneAccounts.add(emergencyPhoneAccount2);
+        PhoneAccountHandle emergencyPhoneAccountHandle2 = emergencyPhoneAccount2.getAccountHandle();
+
+        mTestCreateConnectionProcessor.process();
+
+        verify(mMockCall).setConnectionManagerPhoneAccount(
+                eq(emergencyPhoneAccount1.getAccountHandle()));
+        // The account we're using to place the call should be the user preferred account
+        verify(mMockCall).setTargetPhoneAccount(eq(emergencyPhoneAccount1.getAccountHandle()));
+        verify(mMockCall).setConnectionService(eq(service));
+        verify(service).createConnection(eq(mMockCall), any(CreateConnectionResponse.class));
+        // Notify successful connection to call
+        CallIdMapper mockCallIdMapper = mock(CallIdMapper.class);
+        mTestCreateConnectionProcessor.handleCreateConnectionSuccess(mockCallIdMapper, null);
+        verify(mMockCreateConnectionResponse).handleCreateConnectionSuccess(mockCallIdMapper, null);
+    }
+
+    /**
      * If the user preferred PhoneAccount is associated with an invalid slot, place on the other,
      * valid slot.
      */
@@ -805,7 +843,7 @@
         ConnectionServiceWrapper wrapper = mock(ConnectionServiceWrapper.class);
         when(mMockConnectionServiceRepository.getService(
                 eq(makeQuickConnectionServiceComponentName()),
-                any(UserHandle.class))).thenReturn(wrapper);
+                any(UserHandle.class), any(FeatureFlags.class))).thenReturn(wrapper);
         return wrapper;
     }
 
diff --git a/tests/src/com/android/server/telecom/tests/DisconnectedCallNotifierTest.java b/tests/src/com/android/server/telecom/tests/DisconnectedCallNotifierTest.java
index 2cdc23a..05c5071 100644
--- a/tests/src/com/android/server/telecom/tests/DisconnectedCallNotifierTest.java
+++ b/tests/src/com/android/server/telecom/tests/DisconnectedCallNotifierTest.java
@@ -151,6 +151,7 @@
         when(call.getDisconnectCause()).thenReturn(cause);
         when(call.getTargetPhoneAccount()).thenReturn(PHONE_ACCOUNT_HANDLE);
         when(call.getHandle()).thenReturn(TEL_CALL_HANDLE);
+        when(call.getAssociatedUser()).thenReturn(PHONE_ACCOUNT_HANDLE.getUserHandle());
         return call;
     }
 }
diff --git a/tests/src/com/android/server/telecom/tests/EmergencyCallDiagnosticLoggerTest.java b/tests/src/com/android/server/telecom/tests/EmergencyCallDiagnosticLoggerTest.java
new file mode 100644
index 0000000..c63a3d5
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/EmergencyCallDiagnosticLoggerTest.java
@@ -0,0 +1,308 @@
+/*
+ * Copyright (C) 2023 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.telephony.TelephonyManager.EmergencyCallDiagnosticParams;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doReturn;
+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.content.ComponentName;
+import android.net.Uri;
+import android.os.BugreportManager;
+import android.os.DropBoxManager;
+import android.os.UserHandle;
+import android.telecom.DisconnectCause;
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
+import android.telephony.TelephonyManager;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallState;
+import com.android.server.telecom.CallerInfoLookupHelper;
+import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.ClockProxy;
+import com.android.server.telecom.EmergencyCallDiagnosticLogger;
+import com.android.server.telecom.PhoneAccountRegistrar;
+import com.android.server.telecom.PhoneNumberUtilsAdapter;
+import com.android.server.telecom.TelecomSystem;
+import com.android.server.telecom.Timeouts;
+import com.android.server.telecom.ui.ToastFactory;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+@RunWith(JUnit4.class)
+public class EmergencyCallDiagnosticLoggerTest extends TelecomTestCase {
+
+    private static final ComponentName COMPONENT_NAME_1 = ComponentName
+            .unflattenFromString("com.foo/.Blah");
+    private static final PhoneAccountHandle SIM_1_HANDLE = new PhoneAccountHandle(
+            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 String DROP_BOX_TAG = "ecall_diagnostic_data";
+
+    private static final long EMERGENCY_CALL_ACTIVE_TIME_THRESHOLD_MILLIS = 100L;
+
+    private static final long EMERGENCY_CALL_TIME_BEFORE_USER_DISCONNECT_THRESHOLD_MILLIS = 120L;
+
+    private static final int DAYS_BACK_TO_SEARCH_EMERGENCY_DIAGNOSTIC_ENTRIES = 1;
+    private final TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() {
+    };
+    EmergencyCallDiagnosticLogger mEmergencyCallDiagnosticLogger;
+    @Mock
+    private Timeouts.Adapter mTimeouts;
+    @Mock
+    private CallsManager mMockCallsManager;
+    @Mock
+    private CallerInfoLookupHelper mMockCallerInfoLookupHelper;
+    @Mock
+    private PhoneAccountRegistrar mMockPhoneAccountRegistrar;
+    @Mock
+    private ClockProxy mMockClockProxy;
+    @Mock
+    private ToastFactory mMockToastProxy;
+    @Mock
+    private PhoneNumberUtilsAdapter mMockPhoneNumberUtilsAdapter;
+
+    @Mock
+    private TelephonyManager mTm;
+    @Mock
+    private BugreportManager mBrm;
+    @Mock
+    private DropBoxManager mDbm;
+
+    @Mock
+    private ClockProxy mClockProxy;
+
+    @Override
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+
+        doReturn(mMockCallerInfoLookupHelper).when(mMockCallsManager).getCallerInfoLookupHelper();
+        doReturn(mMockPhoneAccountRegistrar).when(mMockCallsManager).getPhoneAccountRegistrar();
+        doReturn(SIM_1_ACCOUNT).when(mMockPhoneAccountRegistrar).getPhoneAccountUnchecked(
+                eq(SIM_1_HANDLE));
+        when(mTimeouts.getEmergencyCallActiveTimeThresholdMillis()).
+                thenReturn(EMERGENCY_CALL_ACTIVE_TIME_THRESHOLD_MILLIS);
+        when(mTimeouts.getEmergencyCallTimeBeforeUserDisconnectThresholdMillis()).
+                thenReturn(EMERGENCY_CALL_TIME_BEFORE_USER_DISCONNECT_THRESHOLD_MILLIS);
+        when(mTimeouts.getDaysBackToSearchEmergencyDiagnosticEntries()).
+                thenReturn(DAYS_BACK_TO_SEARCH_EMERGENCY_DIAGNOSTIC_ENTRIES);
+        when(mClockProxy.currentTimeMillis()).thenReturn(System.currentTimeMillis());
+        when(mMockCallsManager.getCurrentUserHandle()).thenReturn(UserHandle.CURRENT);
+
+        mEmergencyCallDiagnosticLogger = new EmergencyCallDiagnosticLogger(mTm, mBrm,
+                mTimeouts, mDbm, Runnable::run, mClockProxy);
+    }
+
+    @Override
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
+        //reset(mTm);
+    }
+
+    /**
+     * Helper function that creates the call being tested.
+     * Also invokes onStartCreateConnection
+     */
+    private Call createCall(boolean isEmergencyCall, int direction) {
+        Call call = getCall();
+        call.setCallDirection(direction);
+        call.setIsEmergencyCall(isEmergencyCall);
+        mEmergencyCallDiagnosticLogger.onStartCreateConnection(call);
+        return call;
+    }
+
+    /**
+     * @return an instance of {@link Call} for testing purposes.
+     */
+    private Call getCall() {
+        return new Call(
+                "1", /* callId */
+                mContext,
+                mMockCallsManager,
+                mLock,
+                null /* ConnectionServiceRepository */,
+                mMockPhoneNumberUtilsAdapter,
+                Uri.parse("tel:6505551212"),
+                null /* GatewayInfo */,
+                null /* connectionManagerPhoneAccountHandle */,
+                SIM_1_HANDLE,
+                Call.CALL_DIRECTION_OUTGOING,
+                false /* shouldAttachToExistingConnection*/,
+                false /* isConference */,
+                mMockClockProxy,
+                mMockToastProxy,
+                mFeatureFlags);
+    }
+
+    /**
+     * Test that only outgoing emergency calls are tracked
+     */
+    @Test
+    public void testNonEmergencyCallNotTracked() {
+        //should not be tracked
+        createCall(false, Call.CALL_DIRECTION_OUTGOING);
+        assertEquals(0, mEmergencyCallDiagnosticLogger.getEmergencyCallsMap().size());
+
+        //should not be tracked (not in scope)
+        createCall(false, Call.CALL_DIRECTION_INCOMING);
+        assertEquals(0, mEmergencyCallDiagnosticLogger.getEmergencyCallsMap().size());
+    }
+
+    /**
+     * Test that incoming emergency calls are not tracked (not in scope right now)
+     */
+    @Test
+    public void testIncomingEmergencyCallsNotTracked() {
+        //should not be tracked
+        createCall(true, Call.CALL_DIRECTION_INCOMING);
+        assertEquals(0, mEmergencyCallDiagnosticLogger.getEmergencyCallsMap().size());
+    }
+
+
+    /**
+     * Test getDataCollectionTypes(reason)
+     */
+    @Test
+    public void testCollectionTypeForReasonDoesNotReturnUnreasonableValues() {
+        int reason = EmergencyCallDiagnosticLogger.REPORT_REASON_RANGE_START + 1;
+        while (reason < EmergencyCallDiagnosticLogger.REPORT_REASON_RANGE_END) {
+            List<Integer> ctypes = EmergencyCallDiagnosticLogger.getDataCollectionTypes(reason);
+            assertNotNull(ctypes);
+            Set<Integer> ctypesSet = new HashSet<>(ctypes);
+
+            //assert that list is not empty
+            assertNotEquals(0, ctypes.size());
+
+            //assert no repeated values
+            assertEquals(ctypes.size(), ctypesSet.size());
+
+            //if bugreport type is present, that should be the only collection type
+            if (ctypesSet.contains(EmergencyCallDiagnosticLogger.COLLECTION_TYPE_BUGREPORT)) {
+                assertEquals(1, ctypes.size());
+            }
+            reason++;
+        }
+    }
+
+
+    /**
+     * Test emergency call reported stuck
+     */
+    @Test
+    public void testStuckEmergencyCall() {
+        Call call = createCall(true, Call.CALL_DIRECTION_OUTGOING);
+        mEmergencyCallDiagnosticLogger.onCallAdded(call);
+        mEmergencyCallDiagnosticLogger.reportStuckCall(call);
+
+        //for stuck calls, we should always be persisting some data
+        ArgumentCaptor<EmergencyCallDiagnosticParams> captor =
+                ArgumentCaptor.forClass(EmergencyCallDiagnosticParams.class);
+        verify(mTm, times(1)).persistEmergencyCallDiagnosticData(eq(DROP_BOX_TAG),
+                captor.capture());
+        EmergencyCallDiagnosticParams dp = captor.getValue();
+
+        assertNotNull(dp);
+        assertTrue(
+                dp.isLogcatCollectionEnabled() || dp.isTelecomDumpSysCollectionEnabled()
+                        || dp.isTelephonyDumpSysCollectionEnabled());
+
+        //tracking should end
+        assertEquals(0, mEmergencyCallDiagnosticLogger.getEmergencyCallsMap().size());
+    }
+
+    @Test
+    public void testEmergencyCallNeverWentActiveWithNonLocalDisconnectCause() {
+        Call call = createCall(true, Call.CALL_DIRECTION_OUTGOING);
+        mEmergencyCallDiagnosticLogger.onCallAdded(call);
+
+        //call is tracked
+        assertEquals(1, mEmergencyCallDiagnosticLogger.getEmergencyCallsMap().size());
+
+        call.setDisconnectCause(new DisconnectCause(DisconnectCause.REJECTED));
+        mEmergencyCallDiagnosticLogger.onCallRemoved(call);
+
+        //for non-local disconnect of non-active call,  we should always be persisting some data
+        ArgumentCaptor<TelephonyManager.EmergencyCallDiagnosticParams> captor =
+                ArgumentCaptor.forClass(
+                        TelephonyManager.EmergencyCallDiagnosticParams.class);
+        verify(mTm, times(1)).persistEmergencyCallDiagnosticData(eq(DROP_BOX_TAG),
+                captor.capture());
+        TelephonyManager.EmergencyCallDiagnosticParams dp = captor.getValue();
+
+        assertNotNull(dp);
+        assertTrue(
+                dp.isLogcatCollectionEnabled() || dp.isTelecomDumpSysCollectionEnabled()
+                        || dp.isTelephonyDumpSysCollectionEnabled());
+
+        //tracking should end
+        assertEquals(0, mEmergencyCallDiagnosticLogger.getEmergencyCallsMap().size());
+    }
+
+    @Test
+    public void testEmergencyCallWentActiveForLongDuration_shouldNotCollectDiagnostics()
+            throws Exception {
+        Call call = createCall(true, Call.CALL_DIRECTION_OUTGOING);
+        mEmergencyCallDiagnosticLogger.onCallAdded(call);
+
+        //call went active
+        mEmergencyCallDiagnosticLogger.onCallStateChanged(call, CallState.DIALING,
+                CallState.ACTIVE);
+
+        //return large value for time when call is disconnected
+        when(mClockProxy.currentTimeMillis()).thenReturn(System.currentTimeMillis() + 10000L);
+
+        call.setDisconnectCause(new DisconnectCause(DisconnectCause.ERROR));
+        mEmergencyCallDiagnosticLogger.onCallRemoved(call);
+
+        //no diagnostic data should be persisted
+        verify(mTm, never()).persistEmergencyCallDiagnosticData(eq(DROP_BOX_TAG),
+                any());
+
+        //tracking should end
+        assertEquals(0, mEmergencyCallDiagnosticLogger.getEmergencyCallsMap().size());
+    }
+
+}
diff --git a/tests/src/com/android/server/telecom/tests/EmergencyCallHelperTest.java b/tests/src/com/android/server/telecom/tests/EmergencyCallHelperTest.java
index 380e327..692d720 100644
--- a/tests/src/com/android/server/telecom/tests/EmergencyCallHelperTest.java
+++ b/tests/src/com/android/server/telecom/tests/EmergencyCallHelperTest.java
@@ -19,9 +19,11 @@
 import static android.Manifest.permission.ACCESS_BACKGROUND_LOCATION;
 import static android.Manifest.permission.ACCESS_FINE_LOCATION;
 
-import android.content.Context;
+import android.content.ComponentName;
+import android.content.ContentResolver;
 import android.content.pm.PackageManager;
 import android.os.UserHandle;
+import android.telecom.PhoneAccountHandle;
 import android.test.suitebuilder.annotation.SmallTest;
 
 import com.android.server.telecom.Call;
@@ -34,19 +36,20 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
-import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+
 import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.any;
 
 @RunWith(JUnit4.class)
 public class EmergencyCallHelperTest extends TelecomTestCase {
@@ -62,6 +65,7 @@
   private UserHandle mUserHandle;
   @Mock
   private Call mCall;
+  @Mock private PhoneAccountHandle mPhoneAccountHandle;
 
   @Override
   @Before
@@ -86,6 +90,8 @@
     when(mCall.isEmergencyCall()).thenReturn(true);
     when(mContext.getResources().getBoolean(R.bool.grant_location_permission_enabled)).thenReturn(
         true);
+    when(mTimeoutsAdapter.getEmergencyCallbackWindowMillis(any(ContentResolver.class))).thenReturn(
+            5000L);
   }
 
   @Override
@@ -245,4 +251,21 @@
     verifyRevokeNotInvokedFor(ACCESS_FINE_LOCATION);
     verifyRevokeInvokedFor(ACCESS_BACKGROUND_LOCATION);
   }
+
+  @SmallTest
+  @Test
+  public void testIsLastOutgoingEmergencyCallPAH() {
+    PhoneAccountHandle dummyHandle = new PhoneAccountHandle(new ComponentName("pkg", "cls"), "foo");
+    long currentTimeMillis = System.currentTimeMillis();
+    mEmergencyCallHelper.setLastOutgoingEmergencyCallPAH(mPhoneAccountHandle);
+    mEmergencyCallHelper.setLastOutgoingEmergencyCallTimestampMillis(currentTimeMillis);
+
+    // Verify that ECBM is active on mPhoneAccountHandle.
+    assertTrue(mEmergencyCallHelper.isLastOutgoingEmergencyCallPAH(mPhoneAccountHandle));
+    assertFalse(mEmergencyCallHelper.isLastOutgoingEmergencyCallPAH(dummyHandle));
+
+    // Expire ECBM and verify that mPhoneAccountHandle is no longer supported for ECBM.
+    mEmergencyCallHelper.setLastOutgoingEmergencyCallTimestampMillis(currentTimeMillis/2);
+    assertFalse(mEmergencyCallHelper.isLastOutgoingEmergencyCallPAH(mPhoneAccountHandle));
+  }
 }
diff --git a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
index f50753d..cd8431a 100644
--- a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
+++ b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
@@ -22,6 +22,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.junit.Assert.fail;
@@ -35,7 +36,6 @@
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -53,10 +53,12 @@
 import android.app.UiModeManager;
 import android.content.AttributionSource;
 import android.content.AttributionSourceState;
+import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.PermissionChecker;
 import android.content.ServiceConnection;
 import android.content.pm.ApplicationInfo;
@@ -64,9 +66,10 @@
 import android.content.pm.PermissionInfo;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
+import android.content.pm.UserInfo;
 import android.content.res.Resources;
 import android.compat.testing.PlatformCompatChangeRule;
-import android.os.Binder;
+import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
@@ -74,6 +77,7 @@
 import android.os.Looper;
 import android.os.Process;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.permission.PermissionCheckerManager;
 import android.telecom.CallAudioState;
 import android.telecom.InCallService;
@@ -122,14 +126,13 @@
 import org.mockito.quality.Strictness;
 import org.mockito.stubbing.Answer;
 
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.CompletableFuture;
 
-import libcore.junit.util.compat.CoreCompatChangeRule;
-
 @RunWith(JUnit4.class)
 public class InCallControllerTests extends TelecomTestCase {
     @Mock CallsManager mMockCallsManager;
@@ -138,6 +141,8 @@
     @Mock PackageManager mMockPackageManager;
     @Mock PermissionCheckerManager mMockPermissionCheckerManager;
     @Mock Call mMockCall;
+    @Mock Call mMockSystemCall1;
+    @Mock Call mMockSystemCall2;
     @Mock Resources mMockResources;
     @Mock AppOpsManager mMockAppOpsManager;
     @Mock MockContext mMockContext;
@@ -150,29 +155,32 @@
     @Mock PermissionInfo mMockPermissionInfo;
     @Mock InCallController.InCallServiceInfo mInCallServiceInfo;
     @Mock private AnomalyReporterAdapter mAnomalyReporterAdapter;
+    @Mock UserManager mMockUserManager;
+    @Mock UserInfo mMockUserInfo;
+    @Mock UserInfo mMockChildUserInfo; //work profile
 
     @Rule
     public TestRule compatChangeRule = new PlatformCompatChangeRule();
 
-    private static final int CURRENT_USER_ID = 900973;
+    private static final int CURRENT_USER_ID = 9;
     private static final String DEF_PKG = "defpkg";
     private static final String DEF_CLASS = "defcls";
-    private static final int DEF_UID = 1;
+    private static final int DEF_UID = 900972;
     private static final String SYS_PKG = "syspkg";
     private static final String SYS_CLASS = "syscls";
-    private static final int SYS_UID = 2;
+    private static final int SYS_UID = 900971;
     private static final String COMPANION_PKG = "cpnpkg";
     private static final String COMPANION_CLASS = "cpncls";
-    private static final int COMPANION_UID = 3;
+    private static final int COMPANION_UID = 900970;
     private static final String CAR_PKG = "carpkg";
     private static final String CAR2_PKG = "carpkg2";
     private static final String CAR_CLASS = "carcls";
     private static final String CAR2_CLASS = "carcls";
-    private static final int CAR_UID = 4;
-    private static final int CAR2_UID = 5;
+    private static final int CAR_UID = 900969;
+    private static final int CAR2_UID = 900968;
     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 int NONUI_UID = 900973;
     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;
@@ -180,6 +188,7 @@
     private static final PhoneAccountHandle PA_HANDLE =
             new PhoneAccountHandle(new ComponentName("pa_pkg", "pa_cls"),
                     "pa_id_0", UserHandle.of(CURRENT_USER_ID));
+    private static final UserHandle DUMMY_USER_HANDLE = UserHandle.of(10);
 
     private UserHandle mUserHandle = UserHandle.of(CURRENT_USER_ID);
     private InCallController mInCallController;
@@ -187,18 +196,24 @@
     private EmergencyCallHelper mEmergencyCallHelper;
     private SystemStateHelper.SystemStateListener mSystemStateListener;
     private CarModeTracker mCarModeTracker = spy(new CarModeTracker());
+    private BroadcastReceiver mRegisteredReceiver;
 
     private final int serviceBindingFlags = Context.BIND_AUTO_CREATE
         | Context.BIND_FOREGROUND_SERVICE | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS
         | Context.BIND_SCHEDULE_LIKE_TOP_APP;
 
+    private UserHandle mChildUserHandle = UserHandle.of(10);
+    private @Mock Call mMockChildUserCall;
+    private UserHandle mParentUserHandle = UserHandle.of(1);
+
     @Override
     @Before
     public void setUp() throws Exception {
         super.setUp();
         MockitoAnnotations.initMocks(this);
         when(mMockCall.getAnalytics()).thenReturn(new Analytics.CallInfo());
-        when(mMockCall.getUserHandleFromTargetPhoneAccount()).thenReturn(mUserHandle);
+        when(mMockCall.getAssociatedUser()).thenReturn(mUserHandle);
+        when(mMockCall.getId()).thenReturn("TC@1");
         doReturn(mMockResources).when(mMockContext).getResources();
         doReturn(mMockAppOpsManager).when(mMockContext).getSystemService(AppOpsManager.class);
         doReturn(SYS_PKG).when(mMockResources).getString(
@@ -221,7 +236,13 @@
                 "com.android.server.telecom.tests", null));
         mInCallController = new InCallController(mMockContext, mLock, mMockCallsManager,
                 mMockSystemStateHelper, mDefaultDialerCache, mTimeoutsAdapter,
-                mEmergencyCallHelper, mCarModeTracker, mClockProxy);
+                mEmergencyCallHelper, mCarModeTracker, mClockProxy, mFeatureFlags);
+        // Capture the broadcast receiver registered.
+        doAnswer(invocation -> {
+            mRegisteredReceiver = invocation.getArgument(0);
+            return null;
+        }).when(mMockContext).registerReceiverAsUser(any(BroadcastReceiver.class),
+                any(), any(IntentFilter.class), any(), any());
 
         ArgumentCaptor<SystemStateHelper.SystemStateListener> systemStateListenerArgumentCaptor
                 = ArgumentCaptor.forClass(SystemStateHelper.SystemStateListener.class);
@@ -281,6 +302,13 @@
                 .thenReturn(PackageManager.PERMISSION_DENIED);
 
         when(mMockCallsManager.getAudioState()).thenReturn(new CallAudioState(false, 0, 0));
+
+        when(mMockContext.getSystemService(eq(Context.USER_SERVICE))).thenReturn(mMockUserManager);
+        when(mMockContext.getSystemService(eq(UserManager.class)))
+                .thenReturn(mMockUserManager);
+        // Mock user info to allow binding on user stored in the phone account (mUserHandle).
+        when(mMockUserManager.getUserInfo(anyInt())).thenReturn(mMockUserInfo);
+        when(mMockUserInfo.isManagedProfile()).thenReturn(true);
     }
 
     @Override
@@ -396,6 +424,7 @@
         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
         when(mMockCallsManager.isInEmergencyCall()).thenReturn(false);
         when(mMockCall.isIncoming()).thenReturn(false);
+        when(mMockCall.getAssociatedUser()).thenReturn(mUserHandle);
         when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
         when(mMockCall.getIntentExtras()).thenReturn(callExtras);
         when(mMockCall.isExternalCall()).thenReturn(false);
@@ -425,60 +454,6 @@
 
     @MediumTest
     @Test
-    public void testBindToService_OutgoingCall_FailToBind_AnomalyReported() throws Exception {
-        mInCallController.setAnomalyReporterAdapter(mAnomalyReporterAdapter);
-        when(mMockContext.bindServiceAsUser(any(Intent.class), any(ServiceConnection.class),
-                anyInt(), any(UserHandle.class))).thenReturn(false);
-        Bundle callExtras = new Bundle();
-        callExtras.putBoolean("whatever", true);
-
-        when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
-        when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
-        when(mMockCallsManager.isInEmergencyCall()).thenReturn(false);
-        when(mMockCall.isIncoming()).thenReturn(false);
-        when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
-        when(mMockCall.getIntentExtras()).thenReturn(callExtras);
-        when(mMockCall.isExternalCall()).thenReturn(false);
-        when(mTimeoutsAdapter.getEmergencyCallbackWindowMillis(any(ContentResolver.class)))
-                .thenReturn(300_000L);
-
-        setupMockPackageManager(false /* default */, true /* system */, false /* external calls */);
-        mInCallController.bindToServices(mMockCall);
-
-        verify(mAnomalyReporterAdapter).reportAnomaly(InCallController.BIND_TO_IN_CALL_ERROR_UUID,
-                InCallController.BIND_TO_IN_CALL_ERROR_MSG);
-    }
-
-    @MediumTest
-    @Test
-    public void testBindToService_OutgoingEmergCall_FailToBind_AnomalyReported() throws Exception {
-        mInCallController.setAnomalyReporterAdapter(mAnomalyReporterAdapter);
-        when(mMockContext.bindServiceAsUser(any(Intent.class), any(ServiceConnection.class),
-                anyInt(), any(UserHandle.class))).thenReturn(false);
-        Bundle callExtras = new Bundle();
-        callExtras.putBoolean("whatever", true);
-
-        when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
-        when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
-        when(mMockCallsManager.isInEmergencyCall()).thenReturn(false);
-        when(mMockCall.isEmergencyCall()).thenReturn(true);
-        when(mMockCall.isIncoming()).thenReturn(false);
-        when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
-        when(mMockCall.getIntentExtras()).thenReturn(callExtras);
-        when(mMockCall.isExternalCall()).thenReturn(false);
-        when(mTimeoutsAdapter.getEmergencyCallbackWindowMillis(any(ContentResolver.class)))
-                .thenReturn(300_000L);
-
-        setupMockPackageManager(false /* default */, true /* system */, false /* external calls */);
-        mInCallController.bindToServices(mMockCall);
-
-        verify(mAnomalyReporterAdapter).reportAnomaly(
-                InCallController.BIND_TO_IN_CALL_EMERGENCY_ERROR_UUID,
-                InCallController.BIND_TO_IN_CALL_EMERGENCY_ERROR_MSG);
-    }
-
-    @MediumTest
-    @Test
     public void testBindToService_DefaultDialer_NoEmergency() throws Exception {
         Bundle callExtras = new Bundle();
         callExtras.putBoolean("whatever", true);
@@ -487,6 +462,7 @@
         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
         when(mMockCallsManager.isInEmergencyCall()).thenReturn(false);
         when(mMockCall.isIncoming()).thenReturn(false);
+        when(mMockCall.getAssociatedUser()).thenReturn(mUserHandle);
         when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
         when(mMockCall.getIntentExtras()).thenReturn(callExtras);
         when(mMockCall.isExternalCall()).thenReturn(false);
@@ -539,7 +515,11 @@
         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
         when(mMockCallsManager.isInEmergencyCall()).thenReturn(true);
         when(mMockCall.isEmergencyCall()).thenReturn(true);
+        when(mMockContext.getSystemService(eq(UserManager.class)))
+            .thenReturn(mMockUserManager);
+        when(mMockUserManager.isQuietModeEnabled(any(UserHandle.class))).thenReturn(false);
         when(mMockCall.isIncoming()).thenReturn(false);
+        when(mMockCall.getAssociatedUser()).thenReturn(mUserHandle);
         when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
         when(mMockCall.getIntentExtras()).thenReturn(callExtras);
         when(mMockCall.isExternalCall()).thenReturn(false);
@@ -598,6 +578,125 @@
                 eq(Manifest.permission.ACCESS_FINE_LOCATION), eq(mUserHandle));
     }
 
+    @MediumTest
+    @Test
+    public void
+    testBindToService_UserAssociatedWithCallIsInQuietMode_EmergCallInCallUi_BindsToPrimaryUser()
+        throws Exception {
+        when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
+        when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+        when(mMockCall.isEmergencyCall()).thenReturn(true);
+        when(mMockCall.isIncoming()).thenReturn(true);
+        when(mMockCall.getAssociatedUser()).thenReturn(DUMMY_USER_HANDLE);
+        when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
+        when(mMockContext.getSystemService(eq(UserManager.class)))
+            .thenReturn(mMockUserManager);
+        when(mMockUserManager.isQuietModeEnabled(any(UserHandle.class))).thenReturn(true);
+        setupMockPackageManager(true /* default */, true /* system */, false /* external calls */);
+        setupMockPackageManagerLocationPermission(SYS_PKG, false /* granted */);
+
+        mInCallController.bindToServices(mMockCall);
+
+        ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mMockContext, times(1)).bindServiceAsUser(
+            bindIntentCaptor.capture(),
+            any(ServiceConnection.class),
+            eq(serviceBindingFlags),
+            eq(mUserHandle));
+        Intent bindIntent = bindIntentCaptor.getValue();
+        assertEquals(InCallService.SERVICE_INTERFACE, bindIntent.getAction());
+    }
+
+    @MediumTest
+    @Test
+    public void
+    testBindToService_UserAssociatedWithCallIsInQuietMode_NonEmergCallECBM_BindsToPrimaryUser()
+            throws Exception {
+        when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
+        when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+        when(mMockCall.isEmergencyCall()).thenReturn(false);
+        when(mMockCall.isInECBM()).thenReturn(true);
+        when(mMockCall.isIncoming()).thenReturn(true);
+        when(mMockCall.getAssociatedUser()).thenReturn(DUMMY_USER_HANDLE);
+        when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
+        when(mMockContext.getSystemService(eq(UserManager.class)))
+                .thenReturn(mMockUserManager);
+        when(mMockUserManager.isQuietModeEnabled(any(UserHandle.class))).thenReturn(true);
+        setupMockPackageManager(true /* default */, true /* system */, false /* external calls */);
+        setupMockPackageManagerLocationPermission(SYS_PKG, false /* granted */);
+
+        mInCallController.bindToServices(mMockCall);
+
+        ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mMockContext, times(1)).bindServiceAsUser(
+                bindIntentCaptor.capture(),
+                any(ServiceConnection.class),
+                eq(serviceBindingFlags),
+                eq(mUserHandle));
+        Intent bindIntent = bindIntentCaptor.getValue();
+        assertEquals(InCallService.SERVICE_INTERFACE, bindIntent.getAction());
+    }
+
+    @MediumTest
+    @Test
+    public void
+    testBindToService_UserAssociatedWithCallSecondary_NonEmergCallECBM_BindsToSecondaryUser()
+            throws Exception {
+        UserHandle newUser = new UserHandle(13);
+        when(mMockCallsManager.getCurrentUserHandle()).thenReturn(newUser);
+        when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+        when(mMockCall.isEmergencyCall()).thenReturn(false);
+        when(mMockCall.isInECBM()).thenReturn(true);
+        when(mMockCall.isIncoming()).thenReturn(true);
+        when(mMockCall.getAssociatedUser()).thenReturn(DUMMY_USER_HANDLE);
+        when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
+        when(mMockContext.getSystemService(eq(UserManager.class)))
+                .thenReturn(mMockUserManager);
+        when(mMockUserManager.isQuietModeEnabled(any(UserHandle.class))).thenReturn(false);
+        when(mMockUserManager.isUserAdmin(anyInt())).thenReturn(false);
+        setupMockPackageManager(true /* default */, true /* system */, false /* external calls */);
+        setupMockPackageManagerLocationPermission(SYS_PKG, false /* granted */);
+
+        mInCallController.bindToServices(mMockCall);
+
+        ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mMockContext, times(1)).bindServiceAsUser(
+                bindIntentCaptor.capture(),
+                any(ServiceConnection.class),
+                eq(serviceBindingFlags),
+                eq(newUser));
+        Intent bindIntent = bindIntentCaptor.getValue();
+        assertEquals(InCallService.SERVICE_INTERFACE, bindIntent.getAction());
+    }
+
+    @MediumTest
+    @Test
+    public void
+    testBindToService_UserAssociatedWithCallNotInQuietMode_EmergCallInCallUi_BindsToAssociatedUser()
+        throws Exception {
+        when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
+        when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+        when(mMockCall.isEmergencyCall()).thenReturn(true);
+        when(mMockCall.getAssociatedUser()).thenReturn(DUMMY_USER_HANDLE);
+        when(mMockContext.getSystemService(eq(UserManager.class)))
+            .thenReturn(mMockUserManager);
+        when(mMockUserManager.isQuietModeEnabled(any(UserHandle.class))).thenReturn(false);
+        when(mMockUserManager.isUserAdmin(anyInt())).thenReturn(true);
+        setupMockPackageManager(true /* default */, true /* system */, false /* external calls */);
+        setupMockPackageManagerLocationPermission(SYS_PKG, false /* granted */);
+
+        mInCallController.bindToServices(mMockCall);
+
+        ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mMockContext, times(1)).bindServiceAsUser(
+            bindIntentCaptor.capture(),
+            any(ServiceConnection.class),
+            eq(serviceBindingFlags),
+            eq(DUMMY_USER_HANDLE));
+        Intent bindIntent = bindIntentCaptor.getValue();
+        assertEquals(InCallService.SERVICE_INTERFACE, bindIntent.getAction());
+    }
+
     /**
      * This test verifies the behavior of Telecom when the system dialer crashes on binding and must
      * be restarted.  Specifically, it ensures when the system dialer crashes we revoke the runtime
@@ -614,7 +713,11 @@
         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
         when(mMockCallsManager.isInEmergencyCall()).thenReturn(true);
         when(mMockCall.isEmergencyCall()).thenReturn(true);
+        when(mMockContext.getSystemService(eq(UserManager.class)))
+            .thenReturn(mMockUserManager);
+        when(mMockUserManager.isQuietModeEnabled(any(UserHandle.class))).thenReturn(false);
         when(mMockCall.isIncoming()).thenReturn(false);
+        when(mMockCall.getAssociatedUser()).thenReturn(mUserHandle);
         when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
         when(mMockCall.getIntentExtras()).thenReturn(callExtras);
         when(mMockCall.isExternalCall()).thenReturn(false);
@@ -701,6 +804,7 @@
         when(mMockCallsManager.getAudioState()).thenReturn(null);
         when(mMockCallsManager.canAddCall()).thenReturn(false);
         when(mMockCall.isIncoming()).thenReturn(false);
+        when(mMockCall.getAssociatedUser()).thenReturn(mUserHandle);
         when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
         when(mMockCall.getIntentExtras()).thenReturn(callExtras);
         when(mMockCall.isExternalCall()).thenReturn(false);
@@ -781,6 +885,7 @@
         when(mMockCallsManager.isInEmergencyCall()).thenReturn(false);
         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
         when(mMockCall.isIncoming()).thenReturn(false);
+        when(mMockCall.getAssociatedUser()).thenReturn(mUserHandle);
         when(mMockCall.isExternalCall()).thenReturn(false);
         when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
         when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
@@ -870,6 +975,217 @@
     }
 
     /**
+     * This test verifies the behavior of Telecom when the system dialer crashes on binding and must
+     * be restarted.  Specifically, it ensures when the system dialer crashes we revoke the runtime
+     * location permission, and when it restarts we re-grant the permission.
+     * @throws Exception
+     */
+    @MediumTest
+    @Test
+    public void testBindToLateConnectionNonUiIcs() throws Exception {
+        Bundle callExtras = new Bundle();
+        callExtras.putBoolean("whatever", true);
+
+        // Make a basic call and bind to the default dialer.
+        when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
+        when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+        when(mMockCallsManager.isInEmergencyCall()).thenReturn(true);
+        when(mMockCall.isEmergencyCall()).thenReturn(true);
+        when(mMockContext.getSystemService(eq(UserManager.class)))
+                .thenReturn(mMockUserManager);
+        when(mMockUserManager.isQuietModeEnabled(any(UserHandle.class))).thenReturn(false);
+        when(mMockCall.isIncoming()).thenReturn(false);
+        when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
+        when(mMockCall.getIntentExtras()).thenReturn(callExtras);
+        when(mMockCall.isExternalCall()).thenReturn(false);
+        when(mMockCall.getAssociatedUser()).thenReturn(mUserHandle);
+        when(mDefaultDialerCache.getDefaultDialerApplication(CURRENT_USER_ID))
+                .thenReturn(DEF_PKG);
+        ArgumentCaptor<ServiceConnection> serviceConnectionCaptor =
+                ArgumentCaptor.forClass(ServiceConnection.class);
+        when(mMockContext.bindServiceAsUser(any(Intent.class), serviceConnectionCaptor.capture(),
+                eq(serviceBindingFlags),
+                eq(mUserHandle))).thenReturn(true);
+        when(mTimeoutsAdapter.getEmergencyCallbackWindowMillis(any(ContentResolver.class)))
+                .thenReturn(300_000L);
+
+        // Setup package manager; there is a dialer and disable non-ui ICS
+        when(mMockPackageManager.queryIntentServicesAsUser(
+                any(Intent.class), anyInt(), anyInt())).thenReturn(
+                Arrays.asList(
+                        getDefResolveInfo(false /* externalCalls */, false /* selfMgd */),
+                        getNonUiResolveinfo(false /* selfManaged */,
+                                false /* isEnabled */)
+                )
+        );
+        when(mMockPackageManager
+                .getComponentEnabledSetting(new ComponentName(DEF_PKG, DEF_CLASS)))
+                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+        when(mMockPackageManager
+                .getComponentEnabledSetting(new ComponentName(NONUI_PKG, NONUI_CLASS)))
+                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DISABLED);
+
+        mInCallController.addCall(mMockCall);
+        mInCallController.bindToServices(mMockCall);
+
+        // There will be 4 calls for the various types of ICS.
+        verify(mMockPackageManager, times(4)).queryIntentServicesAsUser(
+                any(Intent.class),
+                eq(PackageManager.GET_META_DATA | PackageManager.MATCH_DISABLED_COMPONENTS),
+                eq(CURRENT_USER_ID));
+
+        // Verify bind to the dialer
+        ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mMockContext, times(1)).bindServiceAsUser(
+                bindIntentCaptor.capture(),
+                any(ServiceConnection.class),
+                eq(serviceBindingFlags),
+                eq(mUserHandle));
+
+        Intent bindIntent = bindIntentCaptor.getValue();
+        assertEquals(InCallService.SERVICE_INTERFACE, bindIntent.getAction());
+        assertEquals(SYS_PKG, bindIntent.getComponent().getPackageName());
+        assertEquals(SYS_CLASS, bindIntent.getComponent().getClassName());
+
+        // Setup mocks to enable nonui ICS
+        when(mMockPackageManager.queryIntentServicesAsUser(
+                any(Intent.class), anyInt(), anyInt())).thenReturn(
+                        Arrays.asList(
+                                getDefResolveInfo(false /* externalCalls */, false /* selfMgd */),
+                                getNonUiResolveinfo(false /* selfManaged */,
+                                        true /* isEnabled */)
+                        )
+        );
+        when(mMockPackageManager
+                .getComponentEnabledSetting(new ComponentName(NONUI_PKG, NONUI_CLASS)))
+                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+
+        // Emulate a late enable of the non-ui ICS
+        Intent packageUpdated = new Intent(Intent.ACTION_PACKAGE_CHANGED);
+        packageUpdated.setData(Uri.fromParts("package", NONUI_PKG, null));
+        packageUpdated.putExtra(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST,
+                new String[] {NONUI_CLASS});
+        packageUpdated.putExtra(Intent.EXTRA_UID, NONUI_UID);
+        mRegisteredReceiver.onReceive(mMockContext, packageUpdated);
+
+        // Now, we expect to auto-rebind to the system dialer (verify 2 times since this is the
+        // second binding).
+        verify(mMockContext, times(2)).bindServiceAsUser(
+                bindIntentCaptor.capture(),
+                any(ServiceConnection.class),
+                eq(serviceBindingFlags),
+                eq(mUserHandle));
+
+        // Unbind!
+        mInCallController.unbindFromServices(UserHandle.of(CURRENT_USER_ID));
+
+        // Make sure we unbound 2 times
+        verify(mMockContext, times(2)).unbindService(any(ServiceConnection.class));
+    }
+
+    /**
+     * Tests a case where InCallController DOES NOT bind to ANY InCallServices when the call is
+     * first added, but then one becomes available after the call starts.  This test was originally
+     * added to reproduce a bug which would cause the call id mapper in the InCallController to not
+     * track a newly added call unless something was bound when the call was first added.
+     * @throws Exception
+     */
+    @MediumTest
+    @Test
+    public void testNoInitialBinding() throws Exception {
+        Bundle callExtras = new Bundle();
+        callExtras.putBoolean("whatever", true);
+
+        // Make a basic call
+        when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
+        when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+        when(mMockCallsManager.isInEmergencyCall()).thenReturn(true);
+        when(mMockCall.isEmergencyCall()).thenReturn(true);
+        when(mMockContext.getSystemService(eq(UserManager.class)))
+                .thenReturn(mMockUserManager);
+        when(mMockUserManager.isQuietModeEnabled(any(UserHandle.class))).thenReturn(false);
+        when(mMockCall.isIncoming()).thenReturn(false);
+        when(mMockCall.getAssociatedUser()).thenReturn(mUserHandle);
+        when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
+        when(mMockCall.getIntentExtras()).thenReturn(callExtras);
+        when(mMockCall.isExternalCall()).thenReturn(false);
+        when(mMockCall.isSelfManaged()).thenReturn(true);
+        when(mMockCall.visibleToInCallService()).thenReturn(true);
+
+        // Dialer doesn't handle these calls, but non-UI ICS does.
+        when(mDefaultDialerCache.getDefaultDialerApplication(CURRENT_USER_ID))
+                .thenReturn(DEF_PKG);
+        ArgumentCaptor<ServiceConnection> serviceConnectionCaptor =
+                ArgumentCaptor.forClass(ServiceConnection.class);
+        when(mMockContext.bindServiceAsUser(any(Intent.class), serviceConnectionCaptor.capture(),
+                eq(serviceBindingFlags),
+                eq(mUserHandle))).thenReturn(true);
+        when(mTimeoutsAdapter.getEmergencyCallbackWindowMillis(any(ContentResolver.class)))
+                .thenReturn(300_000L);
+
+        // Setup package manager; there is a dialer and disable non-ui ICS
+        when(mMockPackageManager.queryIntentServicesAsUser(
+                any(Intent.class), anyInt(), anyInt())).thenReturn(
+                Arrays.asList(
+                        getDefResolveInfo(false /* externalCalls */, false /* selfMgd */),
+                        getNonUiResolveinfo(true /* selfManaged */,
+                                false /* isEnabled */)
+                )
+        );
+        when(mMockPackageManager
+                .getComponentEnabledSetting(new ComponentName(DEF_PKG, DEF_CLASS)))
+                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+        when(mMockPackageManager
+                .getComponentEnabledSetting(new ComponentName(NONUI_PKG, NONUI_CLASS)))
+                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DISABLED);
+
+        // Add the call.
+        mInCallController.onCallAdded(mMockCall);
+
+        // There will be 4 calls for the various types of ICS; this is normal.
+        verify(mMockPackageManager, times(4)).queryIntentServicesAsUser(
+                any(Intent.class),
+                eq(PackageManager.GET_META_DATA | PackageManager.MATCH_DISABLED_COMPONENTS),
+                eq(CURRENT_USER_ID));
+
+        // Verify no bind at this point
+        ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mMockContext, never()).bindServiceAsUser(
+                bindIntentCaptor.capture(),
+                any(ServiceConnection.class),
+                eq(serviceBindingFlags),
+                eq(mUserHandle));
+
+        // Setup mocks to enable non-ui ICS
+        when(mMockPackageManager.queryIntentServicesAsUser(
+                any(Intent.class), anyInt(), anyInt())).thenReturn(
+                Arrays.asList(
+                        getDefResolveInfo(false /* externalCalls */, false /* selfMgd */),
+                        getNonUiResolveinfo(true /* selfManaged */,
+                                true /* isEnabled */)
+                )
+        );
+        when(mMockPackageManager
+                .getComponentEnabledSetting(new ComponentName(NONUI_PKG, NONUI_CLASS)))
+                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+
+        // Emulate a late enable of the non-ui ICS
+        Intent packageUpdated = new Intent(Intent.ACTION_PACKAGE_CHANGED);
+        packageUpdated.setData(Uri.fromParts("package", NONUI_PKG, null));
+        packageUpdated.putExtra(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST,
+                new String[] {NONUI_CLASS});
+        packageUpdated.putExtra(Intent.EXTRA_UID, NONUI_UID);
+        mRegisteredReceiver.onReceive(mMockContext, packageUpdated);
+
+        // Make sure we bound to it.
+        verify(mMockContext, times(1)).bindServiceAsUser(
+                bindIntentCaptor.capture(),
+                any(ServiceConnection.class),
+                eq(serviceBindingFlags),
+                eq(mUserHandle));
+    }
+
+    /**
      * Ensures that the {@link InCallController} will bind to an {@link InCallService} which
      * supports external calls.
      */
@@ -1241,6 +1557,7 @@
         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
         when(mMockCallsManager.isInEmergencyCall()).thenReturn(false);
         when(mMockCall.isIncoming()).thenReturn(false);
+        when(mMockCall.getAssociatedUser()).thenReturn(mUserHandle);
         when(mMockCall.isExternalCall()).thenReturn(false);
         when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
         when(mDefaultDialerCache.getDefaultDialerApplication(CURRENT_USER_ID)).thenReturn(DEF_PKG);
@@ -1455,6 +1772,199 @@
                 android.telecom.Call.EXTRA_IS_SUPPRESSED_BY_DO_NOT_DISTURB));
     }
 
+    @Test
+    public void testSecondaryUserCallBindToCurrentUser() throws Exception {
+        setupMocks(true /* isExternalCall */);
+        setupMockPackageManager(true /* default */, true /* system */, false /* external calls */);
+        // Force the difference between the phone account user and current user. This is supposed to
+        // simulate a secondary user placing a call over an unassociated sim.
+        assertFalse(mUserHandle.equals(UserHandle.USER_CURRENT));
+        when(mMockUserInfo.isManagedProfile()).thenReturn(false);
+
+        mInCallController.bindToServices(mMockCall);
+
+        // Bind InCallService on UserHandle.CURRENT and not the user from the call (mUserHandle)
+        ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mMockContext, times(1)).bindServiceAsUser(
+                bindIntentCaptor.capture(),
+                any(ServiceConnection.class),
+                eq(serviceBindingFlags),
+                eq(UserHandle.CURRENT));
+    }
+
+    @Test
+    public void testGetUserFromCall_TargetPhoneAccountNotSet() throws Exception {
+        setupMocks(false /* isExternalCall */);
+        setupMockPackageManager(true /* default */, true /* system */, false /* external calls */);
+        UserHandle testUser = new UserHandle(10);
+
+        when(mMockCall.getTargetPhoneAccount()).thenReturn(null);
+        when(mMockCall.getAssociatedUser()).thenReturn(testUser);
+
+        // Bind to ICS. The mapping should've been inserted with the testUser as the key.
+        mInCallController.bindToServices(mMockCall);
+        assertTrue(mInCallController.getInCallServiceConnections().containsKey(testUser));
+
+        // Set the target phone account. Simulates the flow when the user has chosen which sim to
+        // place the call on.
+        when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
+
+        // Remove the call. This invokes getUserFromCall to remove the ICS mapping.
+        when(mMockCallsManager.getCalls()).thenReturn(Collections.emptyList());
+        mInCallController.onCallRemoved(mMockCall);
+        waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
+
+        // Verify that the mapping was properly removed.
+        assertNull(mInCallController.getInCallServiceConnections().get(testUser));
+    }
+
+    @Test
+    public void testGetUserFromCall_IncomingCall() throws Exception {
+        setupMocks(false /* isExternalCall */);
+        setupMockPackageManager(true /* default */, true /* system */, false /* external calls */);
+        // Explicitly test on a different user to avoid interference with current user.
+        UserHandle testUser = new UserHandle(10);
+
+        // Set user handle in target phone account to test user
+        when(mMockCall.getAssociatedUser()).thenReturn(testUser);
+        when(mMockCall.isIncoming()).thenReturn(true);
+
+        // Bind to ICS. The mapping should've been inserted with the testUser as the key.
+        mInCallController.bindToServices(mMockCall);
+        assertTrue(mInCallController.getInCallServiceConnections().containsKey(testUser));
+
+        // Remove the call. This invokes getUserFromCall to remove the ICS mapping.
+        when(mMockCallsManager.getCalls()).thenReturn(Collections.emptyList());
+        mInCallController.onCallRemoved(mMockCall);
+        waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
+
+        // Verify that the mapping was properly removed.
+        assertNull(mInCallController.getInCallServiceConnections().get(testUser));
+    }
+
+    @Test
+    public void testRemoveAllServiceConnections_MultiUser() throws Exception {
+        when(mFeatureFlags.workProfileAssociatedUser()).thenReturn(true);
+        setupMocks(false /* isExternalCall */);
+        setupMockPackageManager(true /* default */, true /* system */, false /* external calls */);
+        UserHandle workUser = new UserHandle(12);
+        UserManager um = mContext.getSystemService(UserManager.class);
+        when(um.getUserInfo(anyInt())).thenReturn(mMockUserInfo);
+        when(mMockUserInfo.isManagedProfile()).thenReturn(false);
+        when(mMockCall.getAssociatedUser()).thenReturn(workUser);
+        setupFakeSystemCall(mMockSystemCall1, 1);
+        setupFakeSystemCall(mMockSystemCall2, 2);
+
+        // Add "work" call to service. The mapping should've been inserted
+        // with the workUser as the key.
+        mInCallController.onCallAdded(mMockCall);
+        // Add system call to service. The mapping should've been
+        // inserted with the system user as the key.
+        mInCallController.onCallAdded(mMockSystemCall1);
+
+        ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
+        // Make sure we bound to the system call as well as the work call.
+        verify(mMockContext, times(2)).bindServiceAsUser(
+                bindIntentCaptor.capture(),
+                any(ServiceConnection.class),
+                eq(serviceBindingFlags),
+                eq(UserHandle.CURRENT));
+        assertTrue(mInCallController.getInCallServiceConnections().containsKey(workUser));
+        assertTrue(mInCallController.getInCallServiceConnections().containsKey(UserHandle.SYSTEM));
+
+        // Remove the work call. This leverages getUserFromCall to remove the ICS mapping.
+        when(mMockCallsManager.getCalls()).thenReturn(Collections.singletonList(mMockSystemCall1));
+        mInCallController.onCallRemoved(mMockCall);
+        waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
+        // Verify that the mapping was properly removed.
+        assertNull(mInCallController.getInCallServiceConnections().get(workUser));
+        // Verify mapping for system user is still present.
+        assertNotNull(mInCallController.getInCallServiceConnections().get(UserHandle.SYSTEM));
+
+        // Add another system call
+        mInCallController.onCallAdded(mMockSystemCall2);
+        when(mMockCallsManager.getCalls()).thenReturn(Collections.singletonList(mMockSystemCall2));
+        // Remove first system call and verify that mapping is present
+        mInCallController.onCallRemoved(mMockSystemCall1);
+        waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
+        // Verify mapping for system user is still present.
+        assertNotNull(mInCallController.getInCallServiceConnections().get(UserHandle.SYSTEM));
+        // Remove last system call and verify that connection isn't present in ICS mapping.
+        when(mMockCallsManager.getCalls()).thenReturn(Collections.emptyList());
+        mInCallController.onCallRemoved(mMockSystemCall2);
+        waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
+        assertNull(mInCallController.getInCallServiceConnections().get(UserHandle.SYSTEM));
+    }
+
+    private void setupFakeSystemCall(@Mock Call call, int id) {
+        when(call.getAssociatedUser()).thenReturn(UserHandle.SYSTEM);
+        when(call.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
+        when(call.getAnalytics()).thenReturn(new Analytics.CallInfo());
+        when(call.getId()).thenReturn("TC@" + id);
+    }
+
+    private void setupMocksForWorkProfileTest() {
+        when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+        when(mMockCallsManager.isInEmergencyCall()).thenReturn(false);
+        when(mMockChildUserCall.isIncoming()).thenReturn(false);
+        when(mMockChildUserCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
+        when(mDefaultDialerCache.getDefaultDialerApplication(CURRENT_USER_ID)).thenReturn(DEF_PKG);
+        when(mMockContext.bindServiceAsUser(any(Intent.class), any(ServiceConnection.class),
+                anyInt(), any())).thenReturn(true);
+        when(mMockChildUserCall.isExternalCall()).thenReturn(false);
+        when(mMockChildUserCall.isSelfManaged()).thenReturn(true);
+        when(mMockChildUserCall.visibleToInCallService()).thenReturn(true);
+
+        //Setup up parent and child/work profile relation
+        when(mMockUserInfo.getUserHandle()).thenReturn(mParentUserHandle);
+        when(mMockChildUserInfo.getUserHandle()).thenReturn(mChildUserHandle);
+        when(mMockUserInfo.isManagedProfile()).thenReturn(false);
+        when(mMockChildUserInfo.isManagedProfile()).thenReturn(true);
+        when(mMockChildUserCall.getAssociatedUser()).thenReturn(mChildUserHandle);
+        when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mChildUserHandle);
+        when(mMockUserManager.getProfileParent(mChildUserHandle.getIdentifier())).thenReturn(
+                mMockUserInfo);
+        when(mMockUserManager.getProfileParent(mChildUserHandle)).thenReturn(mParentUserHandle);
+        when(mMockUserManager.getUserInfo(eq(mParentUserHandle.getIdentifier()))).thenReturn(
+                mMockUserInfo);
+        when(mMockUserManager.getUserInfo(eq(mChildUserHandle.getIdentifier()))).thenReturn(
+                mMockChildUserInfo);
+        when(mMockUserManager.isManagedProfile(mChildUserHandle.getIdentifier())).thenReturn(true);
+        when(mMockUserManager.isManagedProfile(mParentUserHandle.getIdentifier())).thenReturn(
+                false);
+    }
+
+    @Test
+    public void testManagedProfileCallQueriesIcsUsingParentUserToo() throws Exception {
+        setupMocksForWorkProfileTest();
+        setupMockPackageManager(true /* default */, true /* system */, false /* external calls */);
+        setupMockPackageManager(true /* default */,
+                true /*useNonUiInCalls*/, true /*useAppOpNonUiInCalls*/,
+                true /*useSystemDialer*/, false /*includeExternalCalls*/,
+                true /*includeSelfManagedCallsInDefaultDialer*/,
+                true /*includeSelfManagedCallsInCarModeDialer*/,
+                true /*includeSelfManagedCallsInNonUi*/);
+
+        //pass in call by child/work-profileuser
+        mInCallController.bindToServices(mMockChildUserCall);
+
+        // Verify that queryIntentServicesAsUser is also called with parent handle
+        // Query for the different InCallServices
+        ArgumentCaptor<Integer> userIdCaptor = ArgumentCaptor.forClass(Integer.class);
+        ArgumentCaptor<Intent> queryIntentCaptor = ArgumentCaptor.forClass(Intent.class);
+        ArgumentCaptor<Integer> flagCaptor = ArgumentCaptor.forClass(Integer.class);
+        verify(mMockPackageManager, times(6)).queryIntentServicesAsUser(
+                queryIntentCaptor.capture(), flagCaptor.capture(), userIdCaptor.capture());
+        List<Integer> userIds = userIdCaptor.getAllValues();
+
+        //check if queryIntentServices was called with child user handle
+        assertTrue("no query parent user handle",
+                userIds.contains(mChildUserHandle.getIdentifier()));
+        //check if queryIntentServices was also called with parent user handle
+        assertTrue("no query parent user handle",
+                userIds.contains(mParentUserHandle.getIdentifier()));
+    }
+
     private void setupMocks(boolean isExternalCall) {
         setupMocks(isExternalCall, false /* isSelfManagedCall */);
     }
@@ -1464,6 +1974,7 @@
         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
         when(mMockCallsManager.isInEmergencyCall()).thenReturn(false);
         when(mMockCall.isIncoming()).thenReturn(false);
+        when(mMockCall.getAssociatedUser()).thenReturn(mUserHandle);
         when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
         when(mDefaultDialerCache.getDefaultDialerApplication(CURRENT_USER_ID)).thenReturn(DEF_PKG);
         when(mMockContext.bindServiceAsUser(any(Intent.class), any(ServiceConnection.class),
@@ -1549,14 +2060,14 @@
         }};
     }
 
-    private ResolveInfo getNonUiResolveinfo(boolean supportsSelfManaged) {
+    private ResolveInfo getNonUiResolveinfo(boolean supportsSelfManaged, boolean isEnabled) {
         return new ResolveInfo() {{
             serviceInfo = new ServiceInfo();
             serviceInfo.packageName = NONUI_PKG;
             serviceInfo.name = NONUI_CLASS;
             serviceInfo.applicationInfo = new ApplicationInfo();
             serviceInfo.applicationInfo.uid = NONUI_UID;
-            serviceInfo.enabled = true;
+            serviceInfo.enabled = isEnabled;
             serviceInfo.permission = Manifest.permission.BIND_INCALL_SERVICE;
             serviceInfo.metaData = new Bundle();
             if (supportsSelfManaged) {
@@ -1647,7 +2158,7 @@
                 } else {
                     // InCallController uses a blank package name when querying for non-ui incalls
                     if (useNonUiInCalls) {
-                        resolveInfo.add(getNonUiResolveinfo(includeSelfManagedCallsInNonUi));
+                        resolveInfo.add(getNonUiResolveinfo(includeSelfManagedCallsInNonUi, true));
                     }
                     // InCallController uses a blank package name when querying for App Op non-ui incalls
                     if (useAppOpNonUiInCalls) {
diff --git a/tests/src/com/android/server/telecom/tests/InCallServiceFixture.java b/tests/src/com/android/server/telecom/tests/InCallServiceFixture.java
index 88b5bb5..39381e6 100644
--- a/tests/src/com/android/server/telecom/tests/InCallServiceFixture.java
+++ b/tests/src/com/android/server/telecom/tests/InCallServiceFixture.java
@@ -16,6 +16,7 @@
 
 package com.android.server.telecom.tests;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telecom.IInCallAdapter;
 import com.android.internal.telecom.IInCallService;
 
@@ -39,7 +40,7 @@
  * Controls a test {@link IInCallService} as would be provided by an InCall UI on a system.
  */
 public class InCallServiceFixture implements TestFixture<IInCallService> {
-
+    public static boolean sIgnoreOverrideAdapterFlag = false;
     public String mLatestCallId;
     public IInCallAdapter mInCallAdapter;
     public CallAudioState mCallAudioState;
@@ -53,10 +54,17 @@
     public CountDownLatch mUpdateCallLock = new CountDownLatch(1);
     public CountDownLatch mAddCallLock = new CountDownLatch(1);
 
+    @VisibleForTesting
+    public static void setIgnoreOverrideAdapterFlag(boolean flag) {
+        sIgnoreOverrideAdapterFlag = flag;
+    }
+
     public class FakeInCallService extends IInCallService.Stub {
         @Override
         public void setInCallAdapter(IInCallAdapter inCallAdapter) throws RemoteException {
-            if (mInCallAdapter != null && inCallAdapter != null) {
+            // sIgnoreOverrideAdapterFlag is being used to verify a scenario where the InCallAdapter
+            // gets set twice (secondary user places MO/MT call).
+            if (mInCallAdapter != null && inCallAdapter != null && !sIgnoreOverrideAdapterFlag) {
                 throw new RuntimeException("Adapter is already set");
             }
             if (mInCallAdapter == null && inCallAdapter == null) {
diff --git a/tests/src/com/android/server/telecom/tests/InCallTonePlayerTest.java b/tests/src/com/android/server/telecom/tests/InCallTonePlayerTest.java
index f11afc1..004aa8e 100644
--- a/tests/src/com/android/server/telecom/tests/InCallTonePlayerTest.java
+++ b/tests/src/com/android/server/telecom/tests/InCallTonePlayerTest.java
@@ -23,7 +23,6 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -35,6 +34,8 @@
 import android.media.ToneGenerator;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import com.android.server.telecom.AsyncRingtonePlayer;
+import com.android.server.telecom.Call;
 import com.android.server.telecom.CallAudioManager;
 import com.android.server.telecom.CallAudioRoutePeripheralAdapter;
 import com.android.server.telecom.CallAudioRouteStateMachine;
@@ -69,6 +70,7 @@
     @Mock private InCallTonePlayer.ToneGeneratorFactory mToneGeneratorFactory;
     @Mock private WiredHeadsetManager mWiredHeadsetManager;
     @Mock private DockManager mDockManager;
+    @Mock private AsyncRingtonePlayer mRingtonePlayer;
     @Mock private BluetoothDevice mDevice;
     @Mock private BluetoothAdapter mBluetoothAdapter;
 
@@ -110,6 +112,8 @@
 
     @Mock
     private CallAudioManager mCallAudioManager;
+    @Mock
+    private Call mCall;
 
     private InCallTonePlayer mInCallTonePlayer;
 
@@ -120,15 +124,15 @@
 
         when(mToneGeneratorFactory.get(anyInt(), anyInt())).thenReturn(mToneGenerator);
         when(mMediaPlayerFactory.get(anyInt(), any())).thenReturn(mMediaPlayerAdapter);
-        doNothing().when(mCallAudioManager).setIsTonePlaying(anyBoolean());
+        doNothing().when(mCallAudioManager).setIsTonePlaying(any(Call.class), anyBoolean());
 
         mCallAudioRoutePeripheralAdapter = new CallAudioRoutePeripheralAdapter(
                 mCallAudioRouteStateMachine, mBluetoothRouteManager, mWiredHeadsetManager,
-                mDockManager);
+                mDockManager, mRingtonePlayer);
         mFactory = new InCallTonePlayer.Factory(mCallAudioRoutePeripheralAdapter, mLock,
                 mToneGeneratorFactory, mMediaPlayerFactory, mAudioManagerAdapter);
         mFactory.setCallAudioManager(mCallAudioManager);
-        mInCallTonePlayer = mFactory.createPlayer(InCallTonePlayer.TONE_CALL_ENDED);
+        mInCallTonePlayer = mFactory.createPlayer(mCall, InCallTonePlayer.TONE_CALL_ENDED);
     }
 
     @Override
@@ -145,11 +149,12 @@
         assertTrue(mInCallTonePlayer.startTone());
         // Verify we did play a tone.
         verify(mMediaPlayerFactory, timeout(TEST_TIMEOUT)).get(anyInt(), any());
-        verify(mCallAudioManager).setIsTonePlaying(eq(true));
+        verify(mCallAudioManager).setIsTonePlaying(any(Call.class), eq(true));
 
         mInCallTonePlayer.stopTone();
         // Timeouts due to threads!
-        verify(mCallAudioManager, timeout(TEST_TIMEOUT)).setIsTonePlaying(eq(false));
+        verify(mCallAudioManager, timeout(TEST_TIMEOUT)).setIsTonePlaying(any(Call.class),
+                eq(false));
     }
 
     @SmallTest
@@ -159,11 +164,12 @@
         assertTrue(mInCallTonePlayer.startTone());
         // Verify we did play a tone.
         verify(mMediaPlayerFactory, timeout(TEST_TIMEOUT)).get(anyInt(), any());
-        verify(mCallAudioManager).setIsTonePlaying(eq(true));
+        verify(mCallAudioManager).setIsTonePlaying(any(Call.class), eq(true));
 
         mInCallTonePlayer.stopTone();
         // Timeouts due to threads!
-        verify(mCallAudioManager, timeout(TEST_TIMEOUT)).setIsTonePlaying(eq(false));
+        verify(mCallAudioManager, timeout(TEST_TIMEOUT)).setIsTonePlaying(any(Call.class),
+                eq(false));
 
         // Correctness check: ensure we can't start the tone again.
         assertFalse(mInCallTonePlayer.startTone());
@@ -172,15 +178,16 @@
     @SmallTest
     @Test
     public void testInterruptToneGenerator() {
-        mInCallTonePlayer = mFactory.createPlayer(InCallTonePlayer.TONE_RING_BACK);
+        mInCallTonePlayer = mFactory.createPlayer(mCall, InCallTonePlayer.TONE_RING_BACK);
         when(mAudioManagerAdapter.isVolumeOverZero()).thenReturn(true);
         assertTrue(mInCallTonePlayer.startTone());
         verify(mToneGenerator, timeout(TEST_TIMEOUT)).startTone(anyInt());
-        verify(mCallAudioManager).setIsTonePlaying(eq(true));
+        verify(mCallAudioManager).setIsTonePlaying(any(Call.class), eq(true));
 
         mInCallTonePlayer.stopTone();
         // Timeouts due to threads!
-        verify(mCallAudioManager, timeout(TEST_TIMEOUT)).setIsTonePlaying(eq(false));
+        verify(mCallAudioManager, timeout(TEST_TIMEOUT)).setIsTonePlaying(any(Call.class),
+                eq(false));
         // Ideally it would be nice to verify this, however release is a native method so appears to
         // cause flakiness when testing on Cuttlefish.
         // verify(mToneGenerator, timeout(TEST_TIMEOUT)).release();
@@ -197,7 +204,8 @@
 
         // Verify we did play a tone.
         verify(mMediaPlayerFactory, timeout(TEST_TIMEOUT)).get(anyInt(), any());
-        verify(mCallAudioManager, timeout(TEST_TIMEOUT)).setIsTonePlaying(eq(true));
+        verify(mCallAudioManager, timeout(TEST_TIMEOUT)).setIsTonePlaying(any(Call.class),
+                eq(true));
     }
 
     @SmallTest
@@ -211,11 +219,11 @@
         when(mBluetoothRouteManager.isCachedLeAudioDevice(mDevice)).thenReturn(false);
         when(mBluetoothRouteManager.isCachedHearingAidDevice(mDevice)).thenReturn(false);
 
-        mInCallTonePlayer = mFactory.createPlayer(InCallTonePlayer.TONE_RING_BACK);
+        mInCallTonePlayer = mFactory.createPlayer(mCall, InCallTonePlayer.TONE_RING_BACK);
         assertTrue(mInCallTonePlayer.startTone());
         verify(mToneGeneratorFactory, timeout(TEST_TIMEOUT))
                 .get(eq(AudioManager.STREAM_BLUETOOTH_SCO), anyInt());
-        verify(mCallAudioManager).setIsTonePlaying(eq(true));
+        verify(mCallAudioManager).setIsTonePlaying(any(Call.class), eq(true));
     }
 
     @SmallTest
@@ -229,11 +237,11 @@
         when(mBluetoothRouteManager.isCachedLeAudioDevice(mDevice)).thenReturn(false);
         when(mBluetoothRouteManager.isCachedHearingAidDevice(mDevice)).thenReturn(false);
 
-        mInCallTonePlayer = mFactory.createPlayer(InCallTonePlayer.TONE_CALL_WAITING);
+        mInCallTonePlayer = mFactory.createPlayer(mCall, InCallTonePlayer.TONE_CALL_WAITING);
         assertTrue(mInCallTonePlayer.startTone());
         verify(mToneGeneratorFactory, timeout(TEST_TIMEOUT))
                 .get(eq(AudioManager.STREAM_BLUETOOTH_SCO), anyInt());
-        verify(mCallAudioManager).setIsTonePlaying(eq(true));
+        verify(mCallAudioManager).setIsTonePlaying(any(Call.class), eq(true));
     }
 
     @SmallTest
@@ -247,11 +255,11 @@
         when(mBluetoothRouteManager.isCachedLeAudioDevice(mDevice)).thenReturn(false);
         when(mBluetoothRouteManager.isCachedHearingAidDevice(mDevice)).thenReturn(true);
 
-        mInCallTonePlayer = mFactory.createPlayer(InCallTonePlayer.TONE_RING_BACK);
+        mInCallTonePlayer = mFactory.createPlayer(mCall, InCallTonePlayer.TONE_RING_BACK);
         assertTrue(mInCallTonePlayer.startTone());
         verify(mToneGeneratorFactory, timeout(TEST_TIMEOUT))
                 .get(eq(AudioManager.STREAM_VOICE_CALL), anyInt());
-        verify(mCallAudioManager).setIsTonePlaying(eq(true));
+        verify(mCallAudioManager).setIsTonePlaying(any(Call.class), eq(true));
     }
 
     @SmallTest
@@ -265,10 +273,10 @@
         when(mBluetoothRouteManager.isCachedLeAudioDevice(mDevice)).thenReturn(false);
         when(mBluetoothRouteManager.isCachedHearingAidDevice(mDevice)).thenReturn(true);
 
-        mInCallTonePlayer = mFactory.createPlayer(InCallTonePlayer.TONE_CALL_WAITING);
+        mInCallTonePlayer = mFactory.createPlayer(mCall, InCallTonePlayer.TONE_CALL_WAITING);
         assertTrue(mInCallTonePlayer.startTone());
         verify(mToneGeneratorFactory, timeout(TEST_TIMEOUT))
                 .get(eq(AudioManager.STREAM_VOICE_CALL), anyInt());
-        verify(mCallAudioManager).setIsTonePlaying(eq(true));
+        verify(mCallAudioManager).setIsTonePlaying(any(Call.class), eq(true));
     }
 }
diff --git a/tests/src/com/android/server/telecom/tests/IncomingCallNotifierTest.java b/tests/src/com/android/server/telecom/tests/IncomingCallNotifierTest.java
index a38de94..914fdc5 100644
--- a/tests/src/com/android/server/telecom/tests/IncomingCallNotifierTest.java
+++ b/tests/src/com/android/server/telecom/tests/IncomingCallNotifierTest.java
@@ -75,7 +75,7 @@
 
         when(mAudioCall.getVideoState()).thenReturn(VideoProfile.STATE_AUDIO_ONLY);
         when(mAudioCall.getTargetPhoneAccountLabel()).thenReturn("Bar");
-        when(mAudioCall.getUserHandleFromTargetPhoneAccount()).
+        when(mAudioCall.getAssociatedUser()).
                 thenReturn(UserHandle.CURRENT);
         when(mVideoCall.getVideoState()).thenReturn(VideoProfile.STATE_BIDIRECTIONAL);
         when(mVideoCall.getTargetPhoneAccountLabel()).thenReturn("Bar");
@@ -84,7 +84,7 @@
         when(mRingingCall.getState()).thenReturn(CallState.RINGING);
         when(mRingingCall.getVideoState()).thenReturn(VideoProfile.STATE_AUDIO_ONLY);
         when(mRingingCall.getTargetPhoneAccountLabel()).thenReturn("Foo");
-        when(mRingingCall.getUserHandleFromTargetPhoneAccount()).
+        when(mRingingCall.getAssociatedUser()).
                 thenReturn(UserHandle.CURRENT);
         when(mRingingCall.getHandoverState()).thenReturn(HandoverState.HANDOVER_NONE);
     }
diff --git a/tests/src/com/android/server/telecom/tests/MissedCallNotifierImplTest.java b/tests/src/com/android/server/telecom/tests/MissedCallNotifierImplTest.java
index 2b05430..ac2f1f1 100644
--- a/tests/src/com/android/server/telecom/tests/MissedCallNotifierImplTest.java
+++ b/tests/src/com/android/server/telecom/tests/MissedCallNotifierImplTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.telecom.tests;
 
+import static com.android.server.telecom.ui.MissedCallNotifierImpl.CALL_LOG_COLUMN_ID;
+
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertTrue;
 
@@ -46,10 +48,14 @@
 import android.test.suitebuilder.annotation.SmallTest;
 
 import android.telecom.CallerInfo;
+
+import com.android.server.telecom.CallLogManager;
 import com.android.server.telecom.CallerInfoLookupHelper;
+import com.android.server.telecom.CallsManager;
 import com.android.server.telecom.Constants;
 import com.android.server.telecom.DefaultDialerCache;
 import com.android.server.telecom.DeviceIdleControllerAdapter;
+import com.android.server.telecom.flags.FeatureFlags;
 import com.android.server.telecom.MissedCallNotifier;
 import com.android.server.telecom.PhoneAccountRegistrar;
 import com.android.server.telecom.TelecomBroadcastIntentProcessor;
@@ -241,7 +247,7 @@
         MissedCallNotifier.CallInfo fakeCall = makeFakeCallInfo(TEL_CALL_HANDLE, CALLER_NAME,
                 CALL_TIMESTAMP, phoneAccount.getAccountHandle());
 
-        missedCallNotifier.showMissedCallNotification(fakeCall);
+        missedCallNotifier.showMissedCallNotification(fakeCall, /* uri= */ null);
         ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class);
         verify(mContext).sendBroadcastAsUser(intentArgumentCaptor.capture(), any(),
                 anyString(), any());
@@ -250,6 +256,31 @@
         assertEquals(1, sentIntent.getIntExtra(TelecomManager.EXTRA_NOTIFICATION_COUNT, -1));
     }
 
+    @SmallTest
+    @Test
+    public void testCallLogUriSentToNotifier(){
+        MissedCallNotifier missedCallNotifier = setupMissedCallNotificationThroughDefaultDialer();
+        PhoneAccount phoneAccount = makePhoneAccount(PRIMARY_USER, NO_CAPABILITY);
+        Cursor mockMissedCallsCursor = new MockMissedCallCursorBuilder()
+                .addEntry(TEL_CALL_HANDLE.getSchemeSpecificPart(),
+                        CallLog.Calls.PRESENTATION_ALLOWED, CALL_TIMESTAMP)
+                .build();
+        MissedCallNotifier.CallInfo fakeCall = makeFakeCallInfo(TEL_CALL_HANDLE, CALLER_NAME,
+                CALL_TIMESTAMP, phoneAccount.getAccountHandle());
+        when(mFeatureFlags.addCallUriForMissedCalls()).thenReturn(true);
+
+        missedCallNotifier.showMissedCallNotification(fakeCall,
+                CallLog.Calls.CONTENT_URI.buildUpon().appendPath(Long.toString(
+                        mockMissedCallsCursor.getInt(CALL_LOG_COLUMN_ID))).build());
+        ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext).sendBroadcastAsUser(intentArgumentCaptor.capture(), any(),
+                anyString(), any());
+
+        Intent sentIntent = intentArgumentCaptor.getValue();
+        Uri actualCallUri = sentIntent.getParcelableExtra(TelecomManager.EXTRA_CALL_LOG_URI);
+        assertTrue(actualCallUri.isPathPrefixMatch(CallLog.Calls.CONTENT_URI));
+    }
+
     private MissedCallNotifier setupMissedCallNotificationThroughDefaultDialer() {
         mComponentContextFixture.addIntentReceiver(
                 TelecomManager.ACTION_SHOW_MISSED_CALLS_NOTIFICATION, COMPONENT_NAME);
@@ -275,9 +306,9 @@
         MissedCallNotifier.CallInfo fakeCall = makeFakeCallInfo(TEL_CALL_HANDLE, CALLER_NAME,
                 CALL_TIMESTAMP, phoneAccount.getAccountHandle());
 
-        missedCallNotifier.showMissedCallNotification(fakeCall);
+        missedCallNotifier.showMissedCallNotification(fakeCall, /* uri= */null);
         missedCallNotifier.clearMissedCalls(userHandle);
-        missedCallNotifier.showMissedCallNotification(fakeCall);
+        missedCallNotifier.showMissedCallNotification(fakeCall, /* uri= */null);
 
         ArgumentCaptor<Integer> requestIdCaptor = ArgumentCaptor.forClass(
                 Integer.class);
@@ -308,10 +339,10 @@
 
         MissedCallNotifier missedCallNotifier = new MissedCallNotifierImpl(mContext,
                 mPhoneAccountRegistrar, mDefaultDialerCache, fakeBuilderFactory,
-                mDeviceIdleControllerAdapter);
+                mDeviceIdleControllerAdapter, mFeatureFlags);
 
-        missedCallNotifier.showMissedCallNotification(fakeCall);
-        missedCallNotifier.showMissedCallNotification(fakeCall);
+        missedCallNotifier.showMissedCallNotification(fakeCall, /* uri= */ null);
+        missedCallNotifier.showMissedCallNotification(fakeCall, /* uri= */ null);
 
         // The following captor is to capture the two notifications that got passed into
         // notifyAsUser. This distinguishes between the builders used for the full notification
@@ -402,7 +433,7 @@
 
         MissedCallNotifier.CallInfo fakeCall = makeFakeCallInfo(TEL_CALL_HANDLE, CALLER_NAME,
                 CALL_TIMESTAMP, phoneAccount.getAccountHandle());
-        missedCallNotifier.showMissedCallNotification(fakeCall);
+        missedCallNotifier.showMissedCallNotification(fakeCall, /* uri= */ null);
 
         ArgumentCaptor<Notification> notificationArgumentCaptor = ArgumentCaptor.forClass(
                 Notification.class);
@@ -464,13 +495,13 @@
 
         MissedCallNotifier missedCallNotifier = new MissedCallNotifierImpl(mContext,
                 mPhoneAccountRegistrar, mDefaultDialerCache, fakeBuilderFactory,
-                mDeviceIdleControllerAdapter);
+                mDeviceIdleControllerAdapter, mFeatureFlags);
         PhoneAccount phoneAccount = makePhoneAccount(PRIMARY_USER, NO_CAPABILITY);
 
         MissedCallNotifier.CallInfo fakeCall =
                 makeFakeCallInfo(SIP_CALL_HANDLE, CALLER_NAME, CALL_TIMESTAMP,
                 phoneAccount.getAccountHandle());
-        missedCallNotifier.showMissedCallNotification(fakeCall);
+        missedCallNotifier.showMissedCallNotification(fakeCall, /* uri= */ null);
 
         // Create two intents that correspond to call-back and respond back with SMS, and assert
         // that in the case of a SIP call, no SMS intent is generated.
@@ -525,7 +556,7 @@
 
         MissedCallNotifier missedCallNotifier = new MissedCallNotifierImpl(mContext,
                 mPhoneAccountRegistrar, mDefaultDialerCache, fakeBuilderFactory,
-                mDeviceIdleControllerAdapter);
+                mDeviceIdleControllerAdapter, mFeatureFlags);
 
         // AsyncQueryHandler used in reloadFromDatabase interacts poorly with the below
         // timeout-verify, so run this in a new handler to mitigate that.
@@ -595,7 +626,7 @@
 
         MissedCallNotifier missedCallNotifier = new MissedCallNotifierImpl(mContext,
                 mPhoneAccountRegistrar, mDefaultDialerCache, fakeBuilderFactory,
-                mDeviceIdleControllerAdapter);
+                mDeviceIdleControllerAdapter, mFeatureFlags);
 
         // AsyncQueryHandler used in reloadFromDatabase interacts poorly with the below
         // timeout-verify, so run this in a new handler to mitigate that.
@@ -637,13 +668,13 @@
 
         MissedCallNotifier missedCallNotifier = new MissedCallNotifierImpl(mContext,
                 mPhoneAccountRegistrar, mDefaultDialerCache, fakeBuilderFactory,
-                mDeviceIdleControllerAdapter);
+                mDeviceIdleControllerAdapter, mFeatureFlags);
         PhoneAccount phoneAccount = makePhoneAccount(PRIMARY_USER, NO_CAPABILITY);
 
         MissedCallNotifier.CallInfo fakeCall =
                 makeFakeCallInfo(SIP_CALL_HANDLE, CALLER_NAME, CALL_TIMESTAMP,
                         phoneAccount.getAccountHandle());
-        missedCallNotifier.showMissedCallNotification(fakeCall);
+        missedCallNotifier.showMissedCallNotification(fakeCall, /* uri= */ null);
 
         ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
         ArgumentCaptor<Bundle> bundleCaptor =
@@ -701,7 +732,7 @@
             NotificationBuilderFactory fakeBuilderFactory, UserHandle currentUser) {
         MissedCallNotifier missedCallNotifier = new MissedCallNotifierImpl(mContext,
                 mPhoneAccountRegistrar, mDefaultDialerCache, fakeBuilderFactory,
-                mDeviceIdleControllerAdapter);
+                mDeviceIdleControllerAdapter, mFeatureFlags);
         missedCallNotifier.setCurrentUserHandle(currentUser);
         return missedCallNotifier;
     }
diff --git a/tests/src/com/android/server/telecom/tests/MissedInformationTest.java b/tests/src/com/android/server/telecom/tests/MissedInformationTest.java
index f28a781..5bba742 100644
--- a/tests/src/com/android/server/telecom/tests/MissedInformationTest.java
+++ b/tests/src/com/android/server/telecom/tests/MissedInformationTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.telecom.tests;
 
+import static android.Manifest.permission.MODIFY_PHONE_STATE;
 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;
@@ -108,6 +109,7 @@
         mAdapter = new CallIntentProcessor.AdapterImpl(mCallsManager.getDefaultDialerCache());
         mNotificationManager = spy((NotificationManager) mContext.getSystemService(
                 Context.NOTIFICATION_SERVICE));
+        when(mFeatureFlags.telecomResolveHiddenDependencies()).thenReturn(true);
         when(mContentResolver.getPackageName()).thenReturn(PACKAGE_NAME);
         when(mContentResolver.acquireProvider(any(String.class))).thenReturn(mContentProvider);
         when(mContentProvider.call(any(String.class), any(String.class),
@@ -117,6 +119,8 @@
         mPackageManager = mContext.getPackageManager();
         when(mPackageManager.getPackageUid(anyString(), eq(0))).thenReturn(Binder.getCallingUid());
         mCountDownLatch  = new CountDownLatch(1);
+        doReturn(PackageManager.PERMISSION_GRANTED)
+                .when(mContext).checkCallingPermission(MODIFY_PHONE_STATE);
     }
 
     @Override
@@ -147,8 +151,10 @@
     public void testEmergencyCallPlacing() throws Exception {
         Analytics.dumpToParcelableAnalytics();
         setUpEmergencyCall();
-        when(mEmergencyCall.getUserHandleFromTargetPhoneAccount()).
+        when(mEmergencyCall.getAssociatedUser()).
                 thenReturn(mPhoneAccountA0.getAccountHandle().getUserHandle());
+        when(mEmergencyCall.getTargetPhoneAccount())
+                .thenReturn(mPhoneAccountA0.getAccountHandle());
         mCallsManager.addCall(mEmergencyCall);
         assertTrue(mCallsManager.isInEmergencyCall());
 
@@ -356,6 +362,7 @@
         doReturn(mNotificationManager).when(mSpyContext)
                 .getSystemService(Context.NOTIFICATION_SERVICE);
         doReturn(false).when(mNotificationManager).matchesCallFilter(any(Bundle.class));
+        doReturn(false).when(mIncomingCall).wasDndCheckComputedForCall();
         mCallsManager.getRinger().setNotificationManager(mNotificationManager);
 
         CallFilteringResult result = new CallFilteringResult.Builder()
@@ -413,7 +420,7 @@
                 null, mCallsManager.getPhoneNumberUtilsAdapter(), null,
                 null, null, mPhoneAccountA0.getAccountHandle(),
                 Call.CALL_DIRECTION_INCOMING, false, false,
-                mClockProxy, null));
+                mClockProxy, null, mFeatureFlags));
         doReturn(1L).when(mIncomingCall).getStartRingTime();
         doAnswer((x) -> {
             mCountDownLatch.countDown();
diff --git a/tests/src/com/android/server/telecom/tests/MmiUtilsTest.java b/tests/src/com/android/server/telecom/tests/MmiUtilsTest.java
new file mode 100644
index 0000000..ed74637
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/MmiUtilsTest.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.telecom.tests;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.net.Uri;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.telecom.MmiUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class MmiUtilsTest extends TelecomTestCase {
+
+    private static final String[] sDangerousDialStrings = {
+        "*21*1234567#", // fwd unconditionally to 1234567,
+        "*67*1234567#", // fwd to 1234567 when line is busy
+        "*61*1234567#", // fwd to 1234567 when no one picks up
+        "*62*1234567#", // fwd to 1234567 when out of range
+        "*004*1234567#", // fwd to 1234567 when busy, not pickup up, out of range
+        "*004*1234567#", // fwd to 1234567 conditionally
+        "**21*1234567#", // fwd unconditionally to 1234567
+
+        // north american vertical service codes
+
+        "*094565678", // Selective Call Blocking/Reporting
+        "*4278889", // Change Forward-To Number for Customer Programmable Call Forwarding Don't
+                    // Answer
+        "*5644456", // Change Forward-To Number for ISDN Call Forwarding
+        "*6045677", // Selective Call Rejection Activation
+        "*635678", // Selective Call Forwarding Activation
+        "*64678899", // Selective Call Acceptance Activation
+        "*683456", // Call Forwarding Busy Line/Don't Answer Activation
+        "*721234", // Call Forwarding Activation
+        "*77", // Anonymous Call Rejection Activation
+        "*78", // Do Not Disturb Activation
+    };
+
+    private MmiUtils mMmiUtils = new MmiUtils();
+    private static final String[] sNonDangerousDialStrings = {"*6712345678", "*272", "*272911"};
+
+    @Override
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+    }
+
+    @Override
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    @SmallTest
+    @Test
+    public void testDangerousDialStringsDetected() throws Exception {
+        for (String s : sDangerousDialStrings) {
+            Uri.Builder b = new Uri.Builder();
+            b.scheme("tel").opaquePart(s);
+            assertTrue(mMmiUtils.isDangerousMmiOrVerticalCode(b.build()));
+        }
+    }
+
+    @SmallTest
+    @Test
+    public void testNonDangerousDialStringsNotDetected() throws Exception {
+        for (String s : sNonDangerousDialStrings) {
+            Uri.Builder b = new Uri.Builder();
+            b.scheme("tel").opaquePart(s);
+            assertFalse(mMmiUtils.isDangerousMmiOrVerticalCode(b.build()));
+        }
+    }
+}
diff --git a/tests/src/com/android/server/telecom/tests/NewOutgoingCallIntentBroadcasterTest.java b/tests/src/com/android/server/telecom/tests/NewOutgoingCallIntentBroadcasterTest.java
index 169aeb2..1ffcb76 100644
--- a/tests/src/com/android/server/telecom/tests/NewOutgoingCallIntentBroadcasterTest.java
+++ b/tests/src/com/android/server/telecom/tests/NewOutgoingCallIntentBroadcasterTest.java
@@ -53,9 +53,11 @@
 import android.telephony.TelephonyManager;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import com.android.server.telecom.flags.FeatureFlags;
 import com.android.server.telecom.Call;
 import com.android.server.telecom.CallsManager;
 import com.android.server.telecom.DefaultDialerCache;
+import com.android.server.telecom.MmiUtils;
 import com.android.server.telecom.NewOutgoingCallIntentBroadcaster;
 import com.android.server.telecom.PhoneAccountRegistrar;
 import com.android.server.telecom.PhoneNumberUtilsAdapter;
@@ -74,6 +76,8 @@
 
 @RunWith(JUnit4.class)
 public class NewOutgoingCallIntentBroadcasterTest extends TelecomTestCase {
+    private static final Uri TEST_URI = Uri.parse("tel:16505551212");
+
     private static class ReceiverIntentPair {
         public BroadcastReceiver receiver;
         public Intent intent;
@@ -92,14 +96,16 @@
     @Mock private PhoneAccountRegistrar mPhoneAccountRegistrar;
     @Mock private RoleManagerAdapter mRoleManagerAdapter;
     @Mock private DefaultDialerCache mDefaultDialerCache;
+    @Mock private FeatureFlags mFeatureFlags;
 
+    @Mock private MmiUtils mMmiUtils;
     private PhoneNumberUtilsAdapter mPhoneNumberUtilsAdapter = new PhoneNumberUtilsAdapterImpl();
 
     @Override
     @Before
     public void setUp() throws Exception {
         super.setUp();
-        when(mCall.getInitiatingUser()).thenReturn(UserHandle.CURRENT);
+        when(mCall.getAssociatedUser()).thenReturn(UserHandle.CURRENT);
         when(mCallsManager.getLock()).thenReturn(new TelecomSystem.SyncRoot() { });
         when(mCallsManager.getSystemStateHelper()).thenReturn(mSystemStateHelper);
         when(mCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
@@ -111,6 +117,7 @@
             any(PhoneAccountHandle.class))).thenReturn(mPhoneAccount);
         when(mPhoneAccount.isSelfManaged()).thenReturn(true);
         when(mSystemStateHelper.isCarModeOrProjectionActive()).thenReturn(false);
+        when(mFeatureFlags.isNewOutgoingCallBroadcastUnblocking()).thenReturn(false);
     }
 
     @Override
@@ -261,6 +268,58 @@
         assertEquals(Intent.FLAG_ACTIVITY_NEW_TASK, dialerIntent.getFlags());
     }
 
+    @Test
+    public void testDangerousMmiCodeWithNonDefaultDialer() {
+        Uri handle = Uri.parse("tel:*21*1234567#");
+        doReturn(true).when(mMmiUtils).isDangerousMmiOrVerticalCode(handle);
+        Intent intent = new Intent(Intent.ACTION_CALL, handle);
+
+        String ui_package_string = "sample_string_1";
+        String dialer_default_class_string = "sample_string_2";
+        mComponentContextFixture.putResource(com.android.internal.R.string.config_defaultDialer,
+                ui_package_string);
+        mComponentContextFixture.putResource(R.string.dialer_default_class,
+                dialer_default_class_string);
+        when(mDefaultDialerCache.getSystemDialerApplication()).thenReturn(ui_package_string);
+        when(mDefaultDialerCache.getDialtactsSystemDialerComponent()).thenReturn(
+                new ComponentName(ui_package_string, dialer_default_class_string));
+
+        int result = processIntent(intent, false).disconnectCause;
+
+        assertEquals(DisconnectCause.OUTGOING_CANCELED, result);
+        verifyNoBroadcastSent();
+        verifyNoCallPlaced();
+
+        ArgumentCaptor<Intent> dialerIntentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext).startActivityAsUser(dialerIntentCaptor.capture(), any(UserHandle.class));
+        Intent dialerIntent = dialerIntentCaptor.getValue();
+        assertEquals(new ComponentName(ui_package_string, dialer_default_class_string),
+                dialerIntent.getComponent());
+        assertEquals(Intent.ACTION_DIAL, dialerIntent.getAction());
+        assertEquals(handle, dialerIntent.getData());
+        assertEquals(Intent.FLAG_ACTIVITY_NEW_TASK, dialerIntent.getFlags());
+    }
+
+    @Test
+    public void testNonDangerousMmiCodeWithNonDefaultDialer() {
+        Uri handle = Uri.parse("tel:*12*1234567#");
+        doReturn(false).when(mMmiUtils).isDangerousMmiOrVerticalCode(handle);
+        Intent intent = new Intent(Intent.ACTION_CALL, handle);
+
+        String ui_package_string = "sample_string_1";
+        String dialer_default_class_string = "sample_string_2";
+        mComponentContextFixture.putResource(com.android.internal.R.string.config_defaultDialer,
+                ui_package_string);
+        mComponentContextFixture.putResource(R.string.dialer_default_class,
+                dialer_default_class_string);
+        when(mDefaultDialerCache.getSystemDialerApplication()).thenReturn(ui_package_string);
+        when(mDefaultDialerCache.getDialtactsSystemDialerComponent()).thenReturn(
+                new ComponentName(ui_package_string, dialer_default_class_string));
+
+        int result = processIntent(intent, false).disconnectCause;
+        assertEquals(DisconnectCause.NOT_DISCONNECTED, result);
+    }
+
     @SmallTest
     @Test
     public void testActionCallEmergencyCall() {
@@ -456,6 +515,84 @@
         testUnmodifiedRegularCall();
     }
 
+    /**
+     * Where the flag `isNewOutgoingCallBroadcastUnblocking` is off, verify that we sent an ordered
+     * broadcast and did not try to start the call immediately (legacy behavior).
+     */
+    @SmallTest
+    @Test
+    public void testSendBroadcastBlocking() {
+        when(mFeatureFlags.isNewOutgoingCallBroadcastUnblocking()).thenReturn(false);
+        Intent intent = new Intent(Intent.ACTION_CALL, TEST_URI);
+        NewOutgoingCallIntentBroadcaster nocib = new NewOutgoingCallIntentBroadcaster(
+                mContext, mCallsManager, intent, mPhoneNumberUtilsAdapter,
+                true /* isDefaultPhoneApp */, mDefaultDialerCache, mMmiUtils, mFeatureFlags);
+
+        NewOutgoingCallIntentBroadcaster.CallDisposition disposition = nocib.evaluateCall();
+        nocib.processCall(mCall, disposition);
+
+        // We should not have not short-circuited to place the outgoing call directly.
+        verify(mCall, never()).setNewOutgoingCallIntentBroadcastIsDone();
+        verify(mCallsManager, never()).placeOutgoingCall(any(Call.class), any(Uri.class),
+                any(GatewayInfo.class), anyBoolean(), anyInt());
+
+        // Ensure we did send the broadcast ordered
+        verifyBroadcastSent(TEST_URI.getSchemeSpecificPart(),
+                createNumberExtras(TEST_URI.getSchemeSpecificPart()));
+
+        // Ensure we did not try to directly send the broadcast unordered.
+        verify(mContext, never()).sendBroadcastAsUser(
+                any(Intent.class),
+                eq(UserHandle.CURRENT),
+                eq(android.Manifest.permission.PROCESS_OUTGOING_CALLS));
+    }
+
+    /**
+     * Where the flag `isNewOutgoingCallBroadcastUnblocking` is off, verify that we sent an ordered
+     * broadcast and did not try to start the call immediately.  Also ensure that the broadcast
+     * flags are correct.
+     */
+    @SmallTest
+    @Test
+    public void testSendBroadcastNonBlocking() {
+        when(mFeatureFlags.isNewOutgoingCallBroadcastUnblocking()).thenReturn(true);
+        Intent intent = new Intent(Intent.ACTION_CALL, TEST_URI);
+        NewOutgoingCallIntentBroadcaster nocib = new NewOutgoingCallIntentBroadcaster(
+                mContext, mCallsManager, intent, mPhoneNumberUtilsAdapter,
+                true /* isDefaultPhoneApp */, mDefaultDialerCache, mMmiUtils, mFeatureFlags);
+
+        NewOutgoingCallIntentBroadcaster.CallDisposition disposition = nocib.evaluateCall();
+        nocib.processCall(mCall, disposition);
+
+        // We should have started the outgoing call flow immediately.
+        verify(mCall).setNewOutgoingCallIntentBroadcastIsDone();
+        verify(mCallsManager).placeOutgoingCall(any(Call.class), any(Uri.class),
+                nullable(GatewayInfo.class), anyBoolean(), anyInt());
+
+        // Ensure we didn't send an ordered broadcast.
+        verify(mContext, never()).sendOrderedBroadcastAsUser(
+                any(Intent.class),
+                any(UserHandle.class),
+                anyString(),
+                anyInt(),
+                any(Bundle.class),
+                any(BroadcastReceiver.class),
+                any(Handler.class),
+                eq(Activity.RESULT_OK),
+                anyString(),
+                any(Bundle.class));
+
+        // But that we did send a regular broadcast.
+        ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext).sendBroadcastAsUser(
+                intentArgumentCaptor.capture(),
+                eq(UserHandle.CURRENT),
+                eq(android.Manifest.permission.PROCESS_OUTGOING_CALLS),
+                eq(AppOpsManager.OP_PROCESS_OUTGOING_CALLS));
+        Intent capturedIntent = intentArgumentCaptor.getValue();
+        assertEquals(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND, capturedIntent.getFlags());
+    }
+
     private ReceiverIntentPair regularCallTestHelper(Intent intent,
             Bundle expectedAdditionalExtras) {
         Uri handle = intent.getData();
@@ -488,7 +625,7 @@
             boolean isDefaultPhoneApp) {
         NewOutgoingCallIntentBroadcaster b = new NewOutgoingCallIntentBroadcaster(
                 mContext, mCallsManager, intent, mPhoneNumberUtilsAdapter,
-                isDefaultPhoneApp, mDefaultDialerCache);
+                isDefaultPhoneApp, mDefaultDialerCache, mMmiUtils, mFeatureFlags);
         NewOutgoingCallIntentBroadcaster.CallDisposition cd = b.evaluateCall();
         if (cd.disconnectCause == DisconnectCause.NOT_DISCONNECTED) {
             b.processCall(mCall, cd);
diff --git a/tests/src/com/android/server/telecom/tests/ParcelableCallUtilsTest.java b/tests/src/com/android/server/telecom/tests/ParcelableCallUtilsTest.java
index fed8084..57c6191 100644
--- a/tests/src/com/android/server/telecom/tests/ParcelableCallUtilsTest.java
+++ b/tests/src/com/android/server/telecom/tests/ParcelableCallUtilsTest.java
@@ -13,6 +13,7 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.SystemClock;
+import android.os.UserHandle;
 import android.telecom.Connection;
 import android.telecom.ParcelableCall;
 import android.telecom.PhoneAccountHandle;
@@ -57,6 +58,7 @@
         when(mClockProxy.elapsedRealtime()).thenReturn(SystemClock.elapsedRealtime());
         when(mCallsManager.getCallerInfoLookupHelper()).thenReturn(mCallerInfoLookupHelper);
         when(mCallsManager.getPhoneAccountRegistrar()).thenReturn(mPhoneAccountRegistrar);
+        when(mCallsManager.getCurrentUserHandle()).thenReturn(UserHandle.CURRENT);
         when(mPhoneAccountRegistrar.getPhoneAccountUnchecked(any())).thenReturn(null);
         when(mComponentContextFixture.getTelephonyManager().isEmergencyNumber(any()))
                 .thenReturn(false);
@@ -75,7 +77,8 @@
                 false /* shouldAttachToExistingConnection */,
                 false /* isConference */,
                 mClockProxy /* ClockProxy */,
-                mToastProxy);
+                mToastProxy,
+                mFeatureFlags);
     }
 
     @Override
diff --git a/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java b/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
index cef032f..9fcb87a 100644
--- a/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
+++ b/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
@@ -23,8 +23,6 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyObject;
-import static org.mockito.ArgumentMatchers.isA;
 import static org.mockito.Matchers.anyBoolean;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyString;
@@ -355,6 +353,40 @@
                 PhoneAccount.SCHEME_TEL));
     }
 
+    /**
+     * Verify when a {@link android.telecom.ConnectionService} is disabled or cannot be resolved,
+     * all phone accounts are unregistered when calling
+     * {@link  PhoneAccountRegistrar#getAccountsForPackage_BypassResolveComp(String, UserHandle)}.
+     */
+    @Test
+    public void testCannotResolveServiceUnregistersAccounts() throws Exception {
+        ComponentName componentName = makeQuickConnectionServiceComponentName();
+        PhoneAccount account = makeQuickAccountBuilder("0", 0, USER_HANDLE_10)
+                .setCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER
+                        | PhoneAccount.CAPABILITY_CALL_PROVIDER).build();
+        // add the ConnectionService and register a single phone account for it
+        mComponentContextFixture.addConnectionService(componentName,
+                Mockito.mock(IConnectionService.class));
+        registerAndEnableAccount(account);
+        // verify the start state
+        assertEquals(1,
+                mRegistrar.getAccountsForPackage_BypassResolveComp(componentName.getPackageName(),
+                        USER_HANDLE_10).size());
+        // remove the ConnectionService so that the account cannot be resolved anymore
+        mComponentContextFixture.removeConnectionService(componentName,
+                Mockito.mock(IConnectionService.class));
+        // verify the account is unregistered when fetching the phone accounts for the package
+        assertEquals(1,
+                mRegistrar.getAccountsForPackage_BypassResolveComp(componentName.getPackageName(),
+                        USER_HANDLE_10).size());
+        assertEquals(0,mRegistrar.cleanupUnresolvableConnectionServiceAccounts(
+                mRegistrar.getAccountsForPackage_BypassResolveComp(componentName.getPackageName(),
+                USER_HANDLE_10)).size());
+        assertEquals(0,
+                mRegistrar.getAccountsForPackage_BypassResolveComp(componentName.getPackageName(),
+                        USER_HANDLE_10).size());
+    }
+
     @MediumTest
     @Test
     public void testSimCallManager() throws Exception {
@@ -707,7 +739,7 @@
                 .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)
                 .build();
 
-        assertFalse(mRegistrar.hasTransactionalCallCapabilites(accountWithoutCapability));
+        assertFalse(mRegistrar.hasTransactionalCallCapabilities(accountWithoutCapability));
 
         try {
             mRegistrar.registerPhoneAccount(accountWithoutCapability);
@@ -730,7 +762,7 @@
                         PhoneAccount.CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS)
                 .build();
 
-        assertTrue(mRegistrar.hasTransactionalCallCapabilites(accountWithCapability));
+        assertTrue(mRegistrar.hasTransactionalCallCapabilities(accountWithCapability));
 
         try {
             mRegistrar.registerPhoneAccount(accountWithCapability);
@@ -752,7 +784,7 @@
                         PhoneAccount.CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS)
                 .build();
 
-        assertTrue(mRegistrar.hasTransactionalCallCapabilites(accountWithCapability));
+        assertTrue(mRegistrar.hasTransactionalCallCapabilities(accountWithCapability));
 
         try {
             // WHEN
@@ -1684,6 +1716,24 @@
         }
     }
 
+    /**
+     * PhoneAccounts with CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS do not require a
+     * ConnectionService. Ensure that such an account can be registered and fetched.
+     */
+    @Test
+    public void testFetchingTransactionalAccounts() {
+        PhoneAccount account = makeBuilderWithBindCapabilities(
+                makeQuickAccountHandle(TEST_ID)).build();
+
+        try {
+            assertEquals(0, mRegistrar.getAllPhoneAccounts(null, true).size());
+            registerAndEnableAccount(account);
+            assertEquals(1, mRegistrar.getAllPhoneAccounts(null, true).size());
+        } finally {
+            mRegistrar.unregisterPhoneAccount(account.getAccountHandle());
+        }
+    }
+
     private static PhoneAccount.Builder makeBuilderWithBindCapabilities(PhoneAccountHandle handle) {
         return new PhoneAccount.Builder(handle, TEST_LABEL)
                 .setCapabilities(PhoneAccount.CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS);
diff --git a/tests/src/com/android/server/telecom/tests/RingbackPlayerTest.java b/tests/src/com/android/server/telecom/tests/RingbackPlayerTest.java
new file mode 100644
index 0000000..e851944
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/RingbackPlayerTest.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.telecom.tests;
+
+import static org.junit.Assert.assertFalse;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.when;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallState;
+import com.android.server.telecom.InCallTonePlayer;
+import com.android.server.telecom.RingbackPlayer;
+
+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.concurrent.CountDownLatch;
+
+@RunWith(JUnit4.class)
+public class RingbackPlayerTest extends TelecomTestCase {
+    @Mock InCallTonePlayer.Factory mFactory;
+    @Mock Call mCall;
+    @Mock InCallTonePlayer mTonePlayer;
+
+    private RingbackPlayer mRingbackPlayer;
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        when(mFactory.createPlayer(any(Call.class), anyInt())).thenReturn(mTonePlayer);
+        mRingbackPlayer = new RingbackPlayer(mFactory);
+    }
+
+    @SmallTest
+    @Test
+    public void testPlayerSync() {
+        // make sure InCallTonePlayer try to start playing the tone after RingbackPlayer receives
+        // stop tone request.
+        CountDownLatch latch = new CountDownLatch(1);
+        doReturn(CallState.DIALING).when(mCall).getState();
+        doAnswer(x -> {
+            new Thread(() -> {
+                try {
+                    latch.await();
+                } catch (InterruptedException e) {
+                    // Ignore
+                }
+            }).start();
+            return true;
+        }).when(mTonePlayer).startTone();
+
+        mRingbackPlayer.startRingbackForCall(mCall);
+        mRingbackPlayer.stopRingbackForCall(mCall);
+        assertFalse(mRingbackPlayer.isRingbackPlaying());
+        latch.countDown();
+    }
+}
diff --git a/tests/src/com/android/server/telecom/tests/RingerTest.java b/tests/src/com/android/server/telecom/tests/RingerTest.java
index d397910..771e736 100644
--- a/tests/src/com/android/server/telecom/tests/RingerTest.java
+++ b/tests/src/com/android/server/telecom/tests/RingerTest.java
@@ -16,21 +16,27 @@
 
 package com.android.server.telecom.tests;
 
+import static android.os.VibrationEffect.EFFECT_CLICK;
 import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
-
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.ArgumentMatchers.nullable;
 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.never;
+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.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
 import android.app.NotificationManager;
@@ -42,15 +48,17 @@
 import android.media.VolumeShaper;
 import android.net.Uri;
 import android.os.Bundle;
-import android.os.Parcel;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
+import android.os.VibratorInfo;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
 import android.test.suitebuilder.annotation.SmallTest;
-import android.view.accessibility.AccessibilityManager;
 
 import com.android.server.telecom.AsyncRingtonePlayer;
 import com.android.server.telecom.Call;
@@ -60,97 +68,47 @@
 import com.android.server.telecom.Ringer;
 import com.android.server.telecom.RingtoneFactory;
 import com.android.server.telecom.SystemSettingsUtil;
+import com.android.server.telecom.flags.FeatureFlags;
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 import org.mockito.Mock;
 import org.mockito.Spy;
 
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.isNull;
-import static org.mockito.ArgumentMatchers.nullable;
-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.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import java.util.Objects;
+import java.time.Duration;
 import java.util.concurrent.CompletableFuture;
 
 @RunWith(JUnit4.class)
 public class RingerTest extends TelecomTestCase {
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
     private static final Uri FAKE_RINGTONE_URI = Uri.parse("content://media/fake/audio/1729");
-    private static class UriVibrationEffect extends VibrationEffect {
-        final Uri mUri;
-
-        private UriVibrationEffect(Uri uri) {
-            mUri = uri;
-        }
-
-        @Override
-        public VibrationEffect resolve(int defaultAmplitude) {
-            return this;
-        }
-
-        @Override
-        public VibrationEffect scale(float scaleFactor) {
-            return this;
-        }
-
-        @Override
-        public long[] computeCreateWaveformOffOnTimingsOrNull() {
-            return null; // not needed
-        }
-
-        @Override
-        public boolean areVibrationFeaturesSupported(Vibrator vibrator) {
-            return true; // not needed
-        }
-
-        @Override
-        public void validate() {
-            // not needed
-        }
-
-        @Override
-        public long getDuration() {
-            return 0;
-        }
-
-        @Override
-        public void writeToParcel(Parcel dest, int flags) {
-            // not needed
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) return true;
-            if (o == null || getClass() != o.getClass()) return false;
-            UriVibrationEffect that = (UriVibrationEffect) o;
-            return Objects.equals(mUri, that.mUri);
-        }
-    }
+    // Returned when the a URI-based VibrationEffect is attempted, to avoid depending on actual
+    // device configuration for ringtone URIs. The actual Uri can be verified via the
+    // VibrationEffectProxy mock invocation.
+    private static final VibrationEffect URI_VIBRATION_EFFECT =
+            VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK);
+    private static final VibrationEffect EXPECTED_SIMPLE_VIBRATION_PATTERN =
+            VibrationEffect.createWaveform(
+                    new long[] {0, 1000, 1000}, new int[] {0, 255, 0}, 1);
+    private static final VibrationEffect EXPECTED_PULSE_VIBRATION_PATTERN =
+            VibrationEffect.createWaveform(
+                    Ringer.PULSE_PATTERN, Ringer.PULSE_AMPLITUDE, 5);
 
     @Mock InCallTonePlayer.Factory mockPlayerFactory;
     @Mock SystemSettingsUtil mockSystemSettingsUtil;
-    @Mock AsyncRingtonePlayer mockRingtonePlayer;
     @Mock RingtoneFactory mockRingtoneFactory;
     @Mock Vibrator mockVibrator;
+    @Mock VibratorInfo mockVibratorInfo;
     @Mock InCallController mockInCallController;
     @Mock NotificationManager mockNotificationManager;
     @Mock Ringer.AccessibilityManagerAdapter mockAccessibilityManagerAdapter;
+    @Mock private FeatureFlags mFeatureFlags;
 
     @Spy Ringer.VibrationEffectProxy spyVibrationEffectProxy;
 
@@ -162,6 +120,8 @@
             new PhoneAccountHandle(new ComponentName("pa_pkg", "pa_cls"),
                     "pa_id");
 
+    boolean mIsHapticPlaybackSupported = true;  // Note: initializeRinger() after changes.
+    AsyncRingtonePlayer asyncRingtonePlayer = new AsyncRingtonePlayer();
     Ringer mRingerUnderTest;
     AudioManager mockAudioManager;
     CompletableFuture<Void> mRingCompletionFuture = new CompletableFuture<>();
@@ -170,26 +130,42 @@
     @Before
     public void setUp() throws Exception {
         super.setUp();
-        mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
-        doAnswer(invocation -> {
-            Uri ringtoneUriForEffect = invocation.getArgument(0);
-            return new UriVibrationEffect(ringtoneUriForEffect);
-        }).when(spyVibrationEffectProxy).get(any(), any());
-        when(mockPlayerFactory.createPlayer(anyInt())).thenReturn(mockTonePlayer);
+        mContext = spy(mComponentContextFixture.getTestDouble().getApplicationContext());
+        when(mFeatureFlags.telecomResolveHiddenDependencies()).thenReturn(true);
+        doReturn(URI_VIBRATION_EFFECT).when(spyVibrationEffectProxy).get(any(), any());
+        when(mockPlayerFactory.createPlayer(any(Call.class), anyInt())).thenReturn(mockTonePlayer);
         mockAudioManager = mContext.getSystemService(AudioManager.class);
         when(mockAudioManager.getRingerMode()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
-        when(mockSystemSettingsUtil.isHapticPlaybackSupported(any(Context.class))).thenReturn(true);
+        when(mockVibrator.getInfo()).thenReturn(mockVibratorInfo);
+        when(mockSystemSettingsUtil.isHapticPlaybackSupported(any(Context.class)))
+                .thenAnswer((invocation) -> mIsHapticPlaybackSupported);
         mockNotificationManager =mContext.getSystemService(NotificationManager.class);
         when(mockTonePlayer.startTone()).thenReturn(true);
-        when(mockNotificationManager.matchesCallFilter(any(Bundle.class))).thenReturn(true);
+        when(mockNotificationManager.matchesCallFilter(any(Uri.class))).thenReturn(true);
         when(mockRingtoneFactory.hasHapticChannels(any(Ringtone.class))).thenReturn(false);
-        mRingerUnderTest = new Ringer(mockPlayerFactory, mContext, mockSystemSettingsUtil,
-                mockRingtonePlayer, mockRingtoneFactory, mockVibrator, spyVibrationEffectProxy,
-                mockInCallController, mockNotificationManager, mockAccessibilityManagerAdapter);
         when(mockCall1.getState()).thenReturn(CallState.RINGING);
         when(mockCall2.getState()).thenReturn(CallState.RINGING);
-        when(mockCall1.getUserHandleFromTargetPhoneAccount()).thenReturn(PA_HANDLE.getUserHandle());
-        when(mockCall2.getUserHandleFromTargetPhoneAccount()).thenReturn(PA_HANDLE.getUserHandle());
+        when(mockCall1.getAssociatedUser()).thenReturn(PA_HANDLE.getUserHandle());
+        when(mockCall2.getAssociatedUser()).thenReturn(PA_HANDLE.getUserHandle());
+        when(mockCall1.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
+        when(mockCall2.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
+        // Set BT active state in tests to ensure that we do not end up blocking tests for 1 sec
+        // waiting for BT to connect in unit tests by default.
+        asyncRingtonePlayer.updateBtActiveState(true);
+
+        createRingerUnderTest();
+    }
+
+    /**
+     * (Re-)Creates the Ringer for the test. This needs to be called if changing final properties,
+     * like mIsHapticPlaybackSupported.
+     */
+    private void createRingerUnderTest() {
+        mRingerUnderTest = new Ringer(mockPlayerFactory, mContext, mockSystemSettingsUtil,
+                asyncRingtonePlayer, mockRingtoneFactory, mockVibrator, spyVibrationEffectProxy,
+                mockInCallController, mockNotificationManager, mockAccessibilityManagerAdapter,
+                mFeatureFlags);
+        // This future is used to wait for AsyncRingtonePlayer to finish its part.
         mRingerUnderTest.setBlockOnRingingFuture(mRingCompletionFuture);
     }
 
@@ -201,33 +177,175 @@
 
     @SmallTest
     @Test
-    public void testNoActionInTheaterMode() {
+    public void testSimpleVibrationPrecedesValidSupportedDefaultRingVibrationOverride()
+            throws Exception {
+        when(mFeatureFlags.useDeviceProvidedSerializedRingerVibration()).thenReturn(true);
+        mockVibrationResourceValues(
+                """
+                    <vibration-effect>
+                        <predefined-effect name="click"/>
+                    </vibration-effect>
+                """,
+                /* useSimpleVibration= */ true);
+        when(mockVibratorInfo.areVibrationFeaturesSupported(any())).thenReturn(true);
+
+        createRingerUnderTest();
+
+        assertEquals(EXPECTED_SIMPLE_VIBRATION_PATTERN, mRingerUnderTest.mDefaultVibrationEffect);
+    }
+
+    @SmallTest
+    @Test
+    public void testDefaultRingVibrationOverrideNotUsedWhenFeatureIsDisabled()
+            throws Exception {
+        when(mFeatureFlags.useDeviceProvidedSerializedRingerVibration()).thenReturn(false);
+        mockVibrationResourceValues(
+                """
+                    <vibration-effect>
+                        <waveform-effect>
+                            <waveform-entry durationMs="100" amplitude="0"/>
+                            <repeating>
+                                <waveform-entry durationMs="500" amplitude="default"/>
+                                <waveform-entry durationMs="700" amplitude="0"/>
+                            </repeating>
+                        </waveform-effect>
+                    </vibration-effect>
+                """,
+                /* useSimpleVibration= */ false);
+        when(mockVibratorInfo.areVibrationFeaturesSupported(any())).thenReturn(true);
+
+        createRingerUnderTest();
+
+        assertEquals(EXPECTED_PULSE_VIBRATION_PATTERN, mRingerUnderTest.mDefaultVibrationEffect);
+    }
+
+    @SmallTest
+    @Test
+    public void testValidSupportedRepeatingDefaultRingVibrationOverride() throws Exception {
+        when(mFeatureFlags.useDeviceProvidedSerializedRingerVibration()).thenReturn(true);
+        mockVibrationResourceValues(
+                """
+                    <vibration-effect>
+                        <waveform-effect>
+                            <waveform-entry durationMs="100" amplitude="0"/>
+                            <repeating>
+                                <waveform-entry durationMs="500" amplitude="default"/>
+                                <waveform-entry durationMs="700" amplitude="0"/>
+                            </repeating>
+                        </waveform-effect>
+                    </vibration-effect>
+                """,
+                /* useSimpleVibration= */ false);
+        when(mockVibratorInfo.areVibrationFeaturesSupported(any())).thenReturn(true);
+
+        createRingerUnderTest();
+
+        assertEquals(
+                VibrationEffect.createWaveform(new long[]{100, 500, 700}, /* repeat= */ 1),
+                mRingerUnderTest.mDefaultVibrationEffect);
+    }
+
+    @SmallTest
+    @Test
+    public void testValidSupportedNonRepeatingDefaultRingVibrationOverride() throws Exception {
+        when(mFeatureFlags.useDeviceProvidedSerializedRingerVibration()).thenReturn(true);
+        mockVibrationResourceValues(
+                """
+                    <vibration-effect>
+                        <predefined-effect name="click"/>
+                    </vibration-effect>
+                """,
+                /* useSimpleVibration= */ false);
+        when(mockVibratorInfo.areVibrationFeaturesSupported(any())).thenReturn(true);
+
+        createRingerUnderTest();
+
+        assertEquals(
+                VibrationEffect
+                        .startComposition()
+                        .repeatEffectIndefinitely(
+                                VibrationEffect
+                                        .startComposition()
+                                        .addEffect(VibrationEffect.createPredefined(EFFECT_CLICK))
+                                        .addOffDuration(Duration.ofSeconds(1))
+                                        .compose()
+                        )
+                        .compose(),
+                mRingerUnderTest.mDefaultVibrationEffect);
+    }
+
+    @SmallTest
+    @Test
+    public void testValidButUnsupportedDefaultRingVibrationOverride() throws Exception {
+        when(mFeatureFlags.useDeviceProvidedSerializedRingerVibration()).thenReturn(true);
+        mockVibrationResourceValues(
+                """
+                    <vibration-effect>
+                        <predefined-effect name="click"/>
+                    </vibration-effect>
+                """,
+                /* useSimpleVibration= */ false);
+        when(mockVibratorInfo.areVibrationFeaturesSupported(
+                eq(VibrationEffect.createPredefined(EFFECT_CLICK)))).thenReturn(false);
+
+        createRingerUnderTest();
+
+        assertEquals(EXPECTED_SIMPLE_VIBRATION_PATTERN, mRingerUnderTest.mDefaultVibrationEffect);
+    }
+
+    @SmallTest
+    @Test
+    public void testInvalidDefaultRingVibrationOverride() throws Exception {
+        when(mFeatureFlags.useDeviceProvidedSerializedRingerVibration()).thenReturn(true);
+        mockVibrationResourceValues(
+                /* defaultVibrationContent= */ "bad serialization",
+                /* useSimpleVibration= */ false);
+        when(mockVibratorInfo.areVibrationFeaturesSupported(any())).thenReturn(true);
+
+        createRingerUnderTest();
+
+        assertEquals(EXPECTED_SIMPLE_VIBRATION_PATTERN, mRingerUnderTest.mDefaultVibrationEffect);
+    }
+
+    @SmallTest
+    @Test
+    public void testEmptyDefaultRingVibrationOverride() throws Exception {
+        when(mFeatureFlags.useDeviceProvidedSerializedRingerVibration()).thenReturn(true);
+        mockVibrationResourceValues(
+                /* defaultVibrationContent= */ "", /* useSimpleVibration= */ false);
+        when(mockVibratorInfo.areVibrationFeaturesSupported(any())).thenReturn(true);
+
+        createRingerUnderTest();
+
+        assertEquals(EXPECTED_SIMPLE_VIBRATION_PATTERN, mRingerUnderTest.mDefaultVibrationEffect);
+    }
+
+    @SmallTest
+    @Test
+    public void testNoActionInTheaterMode() throws Exception {
         // Start call waiting to make sure that it doesn't stop when we start ringing
         mRingerUnderTest.startCallWaiting(mockCall1);
         when(mockSystemSettingsUtil.isTheaterModeOn(any(Context.class))).thenReturn(true);
-        assertFalse(mRingerUnderTest.startRinging(mockCall2, false));
-        verify(mockRingtoneFactory, never())
-            .getRingtone(any(Call.class), nullable(VolumeShaper.Configuration.class), anyBoolean());
+        assertFalse(startRingingAndWaitForAsync(mockCall2, false));
+        verifyZeroInteractions(mockRingtoneFactory);
         verify(mockTonePlayer, never()).stopTone();
-        verify(mockRingtonePlayer, never()).play(any(Ringtone.class));
         verify(mockVibrator, never())
                 .vibrate(any(VibrationEffect.class), any(VibrationAttributes.class));
     }
 
     @SmallTest
     @Test
-    public void testNoActionWithExternalRinger() {
+    public void testNoActionWithExternalRinger() throws Exception {
         Bundle externalRingerExtra = new Bundle();
         externalRingerExtra.putBoolean(TelecomManager.EXTRA_CALL_HAS_IN_BAND_RINGTONE, true);
         when(mockCall1.getIntentExtras()).thenReturn(externalRingerExtra);
         when(mockCall2.getIntentExtras()).thenReturn(externalRingerExtra);
         // Start call waiting to make sure that it doesn't stop when we start ringing
         mRingerUnderTest.startCallWaiting(mockCall1);
-        assertFalse(mRingerUnderTest.startRinging(mockCall2, false));
-        verify(mockRingtoneFactory, never())
-            .getRingtone(any(Call.class), nullable(VolumeShaper.Configuration.class), anyBoolean());
+        assertFalse(startRingingAndWaitForAsync(mockCall2, false));
+
+        verifyZeroInteractions(mockRingtoneFactory);
         verify(mockTonePlayer, never()).stopTone();
-        verify(mockRingtonePlayer, never()).play(any(Ringtone.class));
         verify(mockVibrator, never())
                 .vibrate(any(VibrationEffect.class), any(VibrationAttributes.class));
     }
@@ -235,19 +353,15 @@
     @SmallTest
     @Test
     public void testNoActionWhenDialerRings() throws Exception {
-        ensureRingtoneMocked();
-
         // Start call waiting to make sure that it doesn't stop when we start ringing
         mRingerUnderTest.startCallWaiting(mockCall1);
         when(mockInCallController.doesConnectedDialerSupportRinging(
                 any(UserHandle.class))).thenReturn(true);
         ensureRingerIsNotAudible();
-        assertFalse(mRingerUnderTest.startRinging(mockCall2, false));
-        verify(mockRingtoneFactory, never())
-            .getRingtone(any(Call.class), nullable(VolumeShaper.Configuration.class), anyBoolean());
-        mRingCompletionFuture.get();
+        assertFalse(startRingingAndWaitForAsync(mockCall2, false));
+
+        verifyZeroInteractions(mockRingtoneFactory);
         verify(mockTonePlayer, never()).stopTone();
-        verify(mockRingtonePlayer, never()).play(any(Ringtone.class));
         verify(mockVibrator, never())
                 .vibrate(any(VibrationEffect.class), any(AudioAttributes.class));
     }
@@ -255,48 +369,46 @@
     @SmallTest
     @Test
     public void testAudioFocusStillAcquiredWhenDialerRings() throws Exception {
-        ensureRingtoneMocked();
 
         // Start call waiting to make sure that it doesn't stop when we start ringing
         mRingerUnderTest.startCallWaiting(mockCall1);
         when(mockInCallController.doesConnectedDialerSupportRinging(
                 any(UserHandle.class))).thenReturn(true);
         ensureRingerIsAudible();
-        assertTrue(mRingerUnderTest.startRinging(mockCall2, false));
-        verify(mockRingtoneFactory, never())
-            .getRingtone(any(Call.class), nullable(VolumeShaper.Configuration.class), anyBoolean());
-        mRingCompletionFuture.get();
+        assertTrue(startRingingAndWaitForAsync(mockCall2, false));
+        verifyZeroInteractions(mockRingtoneFactory);
         verify(mockTonePlayer, never()).stopTone();
-        verify(mockRingtonePlayer, never()).play(any(Ringtone.class));
         verify(mockVibrator, never())
                 .vibrate(any(VibrationEffect.class), any(VibrationAttributes.class));
     }
 
     @SmallTest
     @Test
-    public void testNoActionWhenCallIsSelfManaged() {
+    public void testNoActionWhenCallIsSelfManaged() throws Exception {
         // Start call waiting to make sure that it doesn't stop when we start ringing
         mRingerUnderTest.startCallWaiting(mockCall1);
         when(mockCall2.isSelfManaged()).thenReturn(true);
         // We do want to acquire audio focus when self-managed
-        assertTrue(mRingerUnderTest.startRinging(mockCall2, true));
+        assertTrue(startRingingAndWaitForAsync(mockCall2, true));
+
+        verifyZeroInteractions(mockRingtoneFactory);
         verify(mockTonePlayer, never()).stopTone();
-        verify(mockRingtonePlayer, never()).play(any(Ringtone.class));
         verify(mockVibrator, never())
                 .vibrate(any(VibrationEffect.class), any(VibrationAttributes.class));
     }
 
     @SmallTest
     @Test
-    public void testCallWaitingButNoRingForSpecificContacts() {
-        when(mockNotificationManager.matchesCallFilter(any(Bundle.class))).thenReturn(false);
+    public void testCallWaitingButNoRingForSpecificContacts() throws Exception {
+        when(mockNotificationManager.matchesCallFilter(any(Uri.class))).thenReturn(false);
         // Start call waiting to make sure that it does stop when we start ringing
         mRingerUnderTest.startCallWaiting(mockCall1);
         verify(mockTonePlayer).startTone();
 
-        assertFalse(mRingerUnderTest.startRinging(mockCall2, false));
+        assertFalse(startRingingAndWaitForAsync(mockCall2, false));
+
+        verifyZeroInteractions(mockRingtoneFactory);
         verify(mockTonePlayer).stopTone();
-        verify(mockRingtonePlayer, never()).play(any(Ringtone.class));
         verify(mockVibrator, never())
                 .vibrate(any(VibrationEffect.class), any(VibrationAttributes.class));
     }
@@ -311,12 +423,12 @@
         enableVibrationWhenRinging();
         // Pretend we're using audio coupled haptics.
         setIsUsingHaptics(mockRingtone, true);
-        assertTrue(mRingerUnderTest.startRinging(mockCall1, false));
-        mRingCompletionFuture.get();
+        assertTrue(startRingingAndWaitForAsync(mockCall1, false));
         verify(mockRingtoneFactory, times(1))
             .getRingtone(any(Call.class), nullable(VolumeShaper.Configuration.class), anyBoolean());
+        verifyNoMoreInteractions(mockRingtoneFactory);
         verify(mockTonePlayer).stopTone();
-        verify(mockRingtonePlayer).play(any(Ringtone.class));
+        verify(mockRingtone).play();
         verify(mockVibrator, never()).vibrate(any(VibrationEffect.class),
                 any(VibrationAttributes.class));
     }
@@ -331,11 +443,16 @@
         mRingerUnderTest.startCallWaiting(mockCall1);
         when(mockAudioManager.getRingerMode()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
         enableVibrationWhenRinging();
-        assertFalse(mRingerUnderTest.startRinging(mockCall2, false));
-        mRingCompletionFuture.get();
+        // The ringtone isn't known to be null until the async portion after the call completes,
+        // so startRinging still returns true here as there should nominally be a ringtone.
+        // Notably, vibration still happens in this scenario.
+        assertTrue(startRingingAndWaitForAsync(mockCall2, false));
         verify(mockTonePlayer).stopTone();
-        // Ringtone does not exist, make sure it does not try to play it
-        verify(mockRingtonePlayer, never()).play(any(Ringtone.class));
+
+        // Just the one call to mockRingtoneFactory, which returned null.
+        verify(mockRingtoneFactory).getRingtone(
+                any(Call.class), nullable(VolumeShaper.Configuration.class), anyBoolean());
+        verifyNoMoreInteractions(mockRingtoneFactory);
 
         // Play default vibration when future completes with no audio coupled haptics
         verify(mockVibrator).vibrate(eq(mRingerUnderTest.mDefaultVibrationEffect),
@@ -353,14 +470,30 @@
         when(mockAudioManager.getRingerMode()).thenReturn(AudioManager.RINGER_MODE_VIBRATE);
         when(mockAudioManager.getStreamVolume(AudioManager.STREAM_RING)).thenReturn(0);
         enableVibrationWhenRinging();
-        assertFalse(mRingerUnderTest.startRinging(mockCall2, false));
-        mRingCompletionFuture.get();
+        assertFalse(startRingingAndWaitForAsync(mockCall2, false));
         verify(mockTonePlayer).stopTone();
         // Try to play a silent haptics ringtone
-        verify(mockRingtonePlayer).play(any(Ringtone.class));
         verify(mockRingtoneFactory, times(1)).getHapticOnlyRingtone();
-        verify(mockRingtoneFactory, never())
-            .getRingtone(any(Call.class), nullable(VolumeShaper.Configuration.class), anyBoolean());
+        verifyNoMoreInteractions(mockRingtoneFactory);
+        verify(mockRingtone).play();
+
+        // Play default vibration when future completes with no audio coupled haptics
+        verify(mockVibrator).vibrate(eq(mRingerUnderTest.mDefaultVibrationEffect),
+                any(VibrationAttributes.class));
+    }
+
+    @SmallTest
+    @Test
+    public void testVibrateButNoRingForSilentRingtoneWithoutAudioHapticSupport() throws Exception {
+        mIsHapticPlaybackSupported = false;
+        createRingerUnderTest();  // Needed after changing haptic playback support.
+        mRingerUnderTest.startCallWaiting(mockCall1);
+        when(mockAudioManager.getRingerMode()).thenReturn(AudioManager.RINGER_MODE_VIBRATE);
+        when(mockAudioManager.getStreamVolume(AudioManager.STREAM_RING)).thenReturn(0);
+        enableVibrationWhenRinging();
+        assertFalse(startRingingAndWaitForAsync(mockCall2, false));
+        verify(mockTonePlayer).stopTone();
+        verifyZeroInteractions(mockRingtoneFactory);
 
         // Play default vibration when future completes with no audio coupled haptics
         verify(mockVibrator).vibrate(eq(mRingerUnderTest.mDefaultVibrationEffect),
@@ -377,14 +510,13 @@
         when(mockAudioManager.getStreamVolume(AudioManager.STREAM_RING)).thenReturn(0);
         setIsUsingHaptics(mockRingtone, true);
         enableVibrationWhenRinging();
-        assertFalse(mRingerUnderTest.startRinging(mockCall2, false));
+        assertFalse(startRingingAndWaitForAsync(mockCall2, false));
+
         verify(mockRingtoneFactory, times(1)).getHapticOnlyRingtone();
-        verify(mockRingtoneFactory, never())
-            .getRingtone(any(Call.class), nullable(VolumeShaper.Configuration.class), anyBoolean());
-        mRingCompletionFuture.get();
+        verifyNoMoreInteractions(mockRingtoneFactory);
         verify(mockTonePlayer).stopTone();
         // Try to play a silent haptics ringtone
-        verify(mockRingtonePlayer).play(any(Ringtone.class));
+        verify(mockRingtone).play();
         // Skip vibration for audio coupled haptics
         verify(mockVibrator, never()).vibrate(any(VibrationEffect.class),
                 any(VibrationAttributes.class));
@@ -394,36 +526,34 @@
     @Test
     public void testCustomVibrationForRingtone() throws Exception {
         mRingerUnderTest.startCallWaiting(mockCall1);
-        Ringtone mockRingtone = mock(Ringtone.class);
+        Ringtone mockRingtone = ensureRingtoneMocked();
         when(mockAudioManager.getRingerMode()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
-        when(mockRingtoneFactory.getRingtone(any(Call.class), eq(null), anyBoolean()))
-            .thenReturn(mockRingtone);
         when(mockRingtone.getUri()).thenReturn(FAKE_RINGTONE_URI);
         enableVibrationWhenRinging();
-        assertTrue(mRingerUnderTest.startRinging(mockCall2, false));
-        mRingCompletionFuture.get();
+        assertTrue(startRingingAndWaitForAsync(mockCall2, false));
         verify(mockTonePlayer).stopTone();
-        verify(mockRingtonePlayer).play(any(Ringtone.class));
         verify(mockRingtoneFactory, times(1))
-            .getRingtone(any(Call.class), nullable(VolumeShaper.Configuration.class), anyBoolean());
-        verify(mockVibrator).vibrate(eq(spyVibrationEffectProxy.get(FAKE_RINGTONE_URI, mContext)),
-                any(VibrationAttributes.class));
+            .getRingtone(any(Call.class), isNull(), anyBoolean());
+        verifyNoMoreInteractions(mockRingtoneFactory);
+        verify(mockRingtone).play();
+        verify(spyVibrationEffectProxy).get(eq(FAKE_RINGTONE_URI), any(Context.class));
+        verify(mockVibrator).vibrate(eq(URI_VIBRATION_EFFECT), any(VibrationAttributes.class));
     }
 
     @SmallTest
     @Test
     public void testRingAndNoVibrate() throws Exception {
-        ensureRingtoneMocked();
+        Ringtone mockRingtone = ensureRingtoneMocked();
 
         mRingerUnderTest.startCallWaiting(mockCall1);
         ensureRingerIsAudible();
         enableVibrationOnlyWhenNotRinging();
-        assertTrue(mRingerUnderTest.startRinging(mockCall2, false));
-        mRingCompletionFuture.get();
+        assertTrue(startRingingAndWaitForAsync(mockCall2, false));
         verify(mockRingtoneFactory, times(1))
             .getRingtone(any(Call.class), nullable(VolumeShaper.Configuration.class), anyBoolean());
+        verifyNoMoreInteractions(mockRingtoneFactory);
         verify(mockTonePlayer).stopTone();
-        verify(mockRingtonePlayer).play(any(Ringtone.class));
+        verify(mockRingtone).play();
         verify(mockVibrator, never())
                 .vibrate(any(VibrationEffect.class), any(VibrationAttributes.class));
     }
@@ -431,50 +561,31 @@
     @SmallTest
     @Test
     public void testRingWithRampingRinger() throws Exception {
-        ensureRingtoneMocked();
+        Ringtone mockRingtone = ensureRingtoneMocked();
 
         mRingerUnderTest.startCallWaiting(mockCall1);
         ensureRingerIsAudible();
         enableRampingRinger();
         enableVibrationWhenRinging();
-        assertTrue(mRingerUnderTest.startRinging(mockCall2, false));
+        assertTrue(startRingingAndWaitForAsync(mockCall2, false));
         verify(mockRingtoneFactory, times(1))
             .getRingtone(any(Call.class), nullable(VolumeShaper.Configuration.class), anyBoolean());
-        mRingCompletionFuture.get();
+        verifyNoMoreInteractions(mockRingtoneFactory);
         verify(mockTonePlayer).stopTone();
-        verify(mockRingtonePlayer).play(any(Ringtone.class));
+        verify(mockRingtone).play();
     }
 
     @SmallTest
     @Test
-    public void testSilentRingWithHfpStillAcquiresFocus1() throws Exception {
+    public void testSilentRingWithHfpStillAcquiresFocus() throws Exception {
         mRingerUnderTest.startCallWaiting(mockCall1);
-        ensureRingtoneMocked();
         when(mockAudioManager.getRingerMode()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
         when(mockAudioManager.getStreamVolume(AudioManager.STREAM_RING)).thenReturn(0);
         enableVibrationOnlyWhenNotRinging();
-        assertTrue(mRingerUnderTest.startRinging(mockCall2, true));
-        mRingCompletionFuture.get();
+        assertTrue(startRingingAndWaitForAsync(mockCall2, true));
         verify(mockTonePlayer).stopTone();
-        verify(mockRingtonePlayer, never()).play(any(Ringtone.class));
-        verify(mockVibrator, never())
-                .vibrate(any(VibrationEffect.class), any(VibrationAttributes.class));
-    }
-
-    @SmallTest
-    @Test
-    public void testSilentRingWithHfpStillAcquiresFocus2() throws Exception {
-        mRingerUnderTest.startCallWaiting(mockCall1);
-        when(mockRingtoneFactory.getRingtone(
-                 any(Call.class), nullable(VolumeShaper.Configuration.class), anyBoolean()))
-            .thenReturn(null);
-        when(mockAudioManager.getRingerMode()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
-        when(mockAudioManager.getStreamVolume(AudioManager.STREAM_RING)).thenReturn(0);
-        enableVibrationOnlyWhenNotRinging();
-        assertTrue(mRingerUnderTest.startRinging(mockCall2, true));
-        mRingCompletionFuture.get();
-        verify(mockTonePlayer).stopTone();
-        verify(mockRingtonePlayer, never()).play(any(Ringtone.class));
+        // Ringer not audible, so never tries to create a ringtone.
+        verifyZeroInteractions(mockRingtoneFactory);
         verify(mockVibrator, never())
                 .vibrate(any(VibrationEffect.class), any(VibrationAttributes.class));
     }
@@ -483,114 +594,206 @@
     @Test
     public void testRingAndVibrateForAllowedCallInDndMode() throws Exception {
         mRingerUnderTest.startCallWaiting(mockCall1);
-        Ringtone mockRingtone = mock(Ringtone.class);
+        Ringtone mockRingtone = ensureRingtoneMocked();
         when(mockNotificationManager.getZenMode()).thenReturn(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
-        when(mockRingtoneFactory.getRingtone(any(Call.class), eq(null), anyBoolean()))
-                .thenReturn(mockRingtone);
         when(mockAudioManager.getRingerMode()).thenReturn(AudioManager.RINGER_MODE_SILENT);
         when(mockAudioManager.getStreamVolume(AudioManager.STREAM_RING)).thenReturn(100);
         enableVibrationWhenRinging();
-        assertTrue(mRingerUnderTest.startRinging(mockCall2, true));
+        assertTrue(startRingingAndWaitForAsync(mockCall2, true));
         verify(mockRingtoneFactory, times(1))
-            .getRingtone(any(Call.class), nullable(VolumeShaper.Configuration.class), anyBoolean());
-        mRingCompletionFuture.get();
+            .getRingtone(any(Call.class), isNull(), anyBoolean());
+        verifyNoMoreInteractions(mockRingtoneFactory);
         verify(mockTonePlayer).stopTone();
-        verify(mockRingtonePlayer).play(any(Ringtone.class));
+        verify(mockRingtone).play();
     }
 
-    private Ringtone ensureRingtoneMocked() {
-        Ringtone mockRingtone = mock(Ringtone.class);
-        when(mockRingtoneFactory.getRingtone(
-                 any(Call.class), nullable(VolumeShaper.Configuration.class), anyBoolean()))
-            .thenReturn(mockRingtone);
-        when(mockRingtoneFactory.getHapticOnlyRingtone()).thenReturn(mockRingtone);
-        return mockRingtone;
+    @SmallTest
+    @Test
+    public void testDelayRingerForBtHfpDevices() throws Exception {
+        asyncRingtonePlayer.updateBtActiveState(false);
+        Ringtone mockRingtone = ensureRingtoneMocked();
+
+        ensureRingerIsAudible();
+        assertTrue(mRingerUnderTest.startRinging(mockCall1, true));
+        assertTrue(mRingerUnderTest.isRinging());
+        // We should not have the ringtone play until BT moves active
+        verify(mockRingtone, never()).play();
+
+        asyncRingtonePlayer.updateBtActiveState(true);
+        mRingCompletionFuture.get();
+        verify(mockRingtoneFactory, times(1))
+                .getRingtone(any(Call.class), nullable(VolumeShaper.Configuration.class),
+                        anyBoolean());
+        verifyNoMoreInteractions(mockRingtoneFactory);
+        verify(mockRingtone).play();
+
+        mRingerUnderTest.stopRinging();
+        verify(mockRingtone, timeout(1000/*ms*/)).stop();
+        assertFalse(mRingerUnderTest.isRinging());
+    }
+
+    @SmallTest
+    @Test
+    public void testUnblockRingerForStopCommand() throws Exception {
+        asyncRingtonePlayer.updateBtActiveState(false);
+        Ringtone mockRingtone = ensureRingtoneMocked();
+
+        ensureRingerIsAudible();
+        assertTrue(mRingerUnderTest.startRinging(mockCall1, true));
+        // We should not have the ringtone play until BT moves active
+        verify(mockRingtone, never()).play();
+
+        // We are not setting BT active, but calling stop ringing while the other thread is waiting
+        // for BT active should also unblock it.
+        mRingerUnderTest.stopRinging();
+        verify(mockRingtone, timeout(1000/*ms*/)).stop();
     }
 
     /**
-     * assert {@link Ringer#shouldRingForContact(Call, Context) } sets the Call object with suppress
-     * caller
-     *
-     * @throws Exception; should not throw exception.
+     * test shouldRingForContact will suppress the incoming call if matchesCallFilter returns
+     * false (meaning DND is ON and the caller cannot bypass the settings)
      */
     @Test
-    public void testShouldRingForContact_CallSuppressed() throws Exception {
+    public void testShouldRingForContact_CallSuppressed() {
         // WHEN
         when(mockCall1.wasDndCheckComputedForCall()).thenReturn(false);
         when(mockCall1.getHandle()).thenReturn(Uri.parse(""));
-
         when(mContext.getSystemService(NotificationManager.class)).thenReturn(
                 mockNotificationManager);
-        when(mockNotificationManager.matchesCallFilter(any(Bundle.class))).thenReturn(false);
+        // suppress the call
+        when(mockNotificationManager.matchesCallFilter(any(Uri.class))).thenReturn(false);
+
+        // run the method under test
+        assertFalse(mRingerUnderTest.shouldRingForContact(mockCall1));
+
+        // THEN
+        // verify we never set the call object and matchesCallFilter is called
+        verify(mockCall1, never()).setCallIsSuppressedByDoNotDisturb(true);
+        verify(mockNotificationManager, times(1))
+                .matchesCallFilter(any(Uri.class));
+    }
+
+    /**
+     * test shouldRingForContact will alert the user of an incoming call if matchesCallFilter
+     * returns true (meaning DND is NOT suppressing the caller)
+     */
+    @Test
+    public void testShouldRingForContact_CallShouldRing() {
+        // WHEN
+        when(mockCall1.wasDndCheckComputedForCall()).thenReturn(false);
+        when(mockCall1.getHandle()).thenReturn(Uri.parse(""));
+        // alert the user of the call
+
+        // run the method under test
+        assertTrue(mRingerUnderTest.shouldRingForContact(mockCall1));
+
+        // THEN
+        // verify we never set the call object and matchesCallFilter is called
+        verify(mockCall1, never()).setCallIsSuppressedByDoNotDisturb(false);
+        verify(mockNotificationManager, times(1))
+                .matchesCallFilter(any(Uri.class));
+    }
+
+    /**
+     * ensure Telecom does not re-query the NotificationManager if the call object already has
+     * the result.
+     */
+    @Test
+    public void testShouldRingForContact_matchesCallFilterIsAlreadyComputed() {
+        // WHEN
+        when(mockCall1.wasDndCheckComputedForCall()).thenReturn(true);
+        when(mockCall1.isCallSuppressedByDoNotDisturb()).thenReturn(true);
 
         // THEN
         assertFalse(mRingerUnderTest.shouldRingForContact(mockCall1));
-        verify(mockCall1, atLeastOnce()).setCallIsSuppressedByDoNotDisturb(true);
-    }
-
-    /**
-     * assert {@link Ringer#shouldRingForContact(Call, Context) } sets the Call object with ring
-     * caller
-     *
-     * @throws Exception; should not throw exception.
-     */
-    @Test
-    public void testShouldRingForContact_CallShouldRing() throws Exception {
-        // WHEN
-        when(mockCall1.wasDndCheckComputedForCall()).thenReturn(false);
-        when(mockCall1.getHandle()).thenReturn(Uri.parse(""));
-        when(mockNotificationManager.matchesCallFilter(any(Bundle.class))).thenReturn(true);
-
-        // THEN
-        assertTrue(mRingerUnderTest.shouldRingForContact(mockCall1));
-        verify(mockCall1, atLeastOnce()).setCallIsSuppressedByDoNotDisturb(false);
+        verify(mockCall1, never()).setCallIsSuppressedByDoNotDisturb(false);
+        verify(mockNotificationManager, never()).matchesCallFilter(any(Uri.class));
     }
 
     @Test
-    public void testNoFlashNotificationWhenCallSuppressed() {
+    public void testNoFlashNotificationWhenCallSuppressed() throws Exception {
         ensureRingtoneMocked();
         // Start call waiting to make sure that it doesn't stop when we start ringing
         mRingerUnderTest.startCallWaiting(mockCall1);
         when(mockCall2.wasDndCheckComputedForCall()).thenReturn(false);
         when(mockCall2.getHandle()).thenReturn(Uri.parse(""));
-        when(mockNotificationManager.matchesCallFilter(any(Bundle.class))).thenReturn(false);
+        when(mockNotificationManager.matchesCallFilter(any(Uri.class))).thenReturn(false);
 
         assertFalse(mRingerUnderTest.shouldRingForContact(mockCall2));
-        assertFalse(mRingerUnderTest.startRinging(mockCall2, false));
+        assertFalse(startRingingAndWaitForAsync(mockCall2, false));
         verify(mockAccessibilityManagerAdapter, never())
                 .startFlashNotificationSequence(any(Context.class), anyInt());
     }
 
     @Test
-    public void testStartFlashNotificationWhenRingStarts()  {
+    public void testStartFlashNotificationWhenRingStarts() throws Exception {
         ensureRingtoneMocked();
         // Start call waiting to make sure that it doesn't stop when we start ringing
         mRingerUnderTest.startCallWaiting(mockCall1);
         when(mockCall2.wasDndCheckComputedForCall()).thenReturn(false);
         when(mockCall2.getHandle()).thenReturn(Uri.parse(""));
-        when(mockNotificationManager.matchesCallFilter(any(Bundle.class))).thenReturn(true);
 
         assertTrue(mRingerUnderTest.shouldRingForContact(mockCall2));
-        assertTrue(mRingerUnderTest.startRinging(mockCall2, false));
+        assertTrue(startRingingAndWaitForAsync(mockCall2, false));
         verify(mockAccessibilityManagerAdapter, atLeastOnce())
                 .startFlashNotificationSequence(any(Context.class), anyInt());
     }
 
     @Test
-    public void testStopFlashNotificationWhenRingStops() {
-        ensureRingtoneMocked();
+    public void testStopFlashNotificationWhenRingStops() throws Exception {
+        Ringtone mockRingtone = mock(Ringtone.class);
+        when(mockRingtoneFactory.getRingtone(
+                any(Call.class), nullable(VolumeShaper.Configuration.class), anyBoolean()))
+                .thenAnswer(x -> {
+                    // Be slow to create ringtone.
+                    try {
+                        Thread.sleep(300);
+                    } catch (InterruptedException e) {
+                        Thread.currentThread().interrupt();
+                    }
+                    return mockRingtone;
+                });
         // Start call waiting to make sure that it doesn't stop when we start ringing
+        enableVibrationWhenRinging();
         mRingerUnderTest.startCallWaiting(mockCall1);
         when(mockCall2.wasDndCheckComputedForCall()).thenReturn(false);
         when(mockCall2.getHandle()).thenReturn(Uri.parse(""));
-        when(mockNotificationManager.matchesCallFilter(any(Bundle.class))).thenReturn(true);
 
         assertTrue(mRingerUnderTest.shouldRingForContact(mockCall2));
         assertTrue(mRingerUnderTest.startRinging(mockCall2, false));
         mRingerUnderTest.stopRinging();
         verify(mockAccessibilityManagerAdapter, atLeastOnce())
                 .stopFlashNotificationSequence(any(Context.class));
+        mRingCompletionFuture.get();  // Don't leak async work.
+        verify(mockVibrator, never())  // cancelled before it started.
+                .vibrate(any(VibrationEffect.class), any(VibrationAttributes.class));
+    }
 
+    @SmallTest
+    @Test
+    public void testNoRingingForQuietProfile() throws Exception {
+        UserManager um = mContext.getSystemService(UserManager.class);
+        when(um.isManagedProfile(PA_HANDLE.getUserHandle().getIdentifier())).thenReturn(true);
+        when(um.isQuietModeEnabled(PA_HANDLE.getUserHandle())).thenReturn(true);
+        // We don't want to acquire audio focus when self-managed
+        assertFalse(startRingingAndWaitForAsync(mockCall2, true));
+
+        verify(mockTonePlayer, never()).stopTone();
+        verifyZeroInteractions(mockRingtoneFactory);
+        verify(mockVibrator, never())
+                .vibrate(any(VibrationEffect.class), any(VibrationAttributes.class));
+    }
+
+    /**
+     * Call startRinging and wait for its effects to have played out, to allow reliable assertions
+     * after it. The effects are generally "start playing ringtone" and "start vibration" - not
+     * waiting for anything open-ended.
+     */
+    private boolean startRingingAndWaitForAsync(Call mockCall2, boolean isHfpDeviceAttached)
+            throws Exception {
+        boolean result = mRingerUnderTest.startRinging(mockCall2, isHfpDeviceAttached);
+        mRingCompletionFuture.get();
+        return result;
     }
 
     private void ensureRingerIsAudible() {
@@ -618,10 +821,28 @@
     }
 
     private void setIsUsingHaptics(Ringtone mockRingtone, boolean useHaptics) {
-        when(mockSystemSettingsUtil.isHapticPlaybackSupported(any(Context.class)))
-            .thenReturn(useHaptics);
+        // Note: using haptics can also depend on mIsHapticPlaybackSupported. If changing
+        // that, the ringerUnderTest needs to be re-created.
         when(mockSystemSettingsUtil.isAudioCoupledVibrationForRampingRingerEnabled())
             .thenReturn(useHaptics);
         when(mockRingtone.hasHapticChannels()).thenReturn(useHaptics);
     }
+
+    private Ringtone ensureRingtoneMocked() {
+        Ringtone mockRingtone = mock(Ringtone.class);
+        when(mockRingtoneFactory.getRingtone(
+                any(Call.class), nullable(VolumeShaper.Configuration.class), anyBoolean()))
+                .thenReturn(mockRingtone);
+        when(mockRingtoneFactory.getHapticOnlyRingtone()).thenReturn(mockRingtone);
+        return mockRingtone;
+    }
+
+    private void mockVibrationResourceValues(
+            String defaultVibrationContent, boolean useSimpleVibration) {
+        mComponentContextFixture.putRawResource(
+                com.android.internal.R.raw.default_ringtone_vibration_effect,
+                defaultVibrationContent);
+        mComponentContextFixture.putBooleanResource(
+                R.bool.use_simple_vibration_pattern, useSimpleVibration);
+    }
 }
diff --git a/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java b/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
index 569d6b4..e9466ee 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
@@ -33,6 +33,7 @@
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.graphics.drawable.Icon;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Build;
@@ -57,11 +58,13 @@
 import com.android.server.telecom.CallState;
 import com.android.server.telecom.CallsManager;
 import com.android.server.telecom.DefaultDialerCache;
+import com.android.server.telecom.InCallController;
 import com.android.server.telecom.PhoneAccountRegistrar;
 import com.android.server.telecom.TelecomServiceImpl;
 import com.android.server.telecom.TelecomSystem;
 import com.android.server.telecom.components.UserCallIntentProcessor;
 import com.android.server.telecom.components.UserCallIntentProcessorFactory;
+import com.android.server.telecom.flags.FeatureFlags;
 import com.android.server.telecom.voip.IncomingCallTransaction;
 import com.android.server.telecom.voip.OutgoingCallTransaction;
 import com.android.server.telecom.voip.TransactionManager;
@@ -75,6 +78,7 @@
 import org.mockito.ArgumentMatcher;
 import org.mockito.Mock;
 
+import java.lang.reflect.Method;
 import java.util.Collection;
 import java.util.List;
 import java.util.concurrent.Executor;
@@ -85,6 +89,7 @@
 import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
 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.assertThrows;
 import static org.junit.Assert.assertTrue;
@@ -121,7 +126,7 @@
     public static class CallIntentProcessAdapterFake implements CallIntentProcessor.Adapter {
         @Override
         public void processOutgoingCallIntent(Context context, CallsManager callsManager,
-                Intent intent, String callingPackage) {
+                Intent intent, String callingPackage, FeatureFlags flags) {
 
         }
 
@@ -191,6 +196,9 @@
     @Mock private ICallEventCallback mICallEventCallback;
     @Mock private TransactionManager mTransactionManager;
     @Mock private AnomalyReporterAdapter mAnomalyReporterAdapter;
+    @Mock private FeatureFlags mFeatureFlags;
+
+    @Mock private InCallController mInCallController;
 
     private final TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() { };
 
@@ -218,6 +226,7 @@
 
         doReturn(mContext).when(mContext).getApplicationContext();
         doReturn(mContext).when(mContext).createContextAsUser(any(UserHandle.class), anyInt());
+        when(mFakeCallsManager.getInCallController()).thenReturn(mInCallController);
         doNothing().when(mContext).sendBroadcastAsUser(any(Intent.class), any(UserHandle.class),
                 anyString());
         when(mContext.checkCallingOrSelfPermission(Manifest.permission.INTERACT_ACROSS_USERS))
@@ -241,6 +250,7 @@
                 mDefaultDialerCache,
                 mSubscriptionManagerAdapter,
                 mSettingsSecureAdapter,
+                mFeatureFlags,
                 mLock);
         telecomServiceImpl.setTransactionManager(mTransactionManager);
         telecomServiceImpl.setAnomalyReporterAdapter(mAnomalyReporterAdapter);
@@ -259,6 +269,7 @@
 
         mPackageManager = mContext.getPackageManager();
         when(mPackageManager.getPackageUid(anyString(), eq(0))).thenReturn(Binder.getCallingUid());
+        when(mFeatureFlags.earlyBindingToIncallService()).thenReturn(true);
     }
 
     @Override
@@ -292,6 +303,51 @@
         assertEquals(SIP_PA_HANDLE_17, returnedHandleSip);
     }
 
+    /**
+     * Clear the groupId from the PhoneAccount if a package does NOT have MODIFY_PHONE_STATE
+     */
+    @SmallTest
+    @Test
+    public void testGroupIdIsClearedWhenPermissionIsMissing() throws RemoteException {
+        // GIVEN
+        PhoneAccount phoneAccount = makePhoneAccount(TEL_PA_HANDLE_CURRENT)
+                .setGroupId("testId")
+                .build();
+        // WHEN
+        doReturn(phoneAccount).when(mFakePhoneAccountRegistrar).getPhoneAccount(
+                eq(TEL_PA_HANDLE_CURRENT), any(UserHandle.class), anyBoolean());
+        doNothing().when(mAppOpsManager).checkPackage(anyInt(), anyString());
+        doReturn(PackageManager.PERMISSION_DENIED)
+                .when(mContext).checkCallingPermission(MODIFY_PHONE_STATE);
+        // THEN
+        PhoneAccount account =
+                mTSIBinder.getPhoneAccount(TEL_PA_HANDLE_CURRENT, PACKAGE_NAME);
+        assertEquals("***", account.getGroupId());
+    }
+
+    /**
+     * Ensure groupId is not cleared if a package has MODIFY_PHONE_STATE
+     */
+    @SmallTest
+    @Test
+    public void testGroupIdIsNotCleared() throws RemoteException {
+        // GIVEN
+        final String groupId = "testId";
+        PhoneAccount phoneAccount = makePhoneAccount(TEL_PA_HANDLE_CURRENT)
+                .setGroupId(groupId)
+                .build();
+        // WHEN
+        doReturn(phoneAccount).when(mFakePhoneAccountRegistrar).getPhoneAccount(
+                eq(TEL_PA_HANDLE_CURRENT), any(UserHandle.class), anyBoolean());
+        doNothing().when(mAppOpsManager).checkPackage(anyInt(), anyString());
+        doReturn(PackageManager.PERMISSION_GRANTED)
+                .when(mContext).checkCallingPermission(MODIFY_PHONE_STATE);
+        // THEN
+        PhoneAccount account =
+                mTSIBinder.getPhoneAccount(TEL_PA_HANDLE_CURRENT, DEFAULT_DIALER_PACKAGE);
+        assertEquals(groupId, account.getGroupId());
+    }
+
     @SmallTest
     @Test
     public void testGetDefaultOutgoingPhoneAccountSucceedsIfCallerIsSimCallManager()
@@ -608,6 +664,8 @@
     @SmallTest
     @Test
     public void testGetPhoneAccount() throws Exception {
+        doReturn(PackageManager.PERMISSION_GRANTED)
+                .when(mContext).checkCallingPermission(MODIFY_PHONE_STATE);
         makeAccountsVisibleToAllUsers(TEL_PA_HANDLE_16, SIP_PA_HANDLE_17);
         assertEquals(TEL_PA_HANDLE_16, mTSIBinder.getPhoneAccount(TEL_PA_HANDLE_16,
                 mContext.getPackageName()).getAccountHandle());
@@ -903,6 +961,26 @@
 
     @SmallTest
     @Test
+    public void testRegisterPhoneAccountImageIconCrossUser() throws RemoteException {
+        String packageNameToUse = "com.android.officialpackage";
+        PhoneAccountHandle phHandle = new PhoneAccountHandle(new ComponentName(
+                packageNameToUse, "cs"), "test", Binder.getCallingUserHandle());
+        Icon icon = Icon.createWithContentUri("content://10@media/external/images/media/");
+        PhoneAccount phoneAccount = makePhoneAccount(phHandle).setIcon(icon).build();
+        doReturn(PackageManager.PERMISSION_GRANTED)
+                .when(mContext).checkCallingOrSelfPermission(MODIFY_PHONE_STATE);
+
+        // This should fail; security exception will be thrown.
+        registerPhoneAccountTestHelper(phoneAccount, false);
+
+        icon = Icon.createWithContentUri("content://0@media/external/images/media/");
+        phoneAccount = makePhoneAccount(phHandle).setIcon(icon).build();
+        // This should succeed.
+        registerPhoneAccountTestHelper(phoneAccount, true);
+    }
+
+    @SmallTest
+    @Test
     public void testUnregisterPhoneAccount() throws RemoteException {
         String packageNameToUse = "com.android.officialpackage";
         PhoneAccountHandle phHandle = new PhoneAccountHandle(new ComponentName(
@@ -972,6 +1050,7 @@
 
         verify(mFakePhoneAccountRegistrar).getPhoneAccount(
                 TEL_PA_HANDLE_16, TEL_PA_HANDLE_16.getUserHandle());
+        verify(mInCallController, never()).bindToServices(any());
         addCallTestHelper(TelecomManager.ACTION_INCOMING_CALL,
                 CallIntentProcessor.KEY_IS_INCOMING_CALL, extras,
                 TEL_PA_HANDLE_16, false);
@@ -979,6 +1058,81 @@
 
     @SmallTest
     @Test
+    public void testAddNewIncomingFlagDisabledNoEarlyBinding() throws Exception {
+        when(mFeatureFlags.earlyBindingToIncallService()).thenReturn(false);
+        PhoneAccount phoneAccount = makeSkipCallFilteringPhoneAccount(TEL_PA_HANDLE_16).build();
+        phoneAccount.setIsEnabled(true);
+        doReturn(phoneAccount).when(mFakePhoneAccountRegistrar).getPhoneAccount(
+                eq(TEL_PA_HANDLE_16), any(UserHandle.class));
+        doReturn(phoneAccount).when(mFakePhoneAccountRegistrar).getPhoneAccountUnchecked(
+                eq(TEL_PA_HANDLE_16));
+        doNothing().when(mAppOpsManager).checkPackage(anyInt(), anyString());
+        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)).thenReturn(true);
+        Bundle extras = createSampleExtras();
+
+        mTSIBinder.addNewIncomingCall(TEL_PA_HANDLE_16, extras, CALLING_PACKAGE);
+
+        verify(mInCallController, never()).bindToServices(null);
+    }
+
+    @SmallTest
+    @Test
+    public void testAddNewIncomingCallEarlyBindingForNoCallFilterCalls() throws Exception {
+        PhoneAccount phoneAccount = makeSkipCallFilteringPhoneAccount(TEL_PA_HANDLE_16).build();
+        phoneAccount.setIsEnabled(true);
+        doReturn(phoneAccount).when(mFakePhoneAccountRegistrar).getPhoneAccount(
+                eq(TEL_PA_HANDLE_16), any(UserHandle.class));
+        doReturn(phoneAccount).when(mFakePhoneAccountRegistrar).getPhoneAccountUnchecked(
+                eq(TEL_PA_HANDLE_16));
+        doNothing().when(mAppOpsManager).checkPackage(anyInt(), anyString());
+        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)).thenReturn(true);
+        Bundle extras = createSampleExtras();
+
+        mTSIBinder.addNewIncomingCall(TEL_PA_HANDLE_16, extras, CALLING_PACKAGE);
+
+        verify(mInCallController).bindToServices(null);
+    }
+
+    @SmallTest
+    @Test
+    public void testAddNewIncomingCallEarlyBindingNotEnableForNonWatchDevices() throws Exception {
+        PhoneAccount phoneAccount = makeSkipCallFilteringPhoneAccount(TEL_PA_HANDLE_16).build();
+        phoneAccount.setIsEnabled(true);
+        doReturn(phoneAccount).when(mFakePhoneAccountRegistrar).getPhoneAccount(
+                eq(TEL_PA_HANDLE_16), any(UserHandle.class));
+        doReturn(phoneAccount).when(mFakePhoneAccountRegistrar).getPhoneAccountUnchecked(
+                eq(TEL_PA_HANDLE_16));
+        doNothing().when(mAppOpsManager).checkPackage(anyInt(), anyString());
+        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)).thenReturn(false);
+        Bundle extras = createSampleExtras();
+
+        mTSIBinder.addNewIncomingCall(TEL_PA_HANDLE_16, extras, CALLING_PACKAGE);
+
+        verify(mInCallController, never()).bindToServices(null);
+    }
+
+    @SmallTest
+    @Test
+    public void testAddNewIncomingCallEarlyBindingNotEnableForPhoneAccountHasCallFilters()
+            throws Exception {
+        PhoneAccount phoneAccount = makePhoneAccount(TEL_PA_HANDLE_16).build();
+        phoneAccount.setIsEnabled(true);
+        doReturn(phoneAccount).when(mFakePhoneAccountRegistrar).getPhoneAccount(
+                eq(TEL_PA_HANDLE_16), any(UserHandle.class));
+        doReturn(phoneAccount).when(mFakePhoneAccountRegistrar).getPhoneAccountUnchecked(
+                eq(TEL_PA_HANDLE_16));
+        doNothing().when(mAppOpsManager).checkPackage(anyInt(), anyString());
+        when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)).thenReturn(true);
+        Bundle extras = createSampleExtras();
+
+        mTSIBinder.addNewIncomingCall(TEL_PA_HANDLE_16, extras, CALLING_PACKAGE);
+
+        verify(mInCallController, never()).bindToServices(null);
+    }
+
+
+    @SmallTest
+    @Test
     public void testAddNewIncomingCallFailure() throws Exception {
         try {
             mTSIBinder.addNewIncomingCall(TEL_PA_HANDLE_16, null, CALLING_PACKAGE);
@@ -1067,88 +1221,517 @@
         }
     }
 
+    /**
+     * Place a managed call with no PhoneAccount specified and ensure no security exception is
+     * thrown.
+     */
     @SmallTest
     @Test
     public void testPlaceCallWithNonEmergencyPermission() throws Exception {
         Uri handle = Uri.parse("tel:6505551234");
         Bundle extras = createSampleExtras();
 
+        // We have passed in the DEFAULT_DIALER_PACKAGE for this test, so canCallPhone is always
+        // true.
         when(mAppOpsManager.noteOp(eq(AppOpsManager.OP_CALL_PHONE), anyInt(), anyString(),
                 nullable(String.class), nullable(String.class)))
                 .thenReturn(AppOpsManager.MODE_ALLOWED);
         doReturn(PackageManager.PERMISSION_GRANTED)
-                .when(mContext).checkCallingPermission(CALL_PHONE);
+                .when(mContext).checkCallingOrSelfPermission(CALL_PHONE);
         doReturn(PackageManager.PERMISSION_DENIED)
-                .when(mContext).checkCallingPermission(CALL_PRIVILEGED);
+                .when(mContext).checkCallingOrSelfPermission(CALL_PRIVILEGED);
 
         mTSIBinder.placeCall(handle, extras, DEFAULT_DIALER_PACKAGE, null);
-        placeCallTestHelper(handle, extras, true);
+        placeCallTestHelper(handle, extras, /*isSelfManagedExpected*/ false,
+                /*shouldNonEmergencyBeAllowed*/ true);
     }
 
+    /**
+     * Ensure that we get a SecurityException if the UID of the caller doesn't match the UID of the
+     * UID of the package name passed in.
+     */
+    @SmallTest
+    @Test
+    public void testPlaceCall_enforceCallingPackageFailure() throws Exception {
+        Uri handle = Uri.parse("tel:6505551234");
+        Bundle extras = createSampleExtras();
+        // callingPackage matches the PhoneAccountHandle, so this is an app with a self-managed
+        // ConnectionService.
+        extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, TEL_PA_HANDLE_CURRENT);
+
+        // Return a non-matching UID for testing purposes.
+        when(mPackageManager.getPackageUid(anyString(), eq(0))).thenReturn(-1);
+        try {
+            mTSIBinder.placeCall(handle, extras, PACKAGE_NAME, null);
+            fail("Expected SecurityException because calling package doesn't match");
+        } catch(SecurityException e) {
+            // expected
+        }
+    }
+
+    /**
+     * In the case that there is a self-managed call request and MANAGE_OWN_CALLS is granted, ensure
+     * that placeCall does not generate a SecurityException.
+     */
+    @SmallTest
+    @Test
+    public void testPlaceCall_selfManaged_permissionGranted() throws Exception {
+        doReturn(false).when(mDefaultDialerCache).isDefaultOrSystemDialer(
+                eq(DEFAULT_DIALER_PACKAGE), anyInt());
+        when(mFakePhoneAccountRegistrar.getPhoneAccountUnchecked(TEL_PA_HANDLE_CURRENT)).thenReturn(
+                makeSelfManagedPhoneAccount(TEL_PA_HANDLE_CURRENT).build());
+        Uri handle = Uri.parse("tel:6505551234");
+        Bundle extras = createSampleExtras();
+        // callingPackage matches the PhoneAccountHandle, so this is an app with a self-managed
+        // ConnectionService.
+        extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, TEL_PA_HANDLE_CURRENT);
+
+        // pass MANAGE_OWN_CALLS check, but do not have CALL_PHONE
+        doNothing().when(mContext).enforceCallingOrSelfPermission(
+                eq(Manifest.permission.MANAGE_OWN_CALLS), anyString());
+        doReturn(PackageManager.PERMISSION_DENIED)
+                .when(mContext).checkCallingOrSelfPermission(CALL_PHONE);
+        doReturn(PackageManager.PERMISSION_DENIED)
+                .when(mContext).checkCallingOrSelfPermission(CALL_PRIVILEGED);
+
+        try {
+            mTSIBinder.placeCall(handle, extras, PACKAGE_NAME, null);
+            placeCallTestHelper(handle, extras, /*isSelfManagedExpected*/ true,
+                    /*shouldNonEmergencyBeAllowed*/ false);
+        } catch(SecurityException e) {
+            fail("Unexpected SecurityException - MANAGE_OWN_CALLS is set");
+        }
+    }
+
+    /**
+     * In the case that the placeCall API is being used place a self-managed call
+     * (phone account is marked self-managed and the calling application owns that PhoneAccount),
+     * ensure that the call gets placed as not self-managed as to not disclose PA info.
+     */
+    @SmallTest
+    @Test
+    public void testPlaceCall_selfManaged_noPermission() throws Exception {
+        doReturn(false).when(mDefaultDialerCache).isDefaultOrSystemDialer(
+                eq(DEFAULT_DIALER_PACKAGE), anyInt());
+        when(mFakePhoneAccountRegistrar.getPhoneAccountUnchecked(TEL_PA_HANDLE_CURRENT)).thenReturn(
+                makeSelfManagedPhoneAccount(TEL_PA_HANDLE_CURRENT).build());
+        Uri handle = Uri.parse("tel:6505551234");
+        Bundle extras = createSampleExtras();
+        // callingPackage matches the PhoneAccountHandle, so this is an app with a self-managed
+        // ConnectionService.
+        extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, TEL_PA_HANDLE_CURRENT);
+
+        doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission(
+                eq(Manifest.permission.MANAGE_OWN_CALLS), anyString());
+
+        try {
+            mTSIBinder.placeCall(handle, extras, PACKAGE_NAME, null);
+            fail("Expected SecurityException because MANAGE_OWN_CALLS is not set");
+        } catch(SecurityException e) {
+            // expected
+        }
+    }
+
+    /**
+     * In the case that there is a self-managed call request and the app doesn't own that
+     * PhoneAccount, we will need to check CALL_PHONE. If they do not have CALL_PHONE permission,
+     * we need to throw a security exception.
+     */
+    @SmallTest
+    @Test
+    public void testPlaceCall_selfManaged_permissionFail() throws Exception {
+        doReturn(false).when(mDefaultDialerCache).isDefaultOrSystemDialer(
+                eq(DEFAULT_DIALER_PACKAGE), anyInt());
+        when(mFakePhoneAccountRegistrar.getPhoneAccountUnchecked(TEL_PA_HANDLE_CURRENT)).thenReturn(
+                makeSelfManagedPhoneAccount(TEL_PA_HANDLE_CURRENT).build());
+        Uri handle = Uri.parse("tel:6505551234");
+        Bundle extras = createSampleExtras();
+        // callingPackage doesn't match the PhoneAccountHandle package
+        extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, TEL_PA_HANDLE_CURRENT);
+
+        // pass MANAGE_OWN_CALLS check, but do not have CALL PHONE
+        doNothing().when(mContext).enforceCallingOrSelfPermission(
+                eq(Manifest.permission.MANAGE_OWN_CALLS), anyString());
+        doReturn(PackageManager.PERMISSION_DENIED)
+                .when(mContext).checkCallingOrSelfPermission(CALL_PRIVILEGED);
+        doThrow(new SecurityException())
+                .when(mContext).enforceCallingOrSelfPermission(eq(CALL_PHONE), anyString());
+
+        try {
+            // Calling package is received and is not the same as PACKAGE_NAME
+            mTSIBinder.placeCall(handle, extras, PACKAGE_NAME + "2", null);
+            fail("Expected a SecurityException - CALL_PHONE was not granted");
+        } catch(SecurityException e) {
+            // expected
+        }
+    }
+
+    /**
+     * In the case that there is a self-managed call request and the app doesn't own that
+     * PhoneAccount, we will need to check CALL_PHONE. If they have the CALL_PHONE permission, but
+     * the app op has been denied, this should throw a security exception.
+     */
+    @SmallTest
+    @Test
+    public void testPlaceCall_selfManaged_appOpPermissionFail() throws Exception {
+        doReturn(false).when(mDefaultDialerCache).isDefaultOrSystemDialer(
+                eq(DEFAULT_DIALER_PACKAGE), anyInt());
+        when(mFakePhoneAccountRegistrar.getPhoneAccountUnchecked(TEL_PA_HANDLE_CURRENT)).thenReturn(
+                makeSelfManagedPhoneAccount(TEL_PA_HANDLE_CURRENT).build());
+        Uri handle = Uri.parse("tel:6505551234");
+        Bundle extras = createSampleExtras();
+        // callingPackage doesn't match the PhoneAccountHandle package.
+        extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, TEL_PA_HANDLE_CURRENT);
+
+        // pass MANAGE_OWN_CALLS check, but do not have CALL PHONE
+        doNothing().when(mContext).enforceCallingOrSelfPermission(
+                eq(Manifest.permission.MANAGE_OWN_CALLS), anyString());
+        doNothing().when(mContext).enforceCallingOrSelfPermission(eq(CALL_PHONE), anyString());
+        doReturn(PackageManager.PERMISSION_DENIED)
+                .when(mContext).checkCallingOrSelfPermission(CALL_PRIVILEGED);
+        when(mAppOpsManager.noteOp(eq(AppOpsManager.OP_CALL_PHONE), anyInt(), anyString(),
+                nullable(String.class), nullable(String.class)))
+                .thenReturn(AppOpsManager.MODE_ERRORED);
+        try {
+            mTSIBinder.placeCall(handle, extras, PACKAGE_NAME + "2", null);
+            fail("Expected a SecurityException - CALL_PHONE app op is denied");
+        } catch(SecurityException e) {
+            // expected
+        }
+    }
+
+    /**
+     * In the case that there is a self-managed call request and the app doesn't own that
+     * PhoneAccount, we will need to check CALL_PHONE. If they have the correct permissions, the
+     * call will go through, however we will have removed the self-managed PhoneAccountHandle. The
+     * call will go through as a normal managed call request with no PhoneAccountHandle.
+     */
+    @SmallTest
+    @Test
+    public void testPlaceCall_selfManaged_differentCallingPackage() throws Exception {
+        doReturn(false).when(mDefaultDialerCache).isDefaultOrSystemDialer(
+                eq(DEFAULT_DIALER_PACKAGE), anyInt());
+        when(mFakePhoneAccountRegistrar.getPhoneAccountUnchecked(TEL_PA_HANDLE_CURRENT)).thenReturn(
+                makeSelfManagedPhoneAccount(TEL_PA_HANDLE_CURRENT).build());
+        Uri handle = Uri.parse("tel:6505551234");
+        Bundle extras = createSampleExtras();
+        // callingPackage doesn't match the PhoneAccountHandle package
+        extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, TEL_PA_HANDLE_CURRENT);
+
+        // simulate default dialer so CALL_PHONE is granted.
+        when(mAppOpsManager.noteOp(eq(AppOpsManager.OP_CALL_PHONE), anyInt(), anyString(),
+                nullable(String.class), nullable(String.class)))
+                .thenReturn(AppOpsManager.MODE_ALLOWED);
+        doReturn(PackageManager.PERMISSION_GRANTED)
+                .when(mContext).checkCallingOrSelfPermission(CALL_PHONE);
+        doReturn(PackageManager.PERMISSION_DENIED)
+                .when(mContext).checkCallingOrSelfPermission(CALL_PRIVILEGED);
+
+        // We expect the call to go through with no PhoneAccount specified, since the request
+        // contained a self-managed PhoneAccountHandle that didn't belong to this app.
+        Bundle expectedExtras = extras.deepCopy();
+        expectedExtras.remove(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
+        try {
+            mTSIBinder.placeCall(handle, extras, DEFAULT_DIALER_PACKAGE, null);
+        } catch (SecurityException e) {
+            fail("Unexpected SecurityException - CTS is default dialer and MANAGE_OWN_CALLS is not"
+                    + " required. Exception: " + e);
+        }
+        placeCallTestHelper(handle, extras, /*isSelfManagedExpected*/ false,
+                /*shouldNonEmergencyBeAllowed*/ true);
+    }
+
+    /**
+     * In the case that there is a managed call request and the app owns that
+     * PhoneAccount (but is not a self-managed), we will still need to check CALL_PHONE.
+     */
+    @SmallTest
+    @Test
+    public void testPlaceCall_samePackage_managedPhoneAccount_permissionFail() throws Exception {
+        doReturn(false).when(mDefaultDialerCache).isDefaultOrSystemDialer(
+                eq(DEFAULT_DIALER_PACKAGE), anyInt());
+        when(mFakePhoneAccountRegistrar.getPhoneAccountUnchecked(TEL_PA_HANDLE_CURRENT)).thenReturn(
+                makePhoneAccount(TEL_PA_HANDLE_CURRENT).build());
+        Uri handle = Uri.parse("tel:6505551234");
+        Bundle extras = createSampleExtras();
+        // callingPackage doesn't match the PhoneAccountHandle package
+        extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, TEL_PA_HANDLE_CURRENT);
+
+        // CALL_PHONE is not granted to the device.
+        doThrow(new SecurityException())
+                .when(mContext).enforceCallingOrSelfPermission(
+                        eq(Manifest.permission.MANAGE_OWN_CALLS), anyString());
+        doReturn(PackageManager.PERMISSION_DENIED)
+                .when(mContext).checkCallingOrSelfPermission(CALL_PRIVILEGED);
+        doThrow(new SecurityException())
+                .when(mContext).enforceCallingOrSelfPermission(eq(CALL_PHONE), anyString());
+
+        try {
+            mTSIBinder.placeCall(handle, extras, PACKAGE_NAME + "2", null);
+            fail("Expected a SecurityException - CALL_PHONE is not granted");
+        } catch(SecurityException e) {
+            // expected
+        }
+    }
+
+    /**
+     * In the case that there is a managed call request and the app owns that
+     * PhoneAccount (but is not a self-managed), we will still need to check CALL_PHONE.
+     */
+    @SmallTest
+    @Test
+    public void testPlaceCall_samePackage_managedPhoneAccount_AppOpFail() throws Exception {
+        doReturn(false).when(mDefaultDialerCache).isDefaultOrSystemDialer(
+                eq(DEFAULT_DIALER_PACKAGE), anyInt());
+        when(mFakePhoneAccountRegistrar.getPhoneAccountUnchecked(TEL_PA_HANDLE_CURRENT)).thenReturn(
+                makePhoneAccount(TEL_PA_HANDLE_CURRENT).build());
+        Uri handle = Uri.parse("tel:6505551234");
+        Bundle extras = createSampleExtras();
+        // callingPackage matches the PhoneAccountHandle, but this is not a self managed phone
+        // account.
+        extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, TEL_PA_HANDLE_CURRENT);
+
+        // CALL_PHONE is granted, but the app op is not
+        doThrow(new SecurityException())
+                .when(mContext).enforceCallingOrSelfPermission(
+                        eq(Manifest.permission.MANAGE_OWN_CALLS), anyString());
+        doReturn(PackageManager.PERMISSION_DENIED)
+                .when(mContext).checkCallingOrSelfPermission(CALL_PRIVILEGED);
+        doNothing().when(mContext).enforceCallingOrSelfPermission(eq(CALL_PHONE), anyString());
+        when(mAppOpsManager.noteOp(eq(AppOpsManager.OP_CALL_PHONE), anyInt(), anyString(),
+                nullable(String.class), nullable(String.class)))
+                .thenReturn(AppOpsManager.MODE_ERRORED);
+
+        try {
+            mTSIBinder.placeCall(handle, extras, PACKAGE_NAME + "2", null);
+            fail("Expected a SecurityException - CALL_PHONE app op is denied");
+        } catch(SecurityException e) {
+            // expected
+        }
+    }
+
+    /**
+     * Since this is a self-managed call being requested, so ensure we report the call as
+     * self-managed and without non-emergency permissions.
+     */
+    @SmallTest
+    @Test
+    public void testPlaceCall_selfManaged_nonEmergencyPermission() throws Exception {
+        doReturn(false).when(mDefaultDialerCache).isDefaultOrSystemDialer(
+                eq(DEFAULT_DIALER_PACKAGE), anyInt());
+        when(mFakePhoneAccountRegistrar.getPhoneAccountUnchecked(TEL_PA_HANDLE_CURRENT)).thenReturn(
+                makeSelfManagedPhoneAccount(TEL_PA_HANDLE_CURRENT).build());
+        Uri handle = Uri.parse("tel:6505551234");
+        Bundle extras = createSampleExtras();
+        // callingPackage matches the PhoneAccountHandle, so this is an app with a self-managed
+        // ConnectionService.
+        extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, TEL_PA_HANDLE_CURRENT);
+
+        // enforceCallingOrSelfPermission is implicitly granted for MANAGE_OWN_CALLS here and
+        // CALL_PHONE is not required.
+        when(mAppOpsManager.noteOp(eq(AppOpsManager.OP_CALL_PHONE), anyInt(), anyString(),
+                nullable(String.class), nullable(String.class)))
+                .thenReturn(AppOpsManager.MODE_IGNORED);
+        doReturn(PackageManager.PERMISSION_DENIED)
+                .when(mContext).checkCallingOrSelfPermission(CALL_PHONE);
+        doReturn(PackageManager.PERMISSION_DENIED)
+                .when(mContext).checkCallingOrSelfPermission(CALL_PRIVILEGED);
+
+        mTSIBinder.placeCall(handle, extras, PACKAGE_NAME, null);
+        placeCallTestHelper(handle, extras, /*isSelfManagedExpected*/ true,
+                /*shouldNonEmergencyBeAllowed*/ false);
+    }
+
+    /**
+     * Default dialer is calling placeCall and has CALL_PHONE granted, so non-emergency calls
+     * are allowed.
+     */
+    @SmallTest
+    @Test
+    public void testPlaceCall_managed_nonEmergencyGranted() throws Exception {
+        doReturn(false).when(mDefaultDialerCache).isDefaultOrSystemDialer(
+                eq(DEFAULT_DIALER_PACKAGE), anyInt());
+        Uri handle = Uri.parse("tel:6505551234");
+        Bundle extras = createSampleExtras();
+        // callingPackage doesn't match the PhoneAccountHandle, so this app does not have a
+        // self-managed ConnectionService
+        extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, TEL_PA_HANDLE_CURRENT);
+
+        // CALL_PHONE granted
+        when(mAppOpsManager.noteOp(eq(AppOpsManager.OP_CALL_PHONE), anyInt(), anyString(),
+                nullable(String.class), nullable(String.class)))
+                .thenReturn(AppOpsManager.MODE_ALLOWED);
+        doReturn(PackageManager.PERMISSION_GRANTED)
+                .when(mContext).checkCallingOrSelfPermission(CALL_PHONE);
+        doReturn(PackageManager.PERMISSION_DENIED)
+                .when(mContext).checkCallingOrSelfPermission(CALL_PRIVILEGED);
+
+        mTSIBinder.placeCall(handle, extras, DEFAULT_DIALER_PACKAGE, null);
+        placeCallTestHelper(handle, extras, /*isSelfManagedExpected*/ false,
+                /*shouldNonEmergencyBeAllowed*/ true);
+    }
+
+    /**
+     * In the case that there is a managed normal call request and the app has CALL_PRIVILEGED
+     * permission, place call should complete successfully.
+     */
+    @SmallTest
+    @Test
+    public void testPlaceCallPrivileged() throws Exception {
+        doReturn(false).when(mDefaultDialerCache).isDefaultOrSystemDialer(
+                eq(DEFAULT_DIALER_PACKAGE), anyInt());
+        Uri handle = Uri.parse("tel:6505551234");
+
+        // CALL_PHONE is not granted, but CALL_PRIVILEGED is
+        doThrow(new SecurityException())
+                .when(mContext).enforceCallingOrSelfPermission(
+                        eq(Manifest.permission.MANAGE_OWN_CALLS), anyString());
+        doReturn(PackageManager.PERMISSION_GRANTED)
+                .when(mContext).checkCallingOrSelfPermission(CALL_PRIVILEGED);
+        doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission(
+                eq(CALL_PHONE), anyString());
+        when(mAppOpsManager.noteOp(eq(AppOpsManager.OP_CALL_PHONE), anyInt(), anyString(),
+                nullable(String.class), nullable(String.class)))
+                .thenReturn(AppOpsManager.MODE_ERRORED);
+
+        try {
+            mTSIBinder.placeCall(handle, null, PACKAGE_NAME + "2", null);
+        } catch(SecurityException e) {
+            fail("Expected no SecurityException - CALL_PRIVILEGED is granted");
+        }
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mUserCallIntentProcessor).processIntent(intentCaptor.capture(), anyString(),
+                eq(false), eq(true), eq(true));
+        Intent capturedIntent = intentCaptor.getValue();
+        assertEquals(Intent.ACTION_CALL_PRIVILEGED, capturedIntent.getAction());
+        assertEquals(handle, capturedIntent.getData());
+    }
+
+    /**
+     * The default dialer is requesting to place a call and CALL_PHONE is granted, however
+     * OP_CALL_PHONE app op is denied to that app, so non-emergency calls will be denied.
+     */
     @SmallTest
     @Test
     public void testPlaceCallWithAppOpsOff() throws Exception {
         Uri handle = Uri.parse("tel:6505551234");
         Bundle extras = createSampleExtras();
 
+        // We have passed in the DEFAULT_DIALER_PACKAGE for this test, so canCallPhone is always
+        // true.
         when(mAppOpsManager.noteOp(eq(AppOpsManager.OP_CALL_PHONE), anyInt(), anyString(),
                 nullable(String.class), nullable(String.class)))
                 .thenReturn(AppOpsManager.MODE_IGNORED);
         doReturn(PackageManager.PERMISSION_GRANTED)
-                .when(mContext).checkCallingPermission(CALL_PHONE);
+                .when(mContext).checkCallingOrSelfPermission(CALL_PHONE);
         doReturn(PackageManager.PERMISSION_DENIED)
-                .when(mContext).checkCallingPermission(CALL_PRIVILEGED);
+                .when(mContext).checkCallingOrSelfPermission(CALL_PRIVILEGED);
 
         mTSIBinder.placeCall(handle, extras, DEFAULT_DIALER_PACKAGE, null);
-        placeCallTestHelper(handle, extras, false);
+        placeCallTestHelper(handle, extras, /*isSelfManagedExpected*/ false,
+                /*shouldNonEmergencyBeAllowed*/ false);
     }
 
+    /**
+     * The default dialer is requesting to place a call, however CALL_PHONE is denied to that app,
+     * so non-emergency calls will be denied.
+     */
     @SmallTest
     @Test
     public void testPlaceCallWithNoCallingPermission() throws Exception {
         Uri handle = Uri.parse("tel:6505551234");
         Bundle extras = createSampleExtras();
 
+        // We are assumed to be default dialer in this test, so canCallPhone is always true.
         when(mAppOpsManager.noteOp(eq(AppOpsManager.OP_CALL_PHONE), anyInt(), anyString(),
                 nullable(String.class), nullable(String.class)))
                 .thenReturn(AppOpsManager.MODE_ALLOWED);
         doReturn(PackageManager.PERMISSION_DENIED)
-                .when(mContext).checkCallingPermission(CALL_PHONE);
+                .when(mContext).checkCallingOrSelfPermission(CALL_PHONE);
         doReturn(PackageManager.PERMISSION_DENIED)
-                .when(mContext).checkCallingPermission(CALL_PRIVILEGED);
+                .when(mContext).checkCallingOrSelfPermission(CALL_PRIVILEGED);
 
         mTSIBinder.placeCall(handle, extras, DEFAULT_DIALER_PACKAGE, null);
-        placeCallTestHelper(handle, extras, false);
+        placeCallTestHelper(handle, extras, /*isSelfManagedExpected*/ false,
+                /*shouldNonEmergencyBeAllowed*/ false);
     }
 
+    /**
+     * Ensure the expected handle, extras, and non-emergency call permission checks have been
+     * correctly included in the ACTION_CALL intent as part of the
+     * {@link UserCallIntentProcessor#processIntent} method called during the placeCall procedure.
+     * @param expectedHandle Expected outgoing number handle
+     * @param expectedExtras Expected extras in the ACTION_CALL intent.
+     * @param shouldNonEmergencyBeAllowed true if non-emergency calls should be allowed, false if
+     *                                    permission checks failed for non-emergency.
+     */
     private void placeCallTestHelper(Uri expectedHandle, Bundle expectedExtras,
-            boolean shouldNonEmergencyBeAllowed) {
+            boolean isSelfManagedExpected, boolean shouldNonEmergencyBeAllowed) {
         ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
         verify(mUserCallIntentProcessor).processIntent(intentCaptor.capture(), anyString(),
-                eq(shouldNonEmergencyBeAllowed), eq(true));
+                eq(isSelfManagedExpected), eq(shouldNonEmergencyBeAllowed), eq(true));
         Intent capturedIntent = intentCaptor.getValue();
         assertEquals(Intent.ACTION_CALL, capturedIntent.getAction());
         assertEquals(expectedHandle, capturedIntent.getData());
         assertTrue(areBundlesEqual(expectedExtras, capturedIntent.getExtras()));
     }
 
+    /**
+     * Ensure that if the caller was never granted CALL_PHONE (and is not the default dialer), a
+     * SecurityException is thrown.
+     */
     @SmallTest
     @Test
     public void testPlaceCallFailure() throws Exception {
         Uri handle = Uri.parse("tel:6505551234");
         Bundle extras = createSampleExtras();
 
+        // The app is not considered a privileged dialer and does not have the CALL_PHONE
+        // permission.
         doThrow(new SecurityException())
                 .when(mContext).enforceCallingOrSelfPermission(eq(CALL_PHONE), anyString());
+        doReturn(PackageManager.PERMISSION_DENIED)
+                .when(mContext).checkCallingOrSelfPermission(CALL_PRIVILEGED);
 
         try {
             mTSIBinder.placeCall(handle, extras, "arbitrary_package_name", null);
+            fail("Expected SecurityException because CALL_PHONE was not granted to caller");
         } catch (SecurityException e) {
             // expected
         }
 
         verify(mUserCallIntentProcessor, never())
-                .processIntent(any(Intent.class), anyString(), anyBoolean(), eq(true));
+                .processIntent(any(Intent.class), anyString(), eq(false), anyBoolean(), eq(true));
+    }
+
+    /**
+     * Ensure that if the caller was granted CALL_PHONE, but did not get the OP_CALL_PHONE app op
+     * (and is not the default dialer), a SecurityException is thrown.
+     */
+    @SmallTest
+    @Test
+    public void testPlaceCallAppOpFailure() throws Exception {
+        Uri handle = Uri.parse("tel:6505551234");
+        Bundle extras = createSampleExtras();
+
+        // The app is not considered a privileged dialer and does not have the OP_CALL_PHONE
+        // app op.
+        doNothing().when(mContext).enforceCallingOrSelfPermission(eq(CALL_PHONE), anyString());
+        doReturn(PackageManager.PERMISSION_DENIED)
+                .when(mContext).checkCallingOrSelfPermission(CALL_PRIVILEGED);
+        when(mAppOpsManager.noteOp(eq(AppOpsManager.OP_CALL_PHONE), anyInt(), anyString(),
+                nullable(String.class), nullable(String.class)))
+                .thenReturn(AppOpsManager.MODE_IGNORED);
+
+        try {
+            mTSIBinder.placeCall(handle, extras, "arbitrary_package_name", null);
+            fail("Expected SecurityException because CALL_PHONE was not granted to caller");
+        } catch (SecurityException e) {
+            // expected
+        }
+
+        verify(mUserCallIntentProcessor, never())
+                .processIntent(any(Intent.class), anyString(), eq(false), anyBoolean(), eq(true));
     }
 
     @SmallTest
@@ -1206,6 +1789,28 @@
         verify(mContext, never()).sendBroadcastAsUser(any(Intent.class), any(UserHandle.class));
     }
 
+    /**
+     * FeatureFlags is autogenerated code, so there could be a situation where something changes
+     * outside of Telecom control that breaks reflection. This test attempts to ensure that changes
+     * to auto-generated FeatureFlags code that breaks reflection are caught early.
+     */
+    @SmallTest
+    @Test
+    public void testFlagConfigReflectionWorks() {
+        try {
+            Method[] methods = FeatureFlags.class.getMethods();
+            for (Method m : methods) {
+                // test getting the name and invoking the flag code
+                String name = m.getName();
+                Object val = m.invoke(mFeatureFlags);
+                assertNotNull(name);
+                assertNotNull(val);
+            }
+        } catch (Exception e) {
+            fail("Reflection failed for FeatureFlags with error: " + e);
+        }
+    }
+
     @SmallTest
     @Test
     public void testIsVoicemailNumber() throws Exception {
@@ -1647,6 +2252,12 @@
         return new PhoneAccount.Builder(paHandle, "testLabel");
     }
 
+    private PhoneAccount.Builder makeSkipCallFilteringPhoneAccount(PhoneAccountHandle paHandle) {
+        Bundle extras = new Bundle();
+        extras.putBoolean(PhoneAccount.EXTRA_SKIP_CALL_FILTERING, true);
+        return new PhoneAccount.Builder(paHandle, "testLabel").setExtras(extras);
+    }
+
     private Bundle createSampleExtras() {
         Bundle extras = new Bundle();
         extras.putString("test_key", "test_value");
@@ -1654,6 +2265,8 @@
     }
 
     private static boolean areBundlesEqual(Bundle b1, Bundle b2) {
+        if (b1.keySet().size() != b2.keySet().size()) return false;
+
         for (String key1 : b1.keySet()) {
             if (!b1.get(key1).equals(b2.get(key1))) {
                 return false;
diff --git a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
index 7e7235d..aa2cf56 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
@@ -38,6 +38,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.annotation.Nullable;
 import android.app.AppOpsManager;
 import android.bluetooth.BluetoothManager;
 import android.content.BroadcastReceiver;
@@ -66,10 +67,10 @@
 import android.telecom.VideoProfile;
 import android.telephony.TelephonyManager;
 import android.telephony.TelephonyRegistryManager;
-import android.text.TextUtils;
 
 import com.android.internal.telecom.IInCallAdapter;
 import com.android.server.telecom.AsyncRingtonePlayer;
+import com.android.server.telecom.CallAudioCommunicationDeviceTracker;
 import com.android.server.telecom.CallAudioManager;
 import com.android.server.telecom.CallAudioModeStateMachine;
 import com.android.server.telecom.CallAudioRouteStateMachine;
@@ -97,7 +98,9 @@
 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.BlockedNumbersAdapter;
 import com.android.server.telecom.components.UserCallIntentProcessor;
+import com.android.server.telecom.flags.FeatureFlags;
 import com.android.server.telecom.ui.IncomingCallNotifier;
 
 import com.google.common.base.Predicate;
@@ -113,12 +116,13 @@
 import java.util.LinkedList;
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
 
 /**
  * Implements mocks and functionality required to implement telecom system tests.
  */
-public class TelecomSystemTest extends TelecomTestCase {
+public class TelecomSystemTest extends TelecomTestCase{
 
     private static final String CALLING_PACKAGE = TelecomSystemTest.class.getPackageName();
     static final int TEST_POLL_INTERVAL = 10;  // milliseconds
@@ -166,7 +170,7 @@
         }
 
         @Override
-        public void showMissedCallNotification(CallInfo call) {
+        public void showMissedCallNotification(CallInfo call, @Nullable Uri uri) {
             missedCallsNotified.add(call);
         }
 
@@ -211,6 +215,12 @@
     @Mock DeviceIdleControllerAdapter mDeviceIdleControllerAdapter;
 
     @Mock Ringer.AccessibilityManagerAdapter mAccessibilityManagerAdapter;
+    @Mock
+    BlockedNumbersAdapter mBlockedNumbersAdapter;
+    @Mock
+    CallAudioCommunicationDeviceTracker mCommunicationDeviceTracker;
+    @Mock
+    FeatureFlags mFeatureFlags;
 
     final ComponentName mInCallServiceComponentNameX =
             new ComponentName(
@@ -317,6 +327,20 @@
                                     PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS)
                     .build();
 
+    final PhoneAccount mPhoneAccountMultiUser =
+            PhoneAccount.builder(
+                            new PhoneAccountHandle(
+                                    mConnectionServiceComponentNameA,
+                                    "id MU", UserHandle.of(12)),
+                            "Phone account service MU")
+                    .addSupportedUriScheme("tel")
+                    .setCapabilities(
+                            PhoneAccount.CAPABILITY_CALL_PROVIDER |
+                                    PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION |
+                                    PhoneAccount.CAPABILITY_VIDEO_CALLING |
+                                    PhoneAccount.CAPABILITY_MULTI_USER)
+                    .build();
+
     ConnectionServiceFixture mConnectionServiceFixtureA;
     ConnectionServiceFixture mConnectionServiceFixtureB;
     Timeouts.Adapter mTimeoutsAdapter;
@@ -391,6 +415,7 @@
                 handlerThread.quitSafely();
             }
             handlerThreads.clear();
+            mTelecomSystem.getCallsManager().getVoipCallMonitor().stopMonitor();
         }
         waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
         waitForHandlerAction(mHandlerThread.getThreadHandler(), TEST_TIMEOUT);
@@ -424,12 +449,13 @@
         super.tearDown();
     }
 
-    protected ParcelableCall makeConferenceCall() throws Exception {
-        IdPair callId1 = startAndMakeActiveOutgoingCall("650-555-1212",
-                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
+    protected ParcelableCall makeConferenceCall(
+            Intent callIntentExtras1, Intent callIntentExtras2) throws Exception {
+        IdPair callId1 = startAndMakeActiveOutgoingCallWithExtras("650-555-1212",
+                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA, callIntentExtras1);
 
-        IdPair callId2 = startAndMakeActiveOutgoingCall("650-555-1213",
-                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
+        IdPair callId2 = startAndMakeActiveOutgoingCallWithExtras("650-555-1213",
+                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA, callIntentExtras2);
 
         IInCallAdapter inCallAdapter = mInCallServiceFixtureX.getInCallAdapter();
         inCallAdapter.conference(callId1.mCallId, callId2.mCallId);
@@ -488,9 +514,11 @@
         when(mRoleManagerAdapter.getCallCompanionApps()).thenReturn(Collections.emptyList());
         when(mRoleManagerAdapter.getDefaultCallScreeningApp(any(UserHandle.class)))
                 .thenReturn(null);
+        when(mFeatureFlags.useRefactoredAudioRouteSwitching()).thenReturn(false);
         mTelecomSystem = new TelecomSystem(
                 mComponentContextFixture.getTestDouble(),
-                (context, phoneAccountRegistrar, defaultDialerCache, mDeviceIdleControllerAdapter)
+                (context, phoneAccountRegistrar, defaultDialerCache, mDeviceIdleControllerAdapter,
+                        mFeatureFlag)
                         -> mMissedCallNotifier,
                 mCallerInfoAsyncQueryFactoryFixture.getTestDouble(),
                 headsetMediaButtonFactory,
@@ -512,7 +540,10 @@
                             WiredHeadsetManager wiredHeadsetManager,
                             StatusBarNotifier statusBarNotifier,
                             CallAudioManager.AudioServiceFactory audioServiceFactory,
-                            int earpieceControl) {
+                            int earpieceControl,
+                            Executor asyncTaskExecutor,
+                            CallAudioCommunicationDeviceTracker communicationDeviceTracker,
+                            FeatureFlags featureFlags) {
                         return new CallAudioRouteStateMachine(context,
                                 callsManager,
                                 bluetoothManager,
@@ -521,15 +552,21 @@
                                 audioServiceFactory,
                                 // Force enable an earpiece for the end-to-end tests
                                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
-                                mHandlerThread.getLooper());
+                                mHandlerThread.getLooper(),
+                                Runnable::run /* async tasks as now sync for testing! */,
+                                communicationDeviceTracker,
+                                featureFlags);
                     }
                 },
                 new CallAudioModeStateMachine.Factory() {
                     @Override
                     public CallAudioModeStateMachine create(SystemStateHelper systemStateHelper,
-                            AudioManager am) {
+                            AudioManager am, FeatureFlags featureFlags,
+                            CallAudioCommunicationDeviceTracker callAudioCommunicationDeviceTracker
+                    ) {
                         return new CallAudioModeStateMachine(systemStateHelper, am,
-                                mHandlerThread.getLooper());
+                                mHandlerThread.getLooper(), featureFlags,
+                                callAudioCommunicationDeviceTracker);
                     }
                 },
                 mClockProxy,
@@ -540,7 +577,11 @@
                             ContactsAsyncHelper.ContentResolverAdapter adapter) {
                         return new ContactsAsyncHelper(adapter, mHandlerThread.getLooper());
                     }
-                }, mDeviceIdleControllerAdapter, mAccessibilityManagerAdapter);
+                }, mDeviceIdleControllerAdapter, mAccessibilityManagerAdapter,
+                Runnable::run,
+                Runnable::run,
+                mBlockedNumbersAdapter,
+                mFeatureFlags);
 
         mComponentContextFixture.setTelecomManager(new TelecomManager(
                 mComponentContextFixture.getTestDouble(),
@@ -574,6 +615,7 @@
         mTelecomSystem.getPhoneAccountRegistrar().registerPhoneAccount(mPhoneAccountB0);
         mTelecomSystem.getPhoneAccountRegistrar().registerPhoneAccount(mPhoneAccountE0);
         mTelecomSystem.getPhoneAccountRegistrar().registerPhoneAccount(mPhoneAccountE1);
+        mTelecomSystem.getPhoneAccountRegistrar().registerPhoneAccount(mPhoneAccountMultiUser);
 
         mTelecomSystem.getPhoneAccountRegistrar().setUserSelectedOutgoingPhoneAccount(
                 mPhoneAccountA0.getAccountHandle(), Process.myUserHandle());
@@ -632,7 +674,7 @@
 
         startOutgoingPhoneCallWaitForBroadcaster(number, null,
                 connectionServiceFixture, Process.myUserHandle(), VideoProfile.STATE_AUDIO_ONLY,
-                false /*isEmergency*/);
+                false /*isEmergency*/, null);
 
         return mInCallServiceFixtureX.mLatestCallId;
     }
@@ -662,17 +704,17 @@
             throws Exception {
 
         return startOutgoingPhoneCall(number, phoneAccountHandle, connectionServiceFixture,
-                initiatingUser, VideoProfile.STATE_AUDIO_ONLY);
+                initiatingUser, VideoProfile.STATE_AUDIO_ONLY, null);
     }
 
     protected IdPair startOutgoingPhoneCall(String number, PhoneAccountHandle phoneAccountHandle,
             ConnectionServiceFixture connectionServiceFixture, UserHandle initiatingUser,
-            int videoState) throws Exception {
+            int videoState, Intent callIntentExtras) throws Exception {
         int startingNumConnections = connectionServiceFixture.mConnectionById.size();
         int startingNumCalls = mInCallServiceFixtureX.mCallById.size();
 
         startOutgoingPhoneCallPendingCreateConnection(number, phoneAccountHandle,
-                connectionServiceFixture, initiatingUser, videoState);
+                connectionServiceFixture, initiatingUser, videoState, callIntentExtras);
 
         verify(connectionServiceFixture.getTestDouble(), timeout(TEST_TIMEOUT))
                 .createConnectionComplete(anyString(), any());
@@ -715,7 +757,7 @@
 
         // Call will not use the ordered broadcaster, since it is an Emergency Call
         startOutgoingPhoneCallWaitForBroadcaster(number, phoneAccountHandle,
-                connectionServiceFixture, initiatingUser, videoState, true /*isEmergency*/);
+                connectionServiceFixture, initiatingUser, videoState, true /*isEmergency*/, null);
 
         return outgoingCallCreateConnectionComplete(startingNumConnections, startingNumCalls,
                 phoneAccountHandle, connectionServiceFixture);
@@ -724,7 +766,7 @@
     protected void startOutgoingPhoneCallWaitForBroadcaster(String number,
             PhoneAccountHandle phoneAccountHandle,
             ConnectionServiceFixture connectionServiceFixture, UserHandle initiatingUser,
-            int videoState, boolean isEmergency) throws Exception {
+            int videoState, boolean isEmergency, Intent actionCallIntent) throws Exception {
         reset(connectionServiceFixture.getTestDouble(), mInCallServiceFixtureX.getTestDouble(),
                 mInCallServiceFixtureY.getTestDouble());
 
@@ -737,7 +779,9 @@
 
         boolean hasInCallAdapter = mInCallServiceFixtureX.mInCallAdapter != null;
 
-        Intent actionCallIntent = new Intent();
+        if (actionCallIntent == null) {
+            actionCallIntent = new Intent();
+        }
         actionCallIntent.setData(Uri.parse("tel:" + number));
         actionCallIntent.putExtra(Intent.EXTRA_PHONE_NUMBER, number);
         if(isEmergency) {
@@ -757,7 +801,7 @@
         final UserHandle userHandle = initiatingUser;
         Context localAppContext = mComponentContextFixture.getTestDouble().getApplicationContext();
         new UserCallIntentProcessor(localAppContext, userHandle).processIntent(
-                actionCallIntent, null, true /* hasCallAppOp*/, false /* isLocal */);
+                actionCallIntent, null, false, true /* hasCallAppOp*/, false /* isLocal */);
         // Wait for handler to start CallerInfo lookup.
         waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
         // Send the CallerInfo lookup reply.
@@ -782,9 +826,10 @@
     protected String startOutgoingPhoneCallPendingCreateConnection(String number,
             PhoneAccountHandle phoneAccountHandle,
             ConnectionServiceFixture connectionServiceFixture, UserHandle initiatingUser,
-            int videoState) throws Exception {
+            int videoState, Intent callIntentExtras) throws Exception {
         startOutgoingPhoneCallWaitForBroadcaster(number,phoneAccountHandle,
-                connectionServiceFixture, initiatingUser, videoState, false /*isEmergency*/);
+                connectionServiceFixture, initiatingUser,
+                videoState, false /*isEmergency*/, callIntentExtras);
         waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
 
         verifyAndProcessOutgoingCallBroadcast(phoneAccountHandle);
@@ -889,14 +934,24 @@
             PhoneAccountHandle phoneAccountHandle,
             final ConnectionServiceFixture connectionServiceFixture) throws Exception {
         return startIncomingPhoneCall(number, phoneAccountHandle, VideoProfile.STATE_AUDIO_ONLY,
-                connectionServiceFixture);
+                connectionServiceFixture, null);
+    }
+
+    protected IdPair startIncomingPhoneCallWithExtras(
+            String number,
+            PhoneAccountHandle phoneAccountHandle,
+            final ConnectionServiceFixture connectionServiceFixture,
+            Bundle extras) throws Exception {
+        return startIncomingPhoneCall(number, phoneAccountHandle, VideoProfile.STATE_AUDIO_ONLY,
+                connectionServiceFixture, extras);
     }
 
     protected IdPair startIncomingPhoneCall(
             String number,
             PhoneAccountHandle phoneAccountHandle,
             int videoState,
-            final ConnectionServiceFixture connectionServiceFixture) throws Exception {
+            final ConnectionServiceFixture connectionServiceFixture,
+            Bundle extras) throws Exception {
         reset(connectionServiceFixture.getTestDouble(), mInCallServiceFixtureX.getTestDouble(),
                 mInCallServiceFixtureY.getTestDouble());
 
@@ -913,7 +968,9 @@
                 new IncomingCallAddedListener(incomingCallAddedLatch);
         mTelecomSystem.getCallsManager().addListener(callAddedListener);
 
-        Bundle extras = new Bundle();
+        if (extras == null) {
+            extras = new Bundle();
+        }
         extras.putParcelable(
                 TelecomManager.EXTRA_INCOMING_CALL_ADDRESS,
                 Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null));
@@ -1004,7 +1061,16 @@
             PhoneAccountHandle phoneAccountHandle,
             ConnectionServiceFixture connectionServiceFixture) throws Exception {
         return startAndMakeActiveOutgoingCall(number, phoneAccountHandle, connectionServiceFixture,
-                VideoProfile.STATE_AUDIO_ONLY);
+                VideoProfile.STATE_AUDIO_ONLY, null);
+    }
+
+    protected IdPair startAndMakeActiveOutgoingCallWithExtras(
+            String number,
+            PhoneAccountHandle phoneAccountHandle,
+            ConnectionServiceFixture connectionServiceFixture,
+            Intent callIntentExtras) throws Exception {
+        return startAndMakeActiveOutgoingCall(number, phoneAccountHandle, connectionServiceFixture,
+                VideoProfile.STATE_AUDIO_ONLY, callIntentExtras);
     }
 
     // A simple outgoing call, verifying that the appropriate connection service is contacted,
@@ -1012,9 +1078,10 @@
     protected IdPair startAndMakeActiveOutgoingCall(
             String number,
             PhoneAccountHandle phoneAccountHandle,
-            ConnectionServiceFixture connectionServiceFixture, int videoState) throws Exception {
+            ConnectionServiceFixture connectionServiceFixture, int videoState,
+            Intent callIntentExtras) throws Exception {
         IdPair ids = startOutgoingPhoneCall(number, phoneAccountHandle, connectionServiceFixture,
-                Process.myUserHandle(), videoState);
+                Process.myUserHandle(), videoState, callIntentExtras);
 
         connectionServiceFixture.sendSetDialing(ids.mConnectionId);
         if (phoneAccountHandle != mPhoneAccountSelfManaged.getAccountHandle()) {
@@ -1123,7 +1190,7 @@
             PhoneAccountHandle phoneAccountHandle,
             ConnectionServiceFixture connectionServiceFixture) throws Exception {
         IdPair ids = startOutgoingPhoneCall(number, phoneAccountHandle, connectionServiceFixture,
-                Process.myUserHandle(), VideoProfile.STATE_AUDIO_ONLY);
+                Process.myUserHandle(), VideoProfile.STATE_AUDIO_ONLY, null);
 
         connectionServiceFixture.sendSetDialing(ids.mConnectionId);
         if (phoneAccountHandle != mPhoneAccountSelfManaged.getAccountHandle()) {
diff --git a/tests/src/com/android/server/telecom/tests/TelecomTestCase.java b/tests/src/com/android/server/telecom/tests/TelecomTestCase.java
index 5353bc6..e8389a0 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomTestCase.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomTestCase.java
@@ -22,6 +22,9 @@
 
 import androidx.test.InstrumentationRegistry;
 
+import com.android.server.telecom.flags.FeatureFlags;
+
+import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
@@ -33,6 +36,8 @@
 public abstract class TelecomTestCase {
     protected static final String TESTING_TAG = "Telecom-TEST";
     protected Context mContext;
+    @Mock
+    FeatureFlags mFeatureFlags;
 
     MockitoHelper mMockitoHelper = new MockitoHelper();
     ComponentContextFixture mComponentContextFixture;
@@ -42,11 +47,12 @@
         Log.setIsExtendedLoggingEnabled(true);
         Log.setUnitTestingEnabled(true);
         mMockitoHelper.setUp(InstrumentationRegistry.getContext(), getClass());
-        mComponentContextFixture = new ComponentContextFixture();
+        MockitoAnnotations.initMocks(this);
+
+        mComponentContextFixture = new ComponentContextFixture(mFeatureFlags);
         mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
         Log.setSessionContext(mComponentContextFixture.getTestDouble().getApplicationContext());
         Log.getSessionManager().mCleanStaleSessions = null;
-        MockitoAnnotations.initMocks(this);
     }
 
     public void tearDown() throws Exception {
diff --git a/tests/src/com/android/server/telecom/tests/TransactionTests.java b/tests/src/com/android/server/telecom/tests/TransactionTests.java
index 1e6734b..e58c6c4 100644
--- a/tests/src/com/android/server/telecom/tests/TransactionTests.java
+++ b/tests/src/com/android/server/telecom/tests/TransactionTests.java
@@ -16,7 +16,11 @@
 
 package com.android.server.telecom.tests;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
+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.verify;
@@ -39,21 +43,26 @@
 import android.telecom.DisconnectCause;
 import android.telecom.PhoneAccountHandle;
 
+import androidx.test.filters.SmallTest;
+
 import com.android.server.telecom.Call;
 import com.android.server.telecom.CallState;
 import com.android.server.telecom.CallerInfoLookupHelper;
 import com.android.server.telecom.CallsManager;
 import com.android.server.telecom.ClockProxy;
+import com.android.server.telecom.ConnectionServiceWrapper;
+import com.android.server.telecom.flags.FeatureFlags;
 import com.android.server.telecom.PhoneNumberUtilsAdapter;
 import com.android.server.telecom.TelecomSystem;
 import com.android.server.telecom.ui.ToastFactory;
-import com.android.server.telecom.voip.AnswerCallTransaction;
 import com.android.server.telecom.voip.EndCallTransaction;
 import com.android.server.telecom.voip.HoldCallTransaction;
 import com.android.server.telecom.voip.IncomingCallTransaction;
 import com.android.server.telecom.voip.OutgoingCallTransaction;
-import com.android.server.telecom.voip.HoldActiveCallForNewCallTransaction;
-import com.android.server.telecom.voip.RequestFocusTransaction;
+import com.android.server.telecom.voip.MaybeHoldCallForNewCallTransaction;
+import com.android.server.telecom.voip.RequestNewActiveCallTransaction;
+import com.android.server.telecom.voip.VerifyCallStateChangeTransaction;
+import com.android.server.telecom.voip.VoipCallTransactionResult;
 
 import org.junit.After;
 import org.junit.Before;
@@ -63,6 +72,11 @@
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
 
 public class TransactionTests extends TelecomTestCase {
 
@@ -143,40 +157,56 @@
     }
 
     @Test
-    public void testTransactionalRequestFocus() throws Exception {
+    public void testRequestNewCallFocusWithDialingCall() throws Exception {
         // GIVEN
-        RequestFocusTransaction transaction =
-                new RequestFocusTransaction(mCallsManager, mMockCall1);
+        RequestNewActiveCallTransaction transaction =
+                new RequestNewActiveCallTransaction(mCallsManager, mMockCall1);
 
         // WHEN
+        when(mMockCall1.getState()).thenReturn(CallState.DIALING);
         transaction.processTransaction(null);
 
         // THEN
         verify(mCallsManager, times(1))
-                .transactionRequestNewFocusCall(eq(mMockCall1), eq(CallState.ACTIVE),
-                        isA(OutcomeReceiver.class));
+                .requestNewCallFocusAndVerify(eq(mMockCall1), isA(OutcomeReceiver.class));
     }
 
     @Test
-    public void testAnswerCallTransaction() throws Exception {
+    public void testRequestNewCallFocusWithRingingCall() throws Exception {
         // GIVEN
-        AnswerCallTransaction transaction =
-                new AnswerCallTransaction(mCallsManager, mMockCall1, 0);
+        RequestNewActiveCallTransaction transaction =
+                new RequestNewActiveCallTransaction(mCallsManager, mMockCall1);
 
         // WHEN
+        when(mMockCall1.getState()).thenReturn(CallState.RINGING);
         transaction.processTransaction(null);
 
         // THEN
         verify(mCallsManager, times(1))
-                .transactionRequestNewFocusCall(eq(mMockCall1), eq(CallState.ANSWERED),
-                        isA(OutcomeReceiver.class));
+                .requestNewCallFocusAndVerify(eq(mMockCall1), isA(OutcomeReceiver.class));
+    }
+
+    @Test
+    public void testRequestNewCallFocusFailure() throws Exception {
+        // GIVEN
+        RequestNewActiveCallTransaction transaction =
+                new RequestNewActiveCallTransaction(mCallsManager, mMockCall1);
+
+        // WHEN
+        when(mMockCall1.getState()).thenReturn(CallState.DISCONNECTING);
+        when(mCallsManager.getActiveCall()).thenReturn(null);
+        transaction.processTransaction(null);
+
+        // THEN
+        verify(mCallsManager, times(0))
+                .requestNewCallFocusAndVerify( eq(mMockCall1), isA(OutcomeReceiver.class));
     }
 
     @Test
     public void testTransactionalHoldActiveCallForNewCall() throws Exception {
         // GIVEN
-        HoldActiveCallForNewCallTransaction transaction =
-                new HoldActiveCallForNewCallTransaction(mCallsManager, mMockCall1);
+        MaybeHoldCallForNewCallTransaction transaction =
+                new MaybeHoldCallForNewCallTransaction(mCallsManager, mMockCall1);
 
         // WHEN
         transaction.processTransaction(null);
@@ -235,6 +265,65 @@
                         isA(Boolean.class));
     }
 
+    /**
+     * This test verifies if the ConnectionService call is NOT transitioned to the desired call
+     * state (within timeout period), Telecom will disconnect the call.
+     */
+    @SmallTest
+    @Test
+    public void testCallStateChangeTimesOut()
+            throws ExecutionException, InterruptedException, TimeoutException {
+        when(mFeatureFlags.transactionalCsVerifier()).thenReturn(true);
+        VerifyCallStateChangeTransaction t = new VerifyCallStateChangeTransaction(mCallsManager,
+                mMockCall1, CallState.ON_HOLD, true);
+        // WHEN
+        setupHoldableCall();
+
+        // simulate the transaction being processed and the CompletableFuture timing out
+        t.processTransaction(null);
+        CompletableFuture<Integer> timeoutFuture = t.getCallStateOrTimeoutResult();
+        timeoutFuture.complete(VerifyCallStateChangeTransaction.FAILURE_CODE);
+
+        // THEN
+        verify(mMockCall1, times(1)).addCallStateListener(t.getCallStateListenerImpl());
+        assertEquals(timeoutFuture.get().intValue(), VerifyCallStateChangeTransaction.FAILURE_CODE);
+        assertEquals(VoipCallTransactionResult.RESULT_FAILED,
+                t.getTransactionResult().get(2, TimeUnit.SECONDS).getResult());
+        verify(mMockCall1, atLeastOnce()).removeCallStateListener(any());
+        verify(mCallsManager, times(1)).markCallAsDisconnected(eq(mMockCall1), any());
+        verify(mCallsManager, times(1)).markCallAsRemoved(eq(mMockCall1));
+    }
+
+    /**
+     * This test verifies that when an application transitions a call to the requested state,
+     * Telecom does not disconnect the call and transaction completes successfully.
+     */
+    @SmallTest
+    @Test
+    public void testCallStateIsSuccessfullyChanged()
+            throws ExecutionException, InterruptedException, TimeoutException {
+        when(mFeatureFlags.transactionalCsVerifier()).thenReturn(true);
+        VerifyCallStateChangeTransaction t = new VerifyCallStateChangeTransaction(mCallsManager,
+                mMockCall1, CallState.ON_HOLD, true);
+        // WHEN
+        setupHoldableCall();
+
+        // simulate the transaction being processed and the setOnHold() being called / state change
+        t.processTransaction(null);
+        t.getCallStateListenerImpl().onCallStateChanged(CallState.ON_HOLD);
+        when(mMockCall1.getState()).thenReturn(CallState.ON_HOLD);
+
+        // THEN
+        verify(mMockCall1, times(1)).addCallStateListener(t.getCallStateListenerImpl());
+        assertEquals(t.getCallStateOrTimeoutResult().get().intValue(),
+                VerifyCallStateChangeTransaction.SUCCESS_CODE);
+        assertEquals(VoipCallTransactionResult.RESULT_SUCCEED,
+                t.getTransactionResult().get(2, TimeUnit.SECONDS).getResult());
+        verify(mMockCall1, atLeastOnce()).removeCallStateListener(any());
+        verify(mCallsManager, never()).markCallAsDisconnected(eq(mMockCall1), any());
+        verify(mCallsManager, never()).markCallAsRemoved(eq(mMockCall1));
+    }
+
     private Call createSpyCall(PhoneAccountHandle targetPhoneAccount, int initialState, String id) {
         when(mCallsManager.getCallerInfoLookupHelper()).thenReturn(mCallerInfoLookupHelper);
 
@@ -252,7 +341,8 @@
                 false /* shouldAttachToExistingConnection*/,
                 false /* isConference */,
                 mClockProxy,
-                mToastFactory);
+                mToastFactory,
+                mFeatureFlags);
 
         Call callSpy = Mockito.spy(call);
 
@@ -265,4 +355,12 @@
 
         return callSpy;
     }
+
+    private void setupHoldableCall(){
+        when(mMockCall1.getState()).thenReturn(CallState.ACTIVE);
+        when(mMockCall1.getConnectionServiceWrapper()).thenReturn(
+                mock(ConnectionServiceWrapper.class));
+        doNothing().when(mMockCall1).addCallStateListener(any());
+        doReturn(true).when(mMockCall1).removeCallStateListener(any());
+    }
 }
\ No newline at end of file
diff --git a/tests/src/com/android/server/telecom/tests/TransactionalServiceWrapperTest.java b/tests/src/com/android/server/telecom/tests/TransactionalServiceWrapperTest.java
index ae6e15f..fa5f2a2 100644
--- a/tests/src/com/android/server/telecom/tests/TransactionalServiceWrapperTest.java
+++ b/tests/src/com/android/server/telecom/tests/TransactionalServiceWrapperTest.java
@@ -25,6 +25,7 @@
 
 
 import android.content.ComponentName;
+import android.os.IBinder;
 import android.os.OutcomeReceiver;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
@@ -35,6 +36,7 @@
 import com.android.internal.telecom.ICallEventCallback;
 import com.android.server.telecom.Call;
 import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.TelecomSystem;
 import com.android.server.telecom.TransactionalServiceRepository;
 import com.android.server.telecom.TransactionalServiceWrapper;
 import com.android.server.telecom.voip.EndCallTransaction;
@@ -68,6 +70,8 @@
     @Mock private TransactionManager mTransactionManager;
     @Mock private ICallEventCallback mCallEventCallback;
     @Mock private TransactionalServiceRepository mRepository;
+    @Mock private IBinder mIBinder;
+    private final TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() {};
 
     @Override
     @Before
@@ -76,7 +80,8 @@
         MockitoAnnotations.initMocks(this);
         Mockito.when(mMockCall1.getId()).thenReturn(CALL_ID_1);
         Mockito.when(mMockCall2.getId()).thenReturn(CALL_ID_2);
-
+        Mockito.when(mCallsManager.getLock()).thenReturn(mLock);
+        Mockito.when(mCallEventCallback.asBinder()).thenReturn(mIBinder);
         mTransactionalServiceWrapper = new TransactionalServiceWrapper(mCallEventCallback,
                 mCallsManager, SERVICE_HANDLE, mMockCall1, mRepository);
 
diff --git a/tests/src/com/android/server/telecom/tests/VideoCallTests.java b/tests/src/com/android/server/telecom/tests/VideoCallTests.java
index 97e71d1..c77a614 100644
--- a/tests/src/com/android/server/telecom/tests/VideoCallTests.java
+++ b/tests/src/com/android/server/telecom/tests/VideoCallTests.java
@@ -32,6 +32,7 @@
 import android.test.suitebuilder.annotation.MediumTest;
 
 import com.android.server.telecom.CallAudioModeStateMachine;
+import com.android.server.telecom.CallAudioRouteAdapter;
 import com.android.server.telecom.CallAudioRouteStateMachine;
 
 import java.util.List;
@@ -105,7 +106,7 @@
         // Start an incoming video call.
         IdPair ids = startAndMakeActiveOutgoingCall("650-555-1212",
                 mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA,
-                VideoProfile.STATE_BIDIRECTIONAL);
+                VideoProfile.STATE_BIDIRECTIONAL, null);
 
         verifyAudioRoute(CallAudioState.ROUTE_SPEAKER);
     }
@@ -121,7 +122,7 @@
         // Start an incoming video call.
         IdPair ids = startAndMakeActiveOutgoingCall("650-555-1212",
                 mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA,
-                VideoProfile.STATE_TX_ENABLED);
+                VideoProfile.STATE_TX_ENABLED, null);
 
         verifyAudioRoute(CallAudioState.ROUTE_SPEAKER);
     }
@@ -137,7 +138,7 @@
         // Start an incoming video call.
         IdPair ids = startAndMakeActiveOutgoingCall("650-555-1212",
                 mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA,
-                VideoProfile.STATE_AUDIO_ONLY);
+                VideoProfile.STATE_AUDIO_ONLY, null);
 
         verifyAudioRoute(CallAudioState.ROUTE_EARPIECE);
     }
@@ -165,7 +166,7 @@
     @Test
     public void testIncomingVideoCallMissedCheckVideoHistory() throws Exception {
         IdPair ids = startIncomingPhoneCall("650-555-1212", mPhoneAccountA0.getAccountHandle(),
-                VideoProfile.STATE_BIDIRECTIONAL, mConnectionServiceFixtureA);
+                VideoProfile.STATE_BIDIRECTIONAL, mConnectionServiceFixtureA, null);
         com.android.server.telecom.Call call = mTelecomSystem.getCallsManager().getCalls()
                 .iterator().next();
 
@@ -182,7 +183,7 @@
     @Test
     public void testIncomingVideoCallRejectedCheckVideoHistory() throws Exception {
         IdPair ids = startIncomingPhoneCall("650-555-1212", mPhoneAccountA0.getAccountHandle(),
-                VideoProfile.STATE_BIDIRECTIONAL, mConnectionServiceFixtureA);
+                VideoProfile.STATE_BIDIRECTIONAL, mConnectionServiceFixtureA, null);
         com.android.server.telecom.Call call = mTelecomSystem.getCallsManager().getCalls()
                 .iterator().next();
 
@@ -201,7 +202,7 @@
     public void testOutgoingVideoCallCanceledCheckVideoHistory() throws Exception {
         IdPair ids = startOutgoingPhoneCall("650-555-1212", mPhoneAccountA0.getAccountHandle(),
                 mConnectionServiceFixtureA, Process.myUserHandle(),
-                VideoProfile.STATE_BIDIRECTIONAL);
+                VideoProfile.STATE_BIDIRECTIONAL, null);
         com.android.server.telecom.Call call = mTelecomSystem.getCallsManager().getCalls()
                 .iterator().next();
 
@@ -219,7 +220,7 @@
     public void testOutgoingVideoCallRejectedCheckVideoHistory() throws Exception {
         IdPair ids = startOutgoingPhoneCall("650-555-1212", mPhoneAccountA0.getAccountHandle(),
                 mConnectionServiceFixtureA, Process.myUserHandle(),
-                VideoProfile.STATE_BIDIRECTIONAL);
+                VideoProfile.STATE_BIDIRECTIONAL, null);
         com.android.server.telecom.Call call = mTelecomSystem.getCallsManager().getCalls()
                 .iterator().next();
 
@@ -237,7 +238,7 @@
     public void testOutgoingVideoCallAnsweredAsAudio() throws Exception {
         IdPair ids = startOutgoingPhoneCall("650-555-1212", mPhoneAccountA0.getAccountHandle(),
                 mConnectionServiceFixtureA, Process.myUserHandle(),
-                VideoProfile.STATE_BIDIRECTIONAL);
+                VideoProfile.STATE_BIDIRECTIONAL, null);
         com.android.server.telecom.Call call = mTelecomSystem.getCallsManager().getCalls()
                 .iterator().next();
 
@@ -258,13 +259,13 @@
      */
     private void verifyAudioRoute(int expectedRoute) throws Exception {
         // Capture all onCallAudioStateChanged callbacks to InCall.
-        CallAudioRouteStateMachine carsm = mTelecomSystem.getCallsManager()
-                .getCallAudioManager().getCallAudioRouteStateMachine();
+        CallAudioRouteAdapter cara = mTelecomSystem.getCallsManager()
+                .getCallAudioManager().getCallAudioRouteAdapter();
         CallAudioModeStateMachine camsm = mTelecomSystem.getCallsManager()
                 .getCallAudioManager().getCallAudioModeStateMachine();
         waitForHandlerAction(camsm.getHandler(), TEST_TIMEOUT);
         final boolean[] success = {true};
-        carsm.sendMessage(CallAudioRouteStateMachine.RUN_RUNNABLE, (Runnable) () -> {
+        cara.sendMessage(CallAudioRouteStateMachine.RUN_RUNNABLE, (Runnable) () -> {
             ArgumentCaptor<CallAudioState> callAudioStateArgumentCaptor = ArgumentCaptor.forClass(
                     CallAudioState.class);
             try {
@@ -277,7 +278,7 @@
             assertEquals(expectedRoute, changes.get(changes.size() - 1).getRoute());
             success[0] = true;
         });
-        waitForHandlerAction(carsm.getHandler(), TEST_TIMEOUT);
+        waitForHandlerAction(cara.getAdapterHandler(), TEST_TIMEOUT);
         assertTrue(success[0]);
     }
 }
diff --git a/tests/src/com/android/server/telecom/tests/VoipCallMonitorTest.java b/tests/src/com/android/server/telecom/tests/VoipCallMonitorTest.java
new file mode 100644
index 0000000..ddea231
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/VoipCallMonitorTest.java
@@ -0,0 +1,335 @@
+/*
+ * Copyright (C) 2022 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.app.ForegroundServiceDelegationOptions.DELEGATION_SERVICE_PHONE_CALL;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE;
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+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.when;
+
+import android.app.ActivityManagerInternal;
+import android.app.ForegroundServiceDelegationOptions;
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.app.Person;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.UserHandle;
+import android.service.notification.StatusBarNotification;
+import android.telecom.PhoneAccountHandle;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallState;
+import com.android.server.telecom.TelecomSystem;
+import com.android.server.telecom.voip.VoipCallMonitor;
+
+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;
+
+@RunWith(JUnit4.class)
+public class VoipCallMonitorTest extends TelecomTestCase {
+    private VoipCallMonitor mMonitor;
+    private static final String NAME = "John Smith";
+    private static final String PKG_NAME_1 = "telecom.voip.test1";
+    private static final String PKG_NAME_2 = "telecom.voip.test2";
+    private static final String CLS_NAME = "VoipActivity";
+    private static final String ID_1 = "id1";
+    public static final String CHANNEL_ID = "TelecomVoipAppChannelId";
+    private static final UserHandle USER_HANDLE_1 = new UserHandle(1);
+    private static final long TIMEOUT = 5000L;
+
+    @Mock private TelecomSystem.SyncRoot mLock;
+    @Mock private ActivityManagerInternal mActivityManagerInternal;
+    @Mock private IBinder mServiceConnection;
+
+    private final PhoneAccountHandle mHandle1User1 = new PhoneAccountHandle(
+            new ComponentName(PKG_NAME_1, CLS_NAME), ID_1, USER_HANDLE_1);
+    private final PhoneAccountHandle mHandle2User1 = new PhoneAccountHandle(
+            new ComponentName(PKG_NAME_2, CLS_NAME), ID_1, USER_HANDLE_1);
+
+    @Override
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        mMonitor = new VoipCallMonitor(mContext, mLock);
+        mActivityManagerInternal = mock(ActivityManagerInternal.class);
+        mMonitor.setActivityManagerInternal(mActivityManagerInternal);
+        mMonitor.startMonitor();
+        when(mActivityManagerInternal.startForegroundServiceDelegate(any(
+                ForegroundServiceDelegationOptions.class), any(ServiceConnection.class)))
+                .thenReturn(true);
+    }
+
+    /**
+     * This test ensures VoipCallMonitor is passing the correct foregroundServiceTypes when starting
+     * foreground service delegation on behalf of a client.
+     */
+    @SmallTest
+    @Test
+    public void testVerifyForegroundServiceTypesBeingPassedToActivityManager() {
+        Call call = createTestCall("testCall", mHandle1User1);
+        ArgumentCaptor<ForegroundServiceDelegationOptions> optionsCaptor =
+                ArgumentCaptor.forClass(ForegroundServiceDelegationOptions.class);
+
+        mMonitor.onCallAdded(call);
+
+        verify(mActivityManagerInternal, timeout(TIMEOUT)).startForegroundServiceDelegate(
+                 optionsCaptor.capture(), any(ServiceConnection.class));
+
+        assertEquals( FOREGROUND_SERVICE_TYPE_PHONE_CALL |
+                FOREGROUND_SERVICE_TYPE_MICROPHONE |
+                FOREGROUND_SERVICE_TYPE_CAMERA |
+                FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE,
+                optionsCaptor.getValue().mForegroundServiceTypes);
+
+        mMonitor.onCallRemoved(call);
+    }
+
+    @SmallTest
+    @Test
+    public void testStartMonitorForOneCall() {
+        Call call = createTestCall("testCall", mHandle1User1);
+        IBinder service = mock(IBinder.class);
+
+        ArgumentCaptor<ServiceConnection> captor = ArgumentCaptor.forClass(ServiceConnection.class);
+        mMonitor.onCallAdded(call);
+        verify(mActivityManagerInternal, timeout(TIMEOUT)).startForegroundServiceDelegate(any(
+                ForegroundServiceDelegationOptions.class), captor.capture());
+        ServiceConnection conn = captor.getValue();
+        conn.onServiceConnected(mHandle1User1.getComponentName(), service);
+
+        mMonitor.onCallRemoved(call);
+        verify(mActivityManagerInternal, timeout(TIMEOUT)).stopForegroundServiceDelegate(eq(conn));
+    }
+
+    @SmallTest
+    @Test
+    public void testMonitorForTwoCallsOnSameHandle() {
+        Call call1 = createTestCall("testCall1", mHandle1User1);
+        Call call2 = createTestCall("testCall2", mHandle1User1);
+        IBinder service = mock(IBinder.class);
+
+        ArgumentCaptor<ServiceConnection> captor1 =
+                ArgumentCaptor.forClass(ServiceConnection.class);
+        mMonitor.onCallAdded(call1);
+        verify(mActivityManagerInternal, timeout(TIMEOUT).times(1))
+                .startForegroundServiceDelegate(any(ForegroundServiceDelegationOptions.class),
+                        captor1.capture());
+        ServiceConnection conn1 = captor1.getValue();
+        conn1.onServiceConnected(mHandle1User1.getComponentName(), service);
+
+        ArgumentCaptor<ServiceConnection> captor2 =
+                ArgumentCaptor.forClass(ServiceConnection.class);
+        mMonitor.onCallAdded(call2);
+        verify(mActivityManagerInternal, timeout(TIMEOUT).times(2))
+                .startForegroundServiceDelegate(any(ForegroundServiceDelegationOptions.class),
+                        captor2.capture());
+        ServiceConnection conn2 = captor2.getValue();
+        conn2.onServiceConnected(mHandle1User1.getComponentName(), service);
+
+        mMonitor.onCallRemoved(call1);
+        verify(mActivityManagerInternal, never()).stopForegroundServiceDelegate(
+                any(ServiceConnection.class));
+        mMonitor.onCallRemoved(call2);
+        verify(mActivityManagerInternal, timeout(TIMEOUT).times(1))
+                .stopForegroundServiceDelegate(eq(conn2));
+    }
+
+    @SmallTest
+    @Test
+    public void testMonitorForTwoCallsOnDifferentHandle() {
+        Call call1 = createTestCall("testCall1", mHandle1User1);
+        Call call2 = createTestCall("testCall2", mHandle2User1);
+        IBinder service = mock(IBinder.class);
+
+        ArgumentCaptor<ServiceConnection> connCaptor1 = ArgumentCaptor.forClass(
+                ServiceConnection.class);
+        ArgumentCaptor<ForegroundServiceDelegationOptions> optionsCaptor1 =
+                ArgumentCaptor.forClass(ForegroundServiceDelegationOptions.class);
+        mMonitor.onCallAdded(call1);
+        verify(mActivityManagerInternal, timeout(TIMEOUT).times(1))
+                .startForegroundServiceDelegate(optionsCaptor1.capture(), connCaptor1.capture());
+        ForegroundServiceDelegationOptions options1 = optionsCaptor1.getValue();
+        ServiceConnection conn1 = connCaptor1.getValue();
+        conn1.onServiceConnected(mHandle1User1.getComponentName(), service);
+        assertEquals(PKG_NAME_1, options1.getComponentName().getPackageName());
+
+        ArgumentCaptor<ServiceConnection> connCaptor2 = ArgumentCaptor.forClass(
+                ServiceConnection.class);
+        ArgumentCaptor<ForegroundServiceDelegationOptions> optionsCaptor2 =
+                ArgumentCaptor.forClass(ForegroundServiceDelegationOptions.class);
+        mMonitor.onCallAdded(call2);
+        verify(mActivityManagerInternal, timeout(TIMEOUT).times(2))
+                .startForegroundServiceDelegate(optionsCaptor2.capture(), connCaptor2.capture());
+        ForegroundServiceDelegationOptions options2 = optionsCaptor2.getValue();
+        ServiceConnection conn2 = connCaptor2.getValue();
+        conn2.onServiceConnected(mHandle2User1.getComponentName(), service);
+        assertEquals(PKG_NAME_2, options2.getComponentName().getPackageName());
+
+        mMonitor.onCallRemoved(call2);
+        verify(mActivityManagerInternal).stopForegroundServiceDelegate(eq(conn2));
+        mMonitor.onCallRemoved(call1);
+        verify(mActivityManagerInternal).stopForegroundServiceDelegate(eq(conn1));
+    }
+
+    @SmallTest
+    @Test
+    public void testStopDelegation() {
+        Call call1 = createTestCall("testCall1", mHandle1User1);
+        Call call2 = createTestCall("testCall2", mHandle1User1);
+        IBinder service = mock(IBinder.class);
+
+        ArgumentCaptor<ServiceConnection> captor1 =
+                ArgumentCaptor.forClass(ServiceConnection.class);
+        mMonitor.onCallAdded(call1);
+        verify(mActivityManagerInternal, timeout(TIMEOUT).times(1))
+                .startForegroundServiceDelegate(any(ForegroundServiceDelegationOptions.class),
+                        captor1.capture());
+        ServiceConnection conn1 = captor1.getValue();
+        conn1.onServiceConnected(mHandle1User1.getComponentName(), service);
+
+        ArgumentCaptor<ServiceConnection> captor2 =
+                ArgumentCaptor.forClass(ServiceConnection.class);
+        mMonitor.onCallAdded(call2);
+        verify(mActivityManagerInternal, timeout(TIMEOUT).times(2))
+                .startForegroundServiceDelegate(any(ForegroundServiceDelegationOptions.class),
+                        captor2.capture());
+        ServiceConnection conn2 = captor2.getValue();
+        conn2.onServiceConnected(mHandle1User1.getComponentName(), service);
+
+        mMonitor.stopFGSDelegation(call1);
+        verify(mActivityManagerInternal, timeout(TIMEOUT).times(1))
+                .stopForegroundServiceDelegate(eq(conn2));
+        conn2.onServiceDisconnected(mHandle1User1.getComponentName());
+        mMonitor.onCallRemoved(call1);
+        verify(mActivityManagerInternal, timeout(TIMEOUT).times(1))
+                .stopForegroundServiceDelegate(any(ServiceConnection.class));
+    }
+
+    /**
+     * Ensure an app loses foreground service delegation if the user dismisses the call style
+     * notification or the app removes the notification.
+     * Note: post the notification AFTER foreground service delegation is gained
+     */
+    @SmallTest
+    @Test
+    public void testStopFgsIfCallNotificationIsRemoved_PostedAfterFgsIsGained() {
+        // GIVEN
+        StatusBarNotification sbn = createStatusBarNotificationFromHandle(mHandle1User1);
+
+        // WHEN
+        // FGS is gained after the call is added to VoipCallMonitor
+        ServiceConnection c = addCallAndVerifyFgsIsGained(createTestCall("1", mHandle1User1));
+        // simulate an app posting a call style notification after FGS is gained
+        mMonitor.postNotification(sbn);
+
+        // THEN
+        // shortly after posting the notification, simulate the user dismissing it
+        mMonitor.removeNotification(sbn);
+        // FGS should be removed once the notification is removed
+        verify(mActivityManagerInternal, timeout(TIMEOUT)).stopForegroundServiceDelegate(c);
+    }
+
+    /**
+     * Ensure an app loses foreground service delegation if the user dismisses the call style
+     * notification or the app removes the notification.
+     * Note: post the notification BEFORE foreground service delegation is gained
+     */
+    @SmallTest
+    @Test
+    public void testStopFgsIfCallNotificationIsRemoved_PostedBeforeFgsIsGained() {
+        // GIVEN
+        StatusBarNotification sbn = createStatusBarNotificationFromHandle(mHandle1User1);
+
+        // WHEN
+        //  an app posts a call style notification before FGS is gained
+        mMonitor.postNotification(sbn);
+        // FGS is gained after the call is added to VoipCallMonitor
+        ServiceConnection c = addCallAndVerifyFgsIsGained(createTestCall("1", mHandle1User1));
+
+        // THEN
+        // shortly after posting the notification, simulate the user dismissing it
+        mMonitor.removeNotification(sbn);
+        // FGS should be removed once the notification is removed
+        verify(mActivityManagerInternal, timeout(TIMEOUT)).stopForegroundServiceDelegate(c);
+    }
+
+    private Call createTestCall(String id, PhoneAccountHandle handle) {
+        Call call = mock(Call.class);
+        when(call.getTargetPhoneAccount()).thenReturn(handle);
+        when(call.isTransactionalCall()).thenReturn(true);
+        when(call.getExtras()).thenReturn(new Bundle());
+        when(call.getId()).thenReturn(id);
+        when(call.getCallingPackageIdentity()).thenReturn(new Call.CallingPackageIdentity());
+        when(call.getState()).thenReturn(CallState.ACTIVE);
+        return call;
+    }
+
+    private Notification createCallStyleNotification() {
+        PendingIntent pendingOngoingIntent = PendingIntent.getActivity(mContext, 0,
+                new Intent(""), PendingIntent.FLAG_IMMUTABLE);
+
+        return new Notification.Builder(mContext,
+                CHANNEL_ID)
+                .setStyle(Notification.CallStyle.forOngoingCall(
+                        new Person.Builder().setName(NAME).setImportant(true).build(),
+                        pendingOngoingIntent)
+                )
+                .setFullScreenIntent(pendingOngoingIntent, true)
+                .build();
+    }
+
+    private StatusBarNotification createStatusBarNotificationFromHandle(PhoneAccountHandle handle) {
+        return new StatusBarNotification(
+                handle.getComponentName().getPackageName(), "", 0, "", 0, 0,
+                createCallStyleNotification(), handle.getUserHandle(), "", 0);
+    }
+
+    private ServiceConnection addCallAndVerifyFgsIsGained(Call call) {
+        ArgumentCaptor<ServiceConnection> captor = ArgumentCaptor.forClass(ServiceConnection.class);
+        // add the call to the VoipCallMonitor under test which will start FGS
+        mMonitor.onCallAdded(call);
+        // FGS should be granted within the timeout
+        verify(mActivityManagerInternal, timeout(TIMEOUT))
+                .startForegroundServiceDelegate(any(
+                                ForegroundServiceDelegationOptions.class),
+                        captor.capture());
+        // onServiceConnected must be called in order for VoipCallMonitor to start monitoring for
+        // a notification before the timeout expires
+        ServiceConnection serviceConnection = captor.getValue();
+        serviceConnection.onServiceConnected(mHandle1User1.getComponentName(), mServiceConnection);
+        return serviceConnection;
+    }
+}
diff --git a/tests/src/com/android/server/telecom/tests/VoipCallTransactionTest.java b/tests/src/com/android/server/telecom/tests/VoipCallTransactionTest.java
index 58e4a77..0a7e27d 100644
--- a/tests/src/com/android/server/telecom/tests/VoipCallTransactionTest.java
+++ b/tests/src/com/android/server/telecom/tests/VoipCallTransactionTest.java
@@ -25,6 +25,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.server.telecom.TelecomSystem;
 import com.android.server.telecom.voip.ParallelTransaction;
 import com.android.server.telecom.voip.SerialTransaction;
 import com.android.server.telecom.voip.TransactionManager;
@@ -49,6 +50,7 @@
 public class VoipCallTransactionTest extends TelecomTestCase {
     private StringBuilder mLog;
     private TransactionManager mTransactionManager;
+    private static final TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() { };
 
     private class TestVoipCallTransaction extends VoipCallTransaction {
         public static final int SUCCESS = 0;
@@ -60,7 +62,7 @@
         private int mType;
 
         public TestVoipCallTransaction(String name, long sleepTime, int type) {
-            super();
+            super(VoipCallTransactionTest.this.mLock);
             mName = name;
             mSleepTime = sleepTime;
             mType = type;
@@ -125,7 +127,8 @@
         OutcomeReceiver<VoipCallTransactionResult, CallException> outcomeReceiver =
                 resultFuture::complete;
         String expectedLog = "t1 success;\nt2 success;\nt3 success;\n";
-        mTransactionManager.addTransaction(new SerialTransaction(subTransactions), outcomeReceiver);
+        mTransactionManager.addTransaction(new SerialTransaction(subTransactions, mLock),
+                outcomeReceiver);
         assertEquals(VoipCallTransactionResult.RESULT_SUCCEED,
                 resultFuture.get(5000L, TimeUnit.MILLISECONDS).getResult());
         assertEquals(expectedLog, mLog.toString());
@@ -158,7 +161,7 @@
                         exceptionFuture.complete(e.getMessage());
                     }
                 };
-        mTransactionManager.addTransaction(new SerialTransaction(subTransactions),
+        mTransactionManager.addTransaction(new SerialTransaction(subTransactions, mLock),
                 outcomeReceiver);
         exceptionFuture.get(5000L, TimeUnit.MILLISECONDS);
         String expectedLog = "t1 success;\nt2 failed;\n";
@@ -182,7 +185,7 @@
         CompletableFuture<VoipCallTransactionResult> resultFuture = new CompletableFuture<>();
         OutcomeReceiver<VoipCallTransactionResult, CallException> outcomeReceiver =
                 resultFuture::complete;
-        mTransactionManager.addTransaction(new ParallelTransaction(subTransactions),
+        mTransactionManager.addTransaction(new ParallelTransaction(subTransactions, mLock),
                 outcomeReceiver);
         assertEquals(VoipCallTransactionResult.RESULT_SUCCEED,
                 resultFuture.get(5000L, TimeUnit.MILLISECONDS).getResult());
@@ -208,7 +211,7 @@
         subTransactions.add(t3);
         CompletableFuture<String> exceptionFuture = new CompletableFuture<>();
         OutcomeReceiver<VoipCallTransactionResult, CallException> outcomeReceiver =
-                new OutcomeReceiver<VoipCallTransactionResult, CallException>() {
+                new OutcomeReceiver<>() {
             @Override
             public void onResult(VoipCallTransactionResult result) {
 
@@ -219,7 +222,7 @@
                 exceptionFuture.complete(e.getMessage());
             }
         };
-        mTransactionManager.addTransaction(new ParallelTransaction(subTransactions),
+        mTransactionManager.addTransaction(new ParallelTransaction(subTransactions, mLock),
                 outcomeReceiver);
         exceptionFuture.get(5000L, TimeUnit.MILLISECONDS);
         assertTrue(mLog.toString().contains("t2 failed;\n"));
@@ -231,12 +234,20 @@
             throws ExecutionException, InterruptedException, TimeoutException {
         VoipCallTransaction t = new TestVoipCallTransaction("t", 10000L,
                 TestVoipCallTransaction.SUCCESS);
-        CompletableFuture<VoipCallTransactionResult> resultFuture = new CompletableFuture<>();
+        CompletableFuture<String> exceptionFuture = new CompletableFuture<>();
         OutcomeReceiver<VoipCallTransactionResult, CallException> outcomeReceiver =
-                resultFuture::complete;
-        mTransactionManager.addTransaction(t, outcomeReceiver);
-        VoipCallTransactionResult result = resultFuture.get(7000L, TimeUnit.MILLISECONDS);
-        assertEquals(VoipCallTransactionResult.RESULT_FAILED, result.getResult());
-        assertTrue(result.getMessage().contains("timeout"));
+                new OutcomeReceiver<>() {
+                    @Override
+                    public void onResult(VoipCallTransactionResult result) {
+
+                    }
+
+                    @Override
+                    public void onError(CallException e) {
+                        exceptionFuture.complete(e.getMessage());
+                    }
+                };        mTransactionManager.addTransaction(t, outcomeReceiver);
+        String message = exceptionFuture.get(7000L, TimeUnit.MILLISECONDS);
+        assertTrue(message.contains("timeout"));
     }
 }