Merge "Remove service entry from Telecom manifest." into main
diff --git a/flags/Android.bp b/flags/Android.bp
index 99e2dc5..af8b683 100644
--- a/flags/Android.bp
+++ b/flags/Android.bp
@@ -29,8 +29,12 @@
       "telecom_incallservice_flags.aconfig",
       "telecom_default_phone_account_flags.aconfig",
       "telecom_callaudioroutestatemachine_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_resolve_hidden_dependencies.aconfig",
+      "telecom_bluetoothroutemanager_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
index 4dbc03b..74cb447 100644
--- a/flags/telecom_api_flags.aconfig
+++ b/flags/telecom_api_flags.aconfig
@@ -12,4 +12,18 @@
   namespace: "telecom"
   description: "When set, call details/extras id updates to Telecom APIs for Android V are active."
   bug: "301713560"
-}
\ No newline at end of file
+}
+
+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_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
index c231551..ab7ec88 100644
--- a/flags/telecom_callaudioroutestatemachine_flags.aconfig
+++ b/flags/telecom_callaudioroutestatemachine_flags.aconfig
@@ -5,4 +5,46 @@
   namespace: "telecom"
   description: "Fix supported routes wrongly include bluetooth issue."
   bug: "292599751"
-}
\ No newline at end of file
+}
+
+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"
+}
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_incallservice_flags.aconfig b/flags/telecom_incallservice_flags.aconfig
index d70b9cc..e1a652b 100644
--- a/flags/telecom_incallservice_flags.aconfig
+++ b/flags/telecom_incallservice_flags.aconfig
@@ -5,4 +5,11 @@
   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/res/values-kk/strings.xml b/res/values-kk/strings.xml
index 628b440..399da20 100644
--- a/res/values-kk/strings.xml
+++ b/res/values-kk/strings.xml
@@ -69,7 +69,7 @@
     <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>
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index d095522..6e4dc3c 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -17,7 +17,7 @@
 package com.android.server.telecom;
 
 import static android.provider.CallLog.Calls.MISSED_REASON_NOT_MISSED;
-import static android.telephony.TelephonyManager.EVENT_DISPLAY_SOS_MESSAGE;
+import static android.telephony.TelephonyManager.EVENT_DISPLAY_EMERGENCY_MESSAGE;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -306,14 +306,20 @@
                 @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);
+                        }
                     }
                 }
             };
@@ -4236,8 +4242,8 @@
                 l.onReceivedCallQualityReport(this, callQuality);
             }
         } else {
-            if (event.equals(EVENT_DISPLAY_SOS_MESSAGE) && !isEmergencyCall()) {
-                Log.w(this, "onConnectionEvent: EVENT_DISPLAY_SOS_MESSAGE is sent "
+            if (event.equals(EVENT_DISPLAY_EMERGENCY_MESSAGE) && !isEmergencyCall()) {
+                Log.w(this, "onConnectionEvent: EVENT_DISPLAY_EMERGENCY_MESSAGE is sent "
                         + "without an emergency call");
                 return;
             }
diff --git a/src/com/android/server/telecom/CallAudioCommunicationDeviceTracker.java b/src/com/android/server/telecom/CallAudioCommunicationDeviceTracker.java
index 43624a3..5fc2414 100644
--- a/src/com/android/server/telecom/CallAudioCommunicationDeviceTracker.java
+++ b/src/com/android/server/telecom/CallAudioCommunicationDeviceTracker.java
@@ -24,10 +24,12 @@
 
 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
@@ -50,6 +52,7 @@
     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);
@@ -63,6 +66,10 @@
         return mAudioDeviceType == audioDeviceType;
     }
 
+    public int getCurrentLocallyRequestedCommunicationDevice() {
+       return mAudioDeviceType;
+    }
+
     @VisibleForTesting
     public void setTestCommunicationDevice(int audioDeviceType) {
         mAudioDeviceType = audioDeviceType;
@@ -90,6 +97,9 @@
      */
     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",
@@ -149,8 +159,16 @@
                 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;
     }
 
@@ -160,6 +178,9 @@
      * @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",
@@ -177,12 +198,6 @@
             return;
         }
 
-        if (isBtDevice && mBtAudioDevice != null) {
-            // Signal that BT audio was lost for device.
-            mBluetoothRouteManager.onAudioLost(mBtAudioDevice);
-            mBtAudioDevice = null;
-        }
-
         if (mAudioManager == null) {
             Log.i(this, "clearCommunicationDevice: mAudioManager is null");
             return;
@@ -191,6 +206,15 @@
         // 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) {
diff --git a/src/com/android/server/telecom/CallAudioManager.java b/src/com/android/server/telecom/CallAudioManager.java
index 6557dc6..2201c2f 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,6 +30,7 @@
 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;
@@ -54,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;
@@ -62,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;
@@ -69,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);
@@ -94,7 +96,7 @@
             put(CallState.AUDIO_PROCESSING, mAudioProcessingCalls);
         }};
 
-        mCallAudioRouteStateMachine = callAudioRouteStateMachine;
+        mCallAudioRouteAdapter = callAudioRouteAdapter;
         mCallAudioModeStateMachine = callAudioModeStateMachine;
         mCallsManager = callsManager;
         mPlayerFactory = playerFactory;
@@ -102,10 +104,11 @@
         mRingbackPlayer = ringbackPlayer;
         mBluetoothStateReceiver = bluetoothStateReceiver;
         mDtmfLocalTonePlayer = dtmfLocalTonePlayer;
+        mFeatureFlags = featureFlags;
 
         mPlayerFactory.setCallAudioManager(this);
         mCallAudioModeStateMachine.setCallAudioManager(this);
-        mCallAudioRouteStateMachine.setCallAudioManager(this);
+        mCallAudioRouteAdapter.setCallAudioManager(this);
     }
 
     @Override
@@ -222,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);
             }
         }
@@ -374,7 +377,7 @@
     @Override
     public void onConnectionServiceChanged(Call call, ConnectionServiceWrapper oldCs,
             ConnectionServiceWrapper newCs) {
-        mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+        mCallAudioRouteAdapter.sendMessageWithSessionInfo(
                 CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE);
     }
 
@@ -392,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() {
@@ -419,7 +422,7 @@
             Log.v(this, "ignoring toggleMute for emergency call");
             return;
         }
-        mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+        mCallAudioRouteAdapter.sendMessageWithSessionInfo(
                 CallAudioRouteStateMachine.TOGGLE_MUTE);
     }
 
@@ -439,7 +442,7 @@
             Log.v(this, "ignoring mute for emergency call");
         }
 
-        mCallAudioRouteStateMachine.sendMessageWithSessionInfo(shouldMute
+        mCallAudioRouteAdapter.sendMessageWithSessionInfo(shouldMute
                 ? CallAudioRouteStateMachine.MUTE_ON : CallAudioRouteStateMachine.MUTE_OFF);
     }
 
@@ -455,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;
@@ -486,7 +489,7 @@
      */
     void switchBaseline() {
         Log.i(this, "switchBaseline");
-        mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+        mCallAudioRouteAdapter.sendMessageWithSessionInfo(
                 CallAudioRouteStateMachine.USER_SWITCH_BASELINE_ROUTE,
                 CallAudioRouteStateMachine.INCLUDE_BLUETOOTH_IN_BASELINE);
     }
@@ -530,7 +533,7 @@
         synchronized (mCallsManager.getLock()) {
             Call localForegroundCall = mForegroundCall;
             boolean result = mRinger.startRinging(localForegroundCall,
-                    mCallAudioRouteStateMachine.isHfpDeviceAvailable());
+                    mCallAudioRouteAdapter.isHfpDeviceAvailable());
             if (result) {
                 localForegroundCall.setStartRingTime();
             }
@@ -563,7 +566,7 @@
 
     @VisibleForTesting
     public void setCallAudioRouteFocusState(int focusState) {
-        mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+        mCallAudioRouteAdapter.sendMessageWithSessionInfo(
                 CallAudioRouteStateMachine.SWITCH_FOCUS, focusState);
     }
 
@@ -573,8 +576,8 @@
     }
 
     @VisibleForTesting
-    public CallAudioRouteStateMachine getCallAudioRouteStateMachine() {
-        return mCallAudioRouteStateMachine;
+    public CallAudioRouteAdapter getCallAudioRouteAdapter() {
+        return mCallAudioRouteAdapter;
     }
 
     @VisibleForTesting
@@ -611,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:");
@@ -772,20 +775,26 @@
                     possibleConnectingCall = call;
                 }
             }
-            // Prefer a connecting call
-            if (possibleConnectingCall != null) {
-                mForegroundCall = 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 {
-                // 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());
+                // Legacy (buggy) behavior.
+                mForegroundCall = possibleConnectingCall == null ?
+                        mActiveDialingOrConnectingCalls.iterator().next() : possibleConnectingCall;
             }
         } else if (mRingingCalls.size() > 0) {
             mForegroundCall = mRingingCalls.iterator().next();
@@ -803,10 +812,11 @@
                 mHoldingCalls.stream().map(c -> c.getId()).collect(Collectors.joining(","))
         );
         if (mForegroundCall != oldForegroundCall) {
-            mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+            mCallAudioRouteAdapter.sendMessageWithSessionInfo(
                     CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE);
 
-            if (mForegroundCall != null) {
+            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,
diff --git a/src/com/android/server/telecom/CallAudioModeStateMachine.java b/src/com/android/server/telecom/CallAudioModeStateMachine.java
index 6831770..71956a1 100644
--- a/src/com/android/server/telecom/CallAudioModeStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioModeStateMachine.java
@@ -41,8 +41,10 @@
     private LocalLog mLocalLog = new LocalLog(20);
     public static class Factory {
         public CallAudioModeStateMachine create(SystemStateHelper systemStateHelper,
-                                                AudioManager am, FeatureFlags featureFlags) {
-            return new CallAudioModeStateMachine(systemStateHelper, am, featureFlags);
+                AudioManager am, FeatureFlags featureFlags,
+                CallAudioCommunicationDeviceTracker callAudioCommunicationDeviceTracker) {
+            return new CallAudioModeStateMachine(systemStateHelper, am,
+                    featureFlags, callAudioCommunicationDeviceTracker);
         }
     }
 
@@ -277,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
@@ -720,12 +734,12 @@
             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);
         }
 
@@ -871,17 +885,20 @@
     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, FeatureFlags featureFlags) {
+            AudioManager audioManager, FeatureFlags featureFlags,
+            CallAudioCommunicationDeviceTracker callAudioCommunicationDeviceTracker) {
         super(CallAudioModeStateMachine.class.getSimpleName());
         mAudioManager = audioManager;
         mSystemStateHelper = systemStateHelper;
         mMostRecentMode = AudioManager.MODE_NORMAL;
         mFeatureFlags = featureFlags;
+        mCommunicationDeviceTracker = callAudioCommunicationDeviceTracker;
 
         createStates();
     }
@@ -890,12 +907,14 @@
      * Used for testing
      */
     public CallAudioModeStateMachine(SystemStateHelper systemStateHelper,
-            AudioManager audioManager, Looper looper, FeatureFlags featureFlags) {
+            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 af0757c..8a87c22 100644
--- a/src/com/android/server/telecom/CallAudioRoutePeripheralAdapter.java
+++ b/src/com/android/server/telecom/CallAudioRoutePeripheralAdapter.java
@@ -25,17 +25,17 @@
 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,
             AsyncRingtonePlayer ringtonePlayer) {
-        mCallAudioRouteStateMachine = callAudioRouteStateMachine;
+        mCallAudioAdapter = callAudioRouteAdapter;
         mBluetoothRouteManager = bluetoothManager;
         mRingtonePlayer = ringtonePlayer;
 
@@ -60,26 +60,26 @@
 
     @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() {
         mRingtonePlayer.updateBtActiveState(true);
-        mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+        mCallAudioAdapter.sendMessageWithSessionInfo(
                 CallAudioRouteStateMachine.BT_AUDIO_CONNECTED);
     }
 
@@ -87,20 +87,20 @@
     public void onBluetoothAudioConnecting() {
         mRingtonePlayer.updateBtActiveState(false);
         // Pretend like audio is connected when communicating w/ CARSM.
-        mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+        mCallAudioAdapter.sendMessageWithSessionInfo(
                 CallAudioRouteStateMachine.BT_AUDIO_CONNECTED);
     }
 
     @Override
     public void onBluetoothAudioDisconnected() {
         mRingtonePlayer.updateBtActiveState(false);
-        mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+        mCallAudioAdapter.sendMessageWithSessionInfo(
                 CallAudioRouteStateMachine.BT_AUDIO_DISCONNECTED);
     }
 
     @Override
     public void onUnexpectedBluetoothStateChange() {
-        mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
+        mCallAudioAdapter.sendMessageWithSessionInfo(
                 CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE);
     }
 
@@ -111,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 217b553..cc5ee05 100644
--- a/src/com/android/server/telecom/CallAudioRouteStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
@@ -17,18 +17,19 @@
 package com.android.server.telecom;
 
 
+import android.annotation.FlaggedApi;
 import android.app.ActivityManager;
 import android.bluetooth.BluetoothDevice;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
 import android.media.AudioDeviceInfo;
 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;
@@ -45,6 +46,7 @@
 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;
@@ -73,7 +75,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(
@@ -85,7 +87,8 @@
                 CallAudioManager.AudioServiceFactory audioServiceFactory,
                 int earpieceControl,
                 Executor asyncTaskExecutor,
-                CallAudioCommunicationDeviceTracker communicationDeviceTracker) {
+                CallAudioCommunicationDeviceTracker communicationDeviceTracker,
+                FeatureFlags featureFlags) {
             return new CallAudioRouteStateMachine(context,
                     callsManager,
                     bluetoothManager,
@@ -94,7 +97,8 @@
                     audioServiceFactory,
                     earpieceControl,
                     asyncTaskExecutor,
-                    communicationDeviceTracker);
+                    communicationDeviceTracker,
+                    featureFlags);
         }
     }
     /** Values for CallAudioRouteStateMachine constructor's earPieceRouting arg. */
@@ -374,8 +378,10 @@
         public void enter() {
             super.enter();
             setSpeakerphoneOn(false);
-            mCommunicationDeviceTracker.setCommunicationDevice(
-                    AudioDeviceInfo.TYPE_BUILTIN_EARPIECE, null);
+            if (mFeatureFlags.callAudioCommunicationDeviceRefactor()) {
+                mCommunicationDeviceTracker.setCommunicationDevice(
+                        AudioDeviceInfo.TYPE_BUILTIN_EARPIECE, null);
+            }
             CallAudioState newState = new CallAudioState(mIsMuted, ROUTE_EARPIECE,
                     mAvailableRoutes, null,
                     mBluetoothRouteManager.getConnectedDevices());
@@ -406,8 +412,10 @@
                 case SWITCH_BLUETOOTH:
                 case USER_SWITCH_BLUETOOTH:
                     if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0) {
-                        mCommunicationDeviceTracker.clearCommunicationDevice(
-                                AudioDeviceInfo.TYPE_BUILTIN_EARPIECE);
+                        if (mFeatureFlags.callAudioCommunicationDeviceRefactor()) {
+                            mCommunicationDeviceTracker.clearCommunicationDevice(
+                                    AudioDeviceInfo.TYPE_BUILTIN_EARPIECE);
+                        }
                         if (mAudioFocusType == ACTIVE_FOCUS
                                 || mBluetoothRouteManager.isInbandRingingEnabled()) {
                             String address = (msg.obj instanceof SomeArgs) ?
@@ -424,8 +432,10 @@
                 case SWITCH_HEADSET:
                 case USER_SWITCH_HEADSET:
                     if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) {
-                        mCommunicationDeviceTracker.clearCommunicationDevice(
-                                AudioDeviceInfo.TYPE_BUILTIN_EARPIECE);
+                        if (mFeatureFlags.callAudioCommunicationDeviceRefactor()) {
+                            mCommunicationDeviceTracker.clearCommunicationDevice(
+                                    AudioDeviceInfo.TYPE_BUILTIN_EARPIECE);
+                        }
                         transitionTo(mActiveHeadsetRoute);
                     } else {
                         Log.w(this, "Ignoring switch to headset command. Not available.");
@@ -435,8 +445,10 @@
                     // fall through; we want to switch to speaker mode when docked and in a call.
                 case SWITCH_SPEAKER:
                 case USER_SWITCH_SPEAKER:
-                    mCommunicationDeviceTracker.clearCommunicationDevice(
-                            AudioDeviceInfo.TYPE_BUILTIN_EARPIECE);
+                    if (mFeatureFlags.callAudioCommunicationDeviceRefactor()) {
+                        mCommunicationDeviceTracker.clearCommunicationDevice(
+                                AudioDeviceInfo.TYPE_BUILTIN_EARPIECE);
+                    }
                     setSpeakerphoneOn(true);
                     // fall through
                 case SPEAKER_ON:
@@ -590,8 +602,10 @@
         public void enter() {
             super.enter();
             setSpeakerphoneOn(false);
-            mCommunicationDeviceTracker.setCommunicationDevice(
-                    AudioDeviceInfo.TYPE_WIRED_HEADSET, null);
+            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);
@@ -613,8 +627,10 @@
                 case SWITCH_EARPIECE:
                 case USER_SWITCH_EARPIECE:
                     if ((mAvailableRoutes & ROUTE_EARPIECE) != 0) {
-                        mCommunicationDeviceTracker.clearCommunicationDevice(
-                                AudioDeviceInfo.TYPE_WIRED_HEADSET);
+                        if (mFeatureFlags.callAudioCommunicationDeviceRefactor()) {
+                            mCommunicationDeviceTracker.clearCommunicationDevice(
+                                    AudioDeviceInfo.TYPE_WIRED_HEADSET);
+                        }
                         transitionTo(mActiveEarpieceRoute);
                     } else {
                         Log.w(this, "Ignoring switch to earpiece command. Not available.");
@@ -630,8 +646,10 @@
                                 || mBluetoothRouteManager.isInbandRingingEnabled()) {
                             String address = (msg.obj instanceof SomeArgs) ?
                                     (String) ((SomeArgs) msg.obj).arg2 : null;
-                            mCommunicationDeviceTracker.clearCommunicationDevice(
-                                    AudioDeviceInfo.TYPE_WIRED_HEADSET);
+                            if (mFeatureFlags.callAudioCommunicationDeviceRefactor()) {
+                                mCommunicationDeviceTracker.clearCommunicationDevice(
+                                        AudioDeviceInfo.TYPE_WIRED_HEADSET);
+                            }
                             // Omit transition to ActiveBluetoothRoute until actual connection.
                             setBluetoothOn(address);
                         } else {
@@ -648,8 +666,10 @@
                     return HANDLED;
                 case SWITCH_SPEAKER:
                 case USER_SWITCH_SPEAKER:
-                    mCommunicationDeviceTracker.clearCommunicationDevice(
-                            AudioDeviceInfo.TYPE_WIRED_HEADSET);
+                    if (mFeatureFlags.callAudioCommunicationDeviceRefactor()) {
+                        mCommunicationDeviceTracker.clearCommunicationDevice(
+                                AudioDeviceInfo.TYPE_WIRED_HEADSET);
+                    }
                     setSpeakerphoneOn(true);
                     // fall through
                 case SPEAKER_ON:
@@ -817,7 +837,9 @@
             // 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.
-            setBluetoothOn(null);
+            if (mFeatureFlags.callAudioCommunicationDeviceRefactor()) {
+                setBluetoothOn(null);
+            }
             CallAudioState newState = new CallAudioState(mIsMuted, ROUTE_BLUETOOTH,
                     mAvailableRoutes, mBluetoothRouteManager.getBluetoothAudioConnectedDevice(),
                     mBluetoothRouteManager.getConnectedDevices());
@@ -918,8 +940,13 @@
                 case SWITCH_FOCUS:
                     if (msg.arg1 == NO_FOCUS) {
                         // Only disconnect audio here instead of routing away from BT entirely.
-                        mBluetoothRouteManager.disconnectAudio();
-                        transitionTo(mQuiescentBluetoothRoute);
+                        if (mFeatureFlags.transitRouteBeforeAudioDisconnectBt()) {
+                            transitionTo(mQuiescentBluetoothRoute);
+                            mBluetoothRouteManager.disconnectAudio();
+                        } else {
+                            mBluetoothRouteManager.disconnectAudio();
+                            transitionTo(mQuiescentBluetoothRoute);
+                        }
                         mCallAudioManager.notifyAudioOperationsComplete();
                     } else if (msg.arg1 == RINGING_FOCUS
                             && !mBluetoothRouteManager.isInbandRingingEnabled()) {
@@ -1092,7 +1119,11 @@
                     if (msg.arg1 == ACTIVE_FOCUS) {
                         // It is possible that the connection to BT will fail while in-call, in
                         // which case, we want to transition into the active route.
-                        transitionTo(mActiveBluetoothRoute);
+                        if (mFeatureFlags.callAudioCommunicationDeviceRefactor()) {
+                            transitionTo(mActiveBluetoothRoute);
+                        } else {
+                            setBluetoothOn(null);
+                        }
                     } else if (msg.arg1 == RINGING_FOCUS) {
                         if (mBluetoothRouteManager.isInbandRingingEnabled()) {
                             setBluetoothOn(null);
@@ -1548,6 +1579,7 @@
 
     private CallAudioManager mCallAudioManager;
     private CallAudioCommunicationDeviceTracker mCommunicationDeviceTracker;
+    private FeatureFlags mFeatureFlags;
 
     public CallAudioRouteStateMachine(
             Context context,
@@ -1558,7 +1590,8 @@
             CallAudioManager.AudioServiceFactory audioServiceFactory,
             int earpieceControl,
             Executor asyncTaskExecutor,
-            CallAudioCommunicationDeviceTracker communicationDeviceTracker) {
+            CallAudioCommunicationDeviceTracker communicationDeviceTracker,
+            FeatureFlags featureFlags) {
         super(NAME);
         mContext = context;
         mCallsManager = callsManager;
@@ -1570,6 +1603,7 @@
         mLock = callsManager.getLock();
         mAsyncTaskExecutor = asyncTaskExecutor;
         mCommunicationDeviceTracker = communicationDeviceTracker;
+        mFeatureFlags = featureFlags;
         createStates(earpieceControl);
     }
 
@@ -1582,7 +1616,8 @@
             StatusBarNotifier statusBarNotifier,
             CallAudioManager.AudioServiceFactory audioServiceFactory,
             int earpieceControl, Looper looper, Executor asyncTaskExecutor,
-            CallAudioCommunicationDeviceTracker communicationDeviceTracker) {
+            CallAudioCommunicationDeviceTracker communicationDeviceTracker,
+            FeatureFlags featureFlags) {
         super(NAME, looper);
         mContext = context;
         mCallsManager = callsManager;
@@ -1594,7 +1629,7 @@
         mLock = callsManager.getLock();
         mAsyncTaskExecutor = asyncTaskExecutor;
         mCommunicationDeviceTracker = communicationDeviceTracker;
-
+        mFeatureFlags = featureFlags;
         createStates(earpieceControl);
     }
 
@@ -1711,6 +1746,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.
@@ -1740,13 +1780,19 @@
                 }
                 return;
             case UPDATE_SYSTEM_AUDIO_ROUTE:
-                // 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);
+                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;
@@ -1771,7 +1817,7 @@
     }
 
     public void dumpPendingMessages(IndentingPrintWriter pw) {
-        getHandler().getLooper().dump(pw::println, "");
+        getAdapterHandler().getLooper().dump(pw::println, "");
     }
 
     public boolean isHfpDeviceAvailable() {
@@ -1784,12 +1830,16 @@
         // 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 (on) {
-            speakerOn = mCommunicationDeviceTracker.setCommunicationDevice(
-                    AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, null);
+        if (mFeatureFlags.callAudioCommunicationDeviceRefactor()) {
+            if (on) {
+                speakerOn = mCommunicationDeviceTracker.setCommunicationDevice(
+                        AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, null);
+            } else {
+                mCommunicationDeviceTracker.clearCommunicationDevice(
+                        AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
+            }
         } else {
-            mCommunicationDeviceTracker.clearCommunicationDevice(
-                    AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
+            processLegacySpeakerCommunicationDevice(on);
         }
         mStatusBarNotifier.notifySpeakerphone(hasAnyCalls && speakerOn);
     }
@@ -2011,6 +2061,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;
@@ -2023,7 +2125,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;
@@ -2079,4 +2181,8 @@
 
         return base;
     }
+
+    public Handler getAdapterHandler() {
+        return getHandler();
+    }
 }
diff --git a/src/com/android/server/telecom/CallLogManager.java b/src/com/android/server/telecom/CallLogManager.java
index fcb7778..cfa2eb4 100644
--- a/src/com/android/server/telecom/CallLogManager.java
+++ b/src/com/android/server/telecom/CallLogManager.java
@@ -263,8 +263,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 {
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 0e78ca1..f85a809 100755
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -606,23 +606,28 @@
 
         mDtmfLocalTonePlayer =
                 new DtmfLocalTonePlayer(new DtmfLocalTonePlayer.ToneGeneratorProxy());
-        CallAudioRouteStateMachine callAudioRouteStateMachine =
-                callAudioRouteStateMachineFactory.create(
-                        context,
-                        this,
-                        bluetoothManager,
-                        wiredHeadsetManager,
-                        statusBarNotifier,
-                        audioServiceFactory,
-                        CallAudioRouteStateMachine.EARPIECE_AUTO_DETECT,
-                        asyncCallAudioTaskExecutor,
-                        communicationDeviceTracker
-                );
-        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,
@@ -653,11 +658,12 @@
                 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), featureFlags),
+                (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);
@@ -679,10 +685,15 @@
         mCallStreamingNotification = callStreamingNotification;
         mFeatureFlags = featureFlags;
 
-        mListeners.add(mInCallController);
+        if (mFeatureFlags.useImprovedListenerOrder()) {
+            mListeners.add(mInCallController);
+        }
         mListeners.add(mInCallWakeLockController);
         mListeners.add(statusBarNotifier);
         mListeners.add(mCallLogManager);
+        if (!mFeatureFlags.useImprovedListenerOrder()) {
+            mListeners.add(mInCallController);
+        }
         mListeners.add(mCallEndpointController);
         mListeners.add(mCallDiagnosticServiceController);
         mListeners.add(mCallAudioManager);
@@ -761,6 +772,10 @@
         call.setPostCallPackageName(getRoleManagerAdapter().getDefaultCallScreeningApp(
                 call.getAssociatedUser()));
 
+        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.
@@ -772,11 +787,16 @@
             listener.onConnectionServiceChanged(call, null, call.getConnectionService());
         }
 
-        // 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 {
+        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);
         }
     }
@@ -1020,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);
             }
         }
     }
@@ -2940,6 +2960,10 @@
             // 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.
             Bundle bundle = new Bundle();
             bundle.putLong(TelecomManager.EXTRA_CALL_ANSWERED_TIME_MILLIS,
@@ -5507,7 +5531,7 @@
         mCallAudioManager.getCallAudioModeStateMachine().getHandler().post(() -> {
             mainHandlerLatch.countDown();
         });
-        mCallAudioManager.getCallAudioRouteStateMachine().getHandler().post(() -> {
+        mCallAudioManager.getCallAudioRouteAdapter().getAdapterHandler().post(() -> {
             mainHandlerLatch.countDown();
         });
 
diff --git a/src/com/android/server/telecom/ConnectionServiceFocusManager.java b/src/com/android/server/telecom/ConnectionServiceFocusManager.java
index 72cb7c4..35be0f8 100644
--- a/src/com/android/server/telecom/ConnectionServiceFocusManager.java
+++ b/src/com/android/server/telecom/ConnectionServiceFocusManager.java
@@ -30,6 +30,7 @@
 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;
@@ -340,18 +341,23 @@
             if (syncCallFocus != null) {
                 return syncCallFocus.orElse(null);
             } else {
-                Log.w(TAG, "Timed out waiting for synchronous current focus. Returning possibly"
-                        + " inaccurate result. returning currentFocusCall=[%s]", mCurrentFocusCall);
+                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");
+                    // 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);
-
+                    // 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) {
diff --git a/src/com/android/server/telecom/ConnectionServiceWrapper.java b/src/com/android/server/telecom/ConnectionServiceWrapper.java
index a43000a..07b048d 100644
--- a/src/com/android/server/telecom/ConnectionServiceWrapper.java
+++ b/src/com/android/server/telecom/ConnectionServiceWrapper.java
@@ -33,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;
@@ -65,6 +64,7 @@
 import com.android.internal.telecom.IVideoProvider;
 import com.android.internal.telecom.RemoteServiceCallback;
 import com.android.internal.util.Preconditions;
+import com.android.server.telecom.flags.Flags;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -72,13 +72,14 @@
 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
@@ -91,9 +92,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 {
 
@@ -521,8 +525,6 @@
                         .validateAccountIconUserBoundary(icon, callingUserHandle));
             }
 
-            if (ConnectionServiceWrapper.this.mIsRemoteConnectionService) return;
-
             if (parcelableConference.getConnectElapsedTimeMillis() != 0
                     && mContext.checkCallingOrSelfPermission(MODIFY_PHONE_STATE)
                             != PackageManager.PERMISSION_GRANTED) {
@@ -937,9 +939,6 @@
         public void addExistingConnection(String callId, ParcelableConnection connection,
                 Session.Info sessionInfo) {
             Log.startSession(sessionInfo, "CSW.aEC", mPackageAbbreviation);
-
-            if (ConnectionServiceWrapper.this.mIsRemoteConnectionService) return;
-
             UserHandle userHandle = Binder.getCallingUserHandle();
             // Check that the Calling Package matches PhoneAccountHandle's Component Package
             PhoneAccountHandle callingPhoneAccountHandle = connection.getPhoneAccount();
@@ -1354,7 +1353,6 @@
     private final CallsManager mCallsManager;
     private final AppOpsManager mAppOpsManager;
     private final Context mContext;
-    public boolean mIsRemoteConnectionService = false;
 
     private ConnectionServiceFocusManager.ConnectionServiceFocusListener mConnSvrFocusListener;
 
@@ -1600,7 +1598,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(),
@@ -1609,7 +1622,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(
@@ -1642,6 +1654,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;
@@ -1703,6 +1716,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(),
@@ -1711,7 +1741,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(
@@ -2160,7 +2189,8 @@
         }
     }
 
-    void addCall(Call call) {
+    @VisibleForTesting
+    public void addCall(Call call) {
         if (mCallIdMapper.getCallId(call) == null) {
             mCallIdMapper.addCall(call);
         }
@@ -2512,13 +2542,13 @@
     private void logIncoming(String msg, Object... params) {
         // Keep these as debug; the incoming logging is traced on a package level through the
         // session logging.
-        Log.d(this, "CS -> TC[" + Log.getPackageAbbreviation(mComponentName) + "]:"
-                + " isRCS = " + this.mIsRemoteConnectionService + ": " + msg, params);
+        Log.d(this, "CS -> TC[" + Log.getPackageAbbreviation(mComponentName) + "]: "
+                + msg, params);
     }
 
     private void logOutgoing(String msg, Object... params) {
-        Log.d(this, "TC -> CS[" + Log.getPackageAbbreviation(mComponentName) + "]:"
-                + " isRCS = " + this.mIsRemoteConnectionService + ": " + msg, params);
+        Log.d(this, "TC -> CS[" + Log.getPackageAbbreviation(mComponentName) + "]: "
+                + msg, params);
     }
 
     private void queryRemoteConnectionServices(final UserHandle userHandle,
@@ -2545,7 +2575,6 @@
             ConnectionServiceWrapper service = mConnectionServiceRepository.getService(
                     handle.getComponentName(), handle.getUserHandle());
             if (service != null && service != this) {
-                service.mIsRemoteConnectionService = true;
                 simServices.add(service);
             } else {
                 // This is unexpected, normally PhoneAccounts with CAPABILITY_CALL_PROVIDER are not
@@ -2629,4 +2658,9 @@
         sb.append("]");
         return sb.toString();
     }
+
+    @VisibleForTesting
+    public void setScheduledExecutorService(ScheduledExecutorService service) {
+        mScheduledExecutor = service;
+    }
 }
diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java
index ca264c1..184eadd 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;
@@ -47,7 +49,6 @@
 import android.os.Trace;
 import android.os.UserHandle;
 import android.os.UserManager;
-import android.permission.PermissionManager;
 import android.telecom.CallAudioState;
 import android.telecom.CallEndpoint;
 import android.telecom.ConnectionService;
@@ -66,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;
@@ -1237,11 +1239,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);
@@ -1258,6 +1261,7 @@
         IntentFilter userAddedFilter = new IntentFilter(Intent.ACTION_USER_ADDED);
         userAddedFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
         mContext.registerReceiver(mUserAddedReceiver, userAddedFilter);
+        mFeatureFlags = featureFlags;
     }
 
     private void restrictPhoneCallOps() {
@@ -1690,6 +1694,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 {
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/TelecomSystem.java b/src/com/android/server/telecom/TelecomSystem.java
index 57d7139..101cd2d 100644
--- a/src/com/android/server/telecom/TelecomSystem.java
+++ b/src/com/android/server/telecom/TelecomSystem.java
@@ -259,11 +259,13 @@
                     CallAudioCommunicationDeviceTracker(mContext);
             BluetoothDeviceManager bluetoothDeviceManager = new BluetoothDeviceManager(mContext,
                     mContext.getSystemService(BluetoothManager.class).getAdapter(),
-                    communicationDeviceTracker);
+                    communicationDeviceTracker, featureFlags);
             BluetoothRouteManager bluetoothRouteManager = new BluetoothRouteManager(mContext, mLock,
-                    bluetoothDeviceManager, new Timeouts.Adapter(), communicationDeviceTracker);
+                    bluetoothDeviceManager, new Timeouts.Adapter(),
+                    communicationDeviceTracker, featureFlags);
             BluetoothStateReceiver bluetoothStateReceiver = new BluetoothStateReceiver(
-                    bluetoothDeviceManager, bluetoothRouteManager, communicationDeviceTracker);
+                    bluetoothDeviceManager, bluetoothRouteManager,
+                    communicationDeviceTracker, featureFlags);
             mContext.registerReceiver(bluetoothStateReceiver, BluetoothStateReceiver.INTENT_FILTER);
             communicationDeviceTracker.setBluetoothRouteManager(bluetoothRouteManager);
 
@@ -273,7 +275,8 @@
             mMissedCallNotifier = missedCallNotifierImplFactory
                     .makeMissedCallNotifierImpl(mContext, mPhoneAccountRegistrar,
                             defaultDialerCache,
-                            deviceIdleControllerAdapter);
+                            deviceIdleControllerAdapter,
+                            featureFlags);
             DisconnectedCallNotifier.Factory disconnectedCallNotifierFactory =
                     new DisconnectedCallNotifier.Default();
 
@@ -292,7 +295,7 @@
                         EmergencyCallHelper emergencyCallHelper) {
                     return new InCallController(context, lock, callsManager, systemStateProvider,
                             defaultDialerCache, timeoutsAdapter, emergencyCallHelper,
-                            new CarModeTracker(), clockProxy);
+                            new CarModeTracker(), clockProxy, featureFlags);
                 }
             };
 
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java b/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
index e32f72c..c5e56b3 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
@@ -35,6 +35,7 @@
 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;
@@ -211,9 +212,11 @@
     private AudioManager mAudioManager;
     private Executor mExecutor;
     private CallAudioCommunicationDeviceTracker mCommunicationDeviceTracker;
+    private FeatureFlags mFeatureFlags;
 
     public BluetoothDeviceManager(Context context, BluetoothAdapter bluetoothAdapter,
-            CallAudioCommunicationDeviceTracker communicationDeviceTracker) {
+            CallAudioCommunicationDeviceTracker communicationDeviceTracker,
+            FeatureFlags featureFlags) {
         if (bluetoothAdapter != null) {
             mBluetoothAdapter = bluetoothAdapter;
             bluetoothAdapter.getProfileProxy(context, mBluetoothProfileServiceListener,
@@ -225,6 +228,7 @@
             mAudioManager = context.getSystemService(AudioManager.class);
             mExecutor = context.getMainExecutor();
             mCommunicationDeviceTracker = communicationDeviceTracker;
+            mFeatureFlags = featureFlags;
         }
     }
 
@@ -449,7 +453,17 @@
     }
 
     public void disconnectAudio() {
-        mCommunicationDeviceTracker.clearBtCommunicationDevice();
+        if (mFeatureFlags.callAudioCommunicationDeviceRefactor()) {
+            mCommunicationDeviceTracker.clearBtCommunicationDevice();
+            disconnectSco();
+        } else {
+            disconnectSco();
+            clearLeAudioOrSpeakerCommunicationDevice();
+            clearHearingAidOrSpeakerCommunicationDevice();
+        }
+    }
+
+    public void disconnectSco() {
         if (mBluetoothHeadset == null) {
             Log.w(this, "Trying to disconnect audio but no headset service exists.");
         } else {
@@ -481,9 +495,11 @@
         AudioDeviceInfo audioDeviceInfo = mAudioManager.getCommunicationDevice();
         if (audioDeviceInfo != null) {
             if (audioDeviceInfo.getType() == AudioDeviceInfo.TYPE_BLE_HEADSET) {
+                Log.i(this, "clearLeAudioCommunicationDevice: clearing le audio");
                 mBluetoothRouteManager.onAudioLost(audioDeviceInfo.getAddress());
                 mAudioManager.clearCommunicationDevice();
             } else if (audioDeviceInfo.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) {
+                Log.i(this, "clearLeAudioCommunicationDevice: clearing speaker");
                 mAudioManager.clearCommunicationDevice();
             }
         }
@@ -505,10 +521,12 @@
 
         AudioDeviceInfo audioDeviceInfo = mAudioManager.getCommunicationDevice();
         if (audioDeviceInfo != null) {
-            if (audioDeviceInfo.getType() == AudioDeviceInfo.TYPE_BLE_HEADSET) {
+            if (audioDeviceInfo.getType() == AudioDeviceInfo.TYPE_HEARING_AID) {
+                Log.i(this, "clearHearingAidCommunicationDevice: clearing hearing aid");
                 mBluetoothRouteManager.onAudioLost(audioDeviceInfo.getAddress());
                 mAudioManager.clearCommunicationDevice();
             } else if (audioDeviceInfo.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) {
+                Log.i(this, "clearHearingAidCommunicationDevice: clearing speaker");
                 mAudioManager.clearCommunicationDevice();
             }
         }
@@ -664,8 +682,10 @@
                  * 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 mCommunicationDeviceTracker.setCommunicationDevice(
-                            AudioDeviceInfo.TYPE_BLE_HEADSET, device);
+                    return mFeatureFlags.callAudioCommunicationDeviceRefactor() ?
+                            mCommunicationDeviceTracker.setCommunicationDevice(
+                                    AudioDeviceInfo.TYPE_BLE_HEADSET, device)
+                            : setLeAudioCommunicationDevice();
                 }
                 return true;
             }
@@ -676,8 +696,10 @@
                  * 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 mCommunicationDeviceTracker.setCommunicationDevice(
-                            AudioDeviceInfo.TYPE_HEARING_AID, null);
+                    return mFeatureFlags.callAudioCommunicationDeviceRefactor() ?
+                            mCommunicationDeviceTracker.setCommunicationDevice(
+                                    AudioDeviceInfo.TYPE_HEARING_AID, null)
+                            : setHearingAidCommunicationDevice();
                 }
                 return true;
             }
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java b/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
index b411b25..8124f60 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
@@ -17,6 +17,7 @@
 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;
@@ -37,12 +38,14 @@
 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.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedHashSet;
 import java.util.Map;
+import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
@@ -132,7 +135,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
@@ -250,6 +254,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 +416,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);
@@ -470,10 +500,12 @@
     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,
-            CallAudioCommunicationDeviceTracker communicationDeviceTracker) {
+            CallAudioCommunicationDeviceTracker communicationDeviceTracker,
+            FeatureFlags featureFlags) {
         super(BluetoothRouteManager.class.getSimpleName());
         mContext = context;
         mLock = lock;
@@ -481,6 +513,7 @@
         mDeviceManager.setBluetoothRouteManager(this);
         mTimeoutsAdapter = timeoutsAdapter;
         mCommunicationDeviceTracker = communicationDeviceTracker;
+        mFeatureFlags = featureFlags;
 
         mAudioOffState = new AudioOffState();
         addState(mAudioOffState);
@@ -624,14 +657,22 @@
         if (deviceType == BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO) {
             mLeAudioActiveDeviceCache = device;
             if (device == null) {
-                mCommunicationDeviceTracker.clearCommunicationDevice(
-                        AudioDeviceInfo.TYPE_BLE_HEADSET);
+                if (mFeatureFlags.callAudioCommunicationDeviceRefactor()) {
+                    mCommunicationDeviceTracker.clearCommunicationDevice(
+                            AudioDeviceInfo.TYPE_BLE_HEADSET);
+                } else {
+                    mDeviceManager.clearLeAudioOrSpeakerCommunicationDevice();
+                }
             }
         } else if (deviceType == BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID) {
             mHearingAidActiveDeviceCache = device;
             if (device == null) {
-                mCommunicationDeviceTracker.clearCommunicationDevice(
-                        AudioDeviceInfo.TYPE_HEARING_AID);
+                if (mFeatureFlags.callAudioCommunicationDeviceRefactor()) {
+                    mCommunicationDeviceTracker.clearCommunicationDevice(
+                            AudioDeviceInfo.TYPE_HEARING_AID);
+                } else {
+                    mDeviceManager.clearHearingAidOrSpeakerCommunicationDevice();
+                }
             }
         } else if (deviceType == BluetoothDeviceManager.DEVICE_TYPE_HEADSET) {
             mHfpActiveDeviceCache = device;
@@ -672,6 +713,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);
     }
@@ -701,10 +769,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;
             }
@@ -807,23 +884,37 @@
             }
         }
 
+        boolean isHearingAidSetForCommunication =
+                mFeatureFlags.callAudioCommunicationDeviceRefactor()
+                ? mCommunicationDeviceTracker.isAudioDeviceSetForType(
+                        AudioDeviceInfo.TYPE_HEARING_AID)
+                : mDeviceManager.isHearingAidSetAsCommunicationDevice();
         if (bluetoothHearingAid != null) {
-            if (mCommunicationDeviceTracker.isAudioDeviceSetForType(
-                    AudioDeviceInfo.TYPE_HEARING_AID)) {
-                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 (mCommunicationDeviceTracker.isAudioDeviceSetForType(
-                    AudioDeviceInfo.TYPE_BLE_HEADSET)) {
+            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 ec4f263..d2521ac 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
@@ -33,6 +33,7 @@
 
 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;
@@ -59,6 +60,7 @@
     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");
@@ -208,19 +210,27 @@
                     /* 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
                      */
+                    boolean isLeAudioSetForCommunication =
+                            mFeatureFlags.callAudioCommunicationDeviceRefactor()
+                                    ? mCommunicationDeviceTracker.setCommunicationDevice(
+                                    AudioDeviceInfo.TYPE_BLE_HEADSET, device)
+                                    : mBluetoothDeviceManager.setLeAudioCommunicationDevice();
                     if ((!usePreferredAudioProfile
                             || preferredDuplexProfile == BluetoothProfile.LE_AUDIO)
-                            && !mCommunicationDeviceTracker.setCommunicationDevice(
-                            AudioDeviceInfo.TYPE_BLE_HEADSET, device)) {
+                            && !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 (!mCommunicationDeviceTracker.setCommunicationDevice(
-                            AudioDeviceInfo.TYPE_HEARING_AID, null)) {
+                    if (!isHearingAidSetForCommunication) {
                         Log.w(LOG_TAG,
                                 "Device %s cannot be use as hearing aid communication device.",
                                 device);
@@ -238,10 +248,12 @@
 
     public BluetoothStateReceiver(BluetoothDeviceManager deviceManager,
             BluetoothRouteManager routeManager,
-            CallAudioCommunicationDeviceTracker communicationDeviceTracker) {
+            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/components/TelecomService.java b/src/com/android/server/telecom/components/TelecomService.java
index 9a5f2a7..9287d33 100644
--- a/src/com/android/server/telecom/components/TelecomService.java
+++ b/src/com/android/server/telecom/components/TelecomService.java
@@ -45,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;
@@ -116,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() {
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/testapps/transactionalVoipApp/res/values-ca/strings.xml b/testapps/transactionalVoipApp/res/values-ca/strings.xml
index 06f1655..5500444 100644
--- a/testapps/transactionalVoipApp/res/values-ca/strings.xml
+++ b/testapps/transactionalVoipApp/res/values-ca/strings.xml
@@ -31,7 +31,7 @@
     <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 continu"</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/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 5bef130..14065ca 100644
--- a/tests/src/com/android/server/telecom/tests/BasicCallTests.java
+++ b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
@@ -43,16 +43,12 @@
 import android.content.pm.PackageManager;
 import android.graphics.drawable.Icon;
 import android.media.AudioDeviceInfo;
-import android.media.AudioFocusRequest;
 import android.media.AudioManager;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
 import android.os.Process;
 import android.os.UserHandle;
-import android.os.UserManager;
 import android.provider.BlockedNumberContract;
 import android.telecom.Call;
 import android.telecom.CallAudioState;
@@ -650,7 +646,7 @@
 
         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).atLeast(1))
@@ -658,7 +654,7 @@
         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();
@@ -669,7 +665,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())
@@ -997,6 +993,7 @@
         call.setTargetPhoneAccount(mPhoneAccountA1.getAccountHandle());
         assert(call.isVideoCallingSupportedByPhoneAccount());
         assertEquals(VideoProfile.STATE_BIDIRECTIONAL, call.getVideoState());
+        call.setIsCreateConnectionComplete(true);
     }
 
     /**
@@ -1020,6 +1017,7 @@
         call.setTargetPhoneAccount(mPhoneAccountA2.getAccountHandle());
         assert(!call.isVideoCallingSupportedByPhoneAccount());
         assertEquals(VideoProfile.STATE_AUDIO_ONLY, call.getVideoState());
+        call.setIsCreateConnectionComplete(true);
     }
 
     /**
@@ -1197,7 +1195,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.
@@ -1206,14 +1204,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());
 
diff --git a/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java b/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java
index da3f40c..86d2bee 100644
--- a/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java
@@ -52,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;
@@ -107,7 +108,7 @@
         mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
         mCommunicationDeviceTracker = new CallAudioCommunicationDeviceTracker(mContext);
         mBluetoothDeviceManager = new BluetoothDeviceManager(mContext, mAdapter,
-                mCommunicationDeviceTracker);
+                mCommunicationDeviceTracker, mFeatureFlags);
         mBluetoothDeviceManager.setBluetoothRouteManager(mRouteManager);
         mCommunicationDeviceTracker.setBluetoothRouteManager(mRouteManager);
 
@@ -120,7 +121,7 @@
         serviceListenerUnderTest = serviceCaptor.getValue();
 
         receiverUnderTest = new BluetoothStateReceiver(mBluetoothDeviceManager,
-                mRouteManager, mCommunicationDeviceTracker);
+                mRouteManager, mCommunicationDeviceTracker, mFeatureFlags);
 
         mBluetoothDeviceManager.setHeadsetServiceForTesting(mBluetoothHeadset);
         mBluetoothDeviceManager.setHearingAidServiceForTesting(mBluetoothHearingAid);
@@ -131,6 +132,7 @@
         verify(mBluetoothLeAudio).registerCallback(any(), leAudioCallbacksTest.capture());
 
         when(mSpeakerInfo.getType()).thenReturn(TYPE_BUILTIN_SPEAKER);
+        when(mFeatureFlags.callAudioCommunicationDeviceRefactor()).thenReturn(false);
     }
 
     @Override
@@ -440,7 +442,7 @@
 
         when(mockAudioManager.getCommunicationDevice()).thenReturn(mockAudioDeviceInfo);
         mBluetoothDeviceManager.disconnectAudio();
-        verify(mockAudioManager).clearCommunicationDevice();
+        verify(mockAudioManager, atLeastOnce()).clearCommunicationDevice();
     }
 
     @SmallTest
@@ -477,7 +479,7 @@
                 BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
 
         mBluetoothDeviceManager.disconnectAudio();
-        verify(mockAudioManager).clearCommunicationDevice();
+        verify(mockAudioManager, atLeastOnce()).clearCommunicationDevice();
     }
 
     @SmallTest
@@ -516,6 +518,7 @@
     @SmallTest
     @Test
     public void testConnectMultipleLeAudioDevices() {
+        when(mFeatureFlags.callAudioCommunicationDeviceRefactor()).thenReturn(true);
         receiverUnderTest.setIsInCall(true);
         receiverUnderTest.onReceive(mContext,
                 buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device1,
@@ -563,13 +566,8 @@
     @SmallTest
     @Test
     public void testClearCommunicationDeviceOnActiveDeviceChange() {
+        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);
-//        when(mAdapter.setActiveDevice(nullable(BluetoothDevice.class),
-//                eq(BluetoothAdapter.ACTIVE_DEVICE_ALL))).thenReturn(true);
 
         List<AudioDeviceInfo> devices = new ArrayList<>();
         AudioDeviceInfo leAudioDevice1 = createMockAudioDeviceInfo(device1.getAddress(),
@@ -598,6 +596,7 @@
     @SmallTest
     @Test
     public void testConnectDualModeEarbud() {
+        when(mFeatureFlags.callAudioCommunicationDeviceRefactor()).thenReturn(true);
         receiverUnderTest.setIsInCall(true);
 
         // LE Audio earbuds connected
@@ -691,46 +690,28 @@
 
     @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);
-        List<AudioDeviceInfo> devices = new ArrayList<>();
-        devices.add(mockAudioDeviceInfo);
-
-        when(mockAudioManager.getAvailableCommunicationDevices())
-                .thenReturn(devices);
-        when(mockAudioManager.setCommunicationDevice(eq(mockAudioDeviceInfo)))
-                .thenReturn(true);
-
-        mCommunicationDeviceTracker.setCommunicationDevice(AudioDeviceInfo.TYPE_HEARING_AID, null);
-        when(mockAudioManager.getCommunicationDevice()).thenReturn(mSpeakerInfo);
-        mCommunicationDeviceTracker.clearCommunicationDevice(AudioDeviceInfo.TYPE_HEARING_AID);
-        verify(mRouteManager).onAudioLost(eq(DEVICE_ADDRESS_1));
-        assertFalse(mCommunicationDeviceTracker.isAudioDeviceSetForType(
-                AudioDeviceInfo.TYPE_HEARING_AID));
+    public void testClearHearingAidCommunicationDeviceLegacy() {
+        assertClearHearingAidOrLeCommunicationDevice(false, AudioDeviceInfo.TYPE_HEARING_AID);
     }
 
     @SmallTest
     @Test
-    public void testClearLeAudioCommunicationDevice() {
-        AudioDeviceInfo mockAudioDeviceInfo = createMockAudioDeviceInfo(DEVICE_ADDRESS_1,
-                AudioDeviceInfo.TYPE_BLE_HEADSET);
-        List<AudioDeviceInfo> devices = new ArrayList<>();
-        devices.add(mockAudioDeviceInfo);
+    public void testClearHearingAidCommunicationDeviceWithFlag() {
+        when(mFeatureFlags.callAudioCommunicationDeviceRefactor()).thenReturn(true);
+        assertClearHearingAidOrLeCommunicationDevice(true, AudioDeviceInfo.TYPE_HEARING_AID);
+    }
 
-        when(mockAudioManager.getAvailableCommunicationDevices())
-                .thenReturn(devices);
-        when(mockAudioManager.setCommunicationDevice(eq(mockAudioDeviceInfo)))
-                .thenReturn(true);
+    @SmallTest
+    @Test
+    public void testClearLeAudioCommunicationDeviceLegacy() {
+        assertClearHearingAidOrLeCommunicationDevice(false, AudioDeviceInfo.TYPE_BLE_HEADSET);
+    }
 
-        mCommunicationDeviceTracker.setCommunicationDevice(
-                AudioDeviceInfo.TYPE_BLE_HEADSET, device1);
-        when(mockAudioManager.getCommunicationDevice()).thenReturn(mSpeakerInfo);
-        mCommunicationDeviceTracker.clearCommunicationDevice(AudioDeviceInfo.TYPE_BLE_HEADSET);
-        verify(mRouteManager).onAudioLost(eq(DEVICE_ADDRESS_1));
-        assertFalse(mCommunicationDeviceTracker.isAudioDeviceSetForType(
-                AudioDeviceInfo.TYPE_BLE_HEADSET));
+    @SmallTest
+    @Test
+    public void testClearLeAudioCommunicationDeviceWithFlag() {
+        when(mFeatureFlags.callAudioCommunicationDeviceRefactor()).thenReturn(true);
+        assertClearHearingAidOrLeCommunicationDevice(true, AudioDeviceInfo.TYPE_BLE_HEADSET);
     }
 
     @SmallTest
@@ -769,6 +750,47 @@
         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.clearLeAudioOrSpeakerCommunicationDevice();
+                assertFalse(mBluetoothDeviceManager.isLeAudioCommunicationDevice());
+            } else {
+                mBluetoothDeviceManager.clearHearingAidOrSpeakerCommunicationDevice();
+                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);
diff --git a/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java b/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java
index 8e31f9c..e1ef08a 100644
--- a/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java
@@ -20,6 +20,7 @@
 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;
@@ -37,6 +38,8 @@
 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;
@@ -62,7 +65,14 @@
     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;
@@ -87,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);
@@ -113,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
@@ -176,21 +239,48 @@
     @Test
     public void testSkipInactiveBtDeviceWhenEvaluateActualState() {
         BluetoothRouteManager sm = setupStateMachine(
-                BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX, HEARING_AID_DEVICE);
-        setupConnectedDevices(null, new BluetoothDevice[]{HEARING_AID_DEVICE},
-                null, null, HEARING_AID_DEVICE, null);
+                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.getAddress());
+                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, mCommunicationDeviceTracker);
+                mTimeoutsAdapter, mCommunicationDeviceTracker, mFeatureFlags);
         sm.setListener(mListener);
         sm.setInitialStateForTesting(initialState, initialDevice);
         waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
@@ -200,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(
@@ -222,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 15a81d4..65854af 100644
--- a/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java
+++ b/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java
@@ -419,7 +419,7 @@
                 nullable(ContentResolver.class))).thenReturn(100000L);
         BluetoothRouteManager sm = new BluetoothRouteManager(mContext,
                 new TelecomSystem.SyncRoot() { }, mDeviceManager,
-                mTimeoutsAdapter, mCommunicationDeviceTracker);
+                mTimeoutsAdapter, mCommunicationDeviceTracker, mFeatureFlags);
         sm.setListener(mListener);
         sm.setInitialStateForTesting(initialState, initialDevice);
         waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioManagerTest.java b/tests/src/com/android/server/telecom/tests/CallAudioManagerTest.java
index c8ceea9..df281ca 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;
@@ -79,6 +80,8 @@
     @Mock private BluetoothStateReceiver mBluetoothStateReceiver;
     @Mock private TelecomSystem.SyncRoot mLock;
 
+    @Mock private FeatureFlags mFlags;
+
     private CallAudioManager mCallAudioManager;
 
     @Override
@@ -94,6 +97,7 @@
             return mockInCallTonePlayer;
         }).when(mPlayerFactory).createPlayer(anyInt());
         when(mCallsManager.getLock()).thenReturn(mLock);
+        when(mFlags.ensureAudioModeUpdatesOnForegroundCallChange()).thenReturn(true);
         mCallAudioManager = new CallAudioManager(
                 mCallAudioRouteStateMachine,
                 mCallsManager,
@@ -102,7 +106,8 @@
                 mRinger,
                 mRingbackPlayer,
                 mBluetoothStateReceiver,
-                mDtmfLocalTonePlayer);
+                mDtmfLocalTonePlayer,
+                mFlags);
     }
 
     @Override
@@ -278,19 +283,27 @@
         verify(mCallAudioModeStateMachine, times(2)).sendMessageWithArgs(
                 eq(CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL), captor.capture());
         assertMessageArgEquality(expectedArgs, captor.getValue());
-        // Expet another invocation due to audio mode change signal.
-        verify(mCallAudioModeStateMachine, times(3)).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(4)).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();
 
@@ -298,6 +311,12 @@
         verifyProperCleanup();
     }
 
+    @Test
+    public void testSingleOutgoingCallWithoutAudioModeUpdateOnForegroundCallChange() {
+        when(mFlags.ensureAudioModeUpdatesOnForegroundCallChange()).thenReturn(false);
+        testSingleOutgoingCall();
+    }
+
     @MediumTest
     @Test
     public void testRingbackStartStop() {
@@ -329,9 +348,14 @@
         verify(mCallAudioModeStateMachine, times(2)).sendMessageWithArgs(
                 eq(CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL), captor.capture());
         assertMessageArgEquality(expectedArgs, captor.getValue());
-        // Expect an extra time due to audio mode change signal
-        verify(mCallAudioModeStateMachine, times(3)).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));
@@ -353,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() {
@@ -708,6 +738,10 @@
     @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);
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioModeStateMachineTest.java b/tests/src/com/android/server/telecom/tests/CallAudioModeStateMachineTest.java
index 8c6d84c..cddf2ad 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioModeStateMachineTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioModeStateMachineTest.java
@@ -35,6 +35,7 @@
 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.Builder;
@@ -57,6 +58,7 @@
     @Mock private AudioManager mAudioManager;
     @Mock private CallAudioManager mCallAudioManager;
     @Mock private CallAudioRouteStateMachine mCallAudioRouteStateMachine;
+    @Mock private CallAudioCommunicationDeviceTracker mCommunicationDeviceTracker;
 
     private HandlerThread mTestThread;
 
@@ -66,7 +68,7 @@
         mTestThread = new HandlerThread("CallAudioModeStateMachineTest");
         mTestThread.start();
         super.setUp();
-        when(mCallAudioManager.getCallAudioRouteStateMachine())
+        when(mCallAudioManager.getCallAudioRouteAdapter())
                 .thenReturn(mCallAudioRouteStateMachine);
         when(mFeatureFlags.telecomResolveHiddenDependencies()).thenReturn(false);
     }
@@ -83,7 +85,7 @@
     @Test
     public void testNoFocusWhenRingerSilenced() throws Throwable {
         CallAudioModeStateMachine sm = new CallAudioModeStateMachine(mSystemStateHelper,
-                mAudioManager, mTestThread.getLooper(), mFeatureFlags);
+                mAudioManager, mTestThread.getLooper(), mFeatureFlags, mCommunicationDeviceTracker);
         sm.setCallAudioManager(mCallAudioManager);
         sm.sendMessage(CallAudioModeStateMachine.ABANDON_FOCUS_FOR_TESTING);
         waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
@@ -115,7 +117,7 @@
     @Test
     public void testSwitchToStreamingMode() {
         CallAudioModeStateMachine sm = new CallAudioModeStateMachine(mSystemStateHelper,
-                mAudioManager, mTestThread.getLooper(), mFeatureFlags);
+                mAudioManager, mTestThread.getLooper(), mFeatureFlags, mCommunicationDeviceTracker);
         sm.setCallAudioManager(mCallAudioManager);
         sm.sendMessage(CallAudioModeStateMachine.ABANDON_FOCUS_FOR_TESTING);
         waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
@@ -145,7 +147,7 @@
     @Test
     public void testExitStreamingMode() {
         CallAudioModeStateMachine sm = new CallAudioModeStateMachine(mSystemStateHelper,
-                mAudioManager, mTestThread.getLooper(), mFeatureFlags);
+                mAudioManager, mTestThread.getLooper(), mFeatureFlags, mCommunicationDeviceTracker);
         sm.setCallAudioManager(mCallAudioManager);
         sm.sendMessage(CallAudioModeStateMachine.ENTER_STREAMING_FOCUS_FOR_TESTING);
         waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
@@ -173,7 +175,7 @@
     @Test
     public void testNoRingWhenDeviceIsAtEar() {
         CallAudioModeStateMachine sm = new CallAudioModeStateMachine(mSystemStateHelper,
-                mAudioManager, mTestThread.getLooper(), mFeatureFlags);
+                mAudioManager, mTestThread.getLooper(), mFeatureFlags, mCommunicationDeviceTracker);
         sm.setCallAudioManager(mCallAudioManager);
         sm.sendMessage(CallAudioModeStateMachine.ABANDON_FOCUS_FOR_TESTING);
         sm.sendMessage(CallAudioModeStateMachine.NEW_HOLDING_CALL, new Builder()
@@ -209,7 +211,7 @@
     @Test
     public void testRegainFocusWhenHfpIsConnectedSilenced() throws Throwable {
         CallAudioModeStateMachine sm = new CallAudioModeStateMachine(mSystemStateHelper,
-                mAudioManager, mTestThread.getLooper(), mFeatureFlags);
+                mAudioManager, mTestThread.getLooper(), mFeatureFlags, mCommunicationDeviceTracker);
         sm.setCallAudioManager(mCallAudioManager);
         sm.sendMessage(CallAudioModeStateMachine.ABANDON_FOCUS_FOR_TESTING);
         waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
@@ -253,7 +255,7 @@
     @Test
     public void testDoNotRingTwiceWhenHfpConnected() {
         CallAudioModeStateMachine sm = new CallAudioModeStateMachine(mSystemStateHelper,
-                mAudioManager, mTestThread.getLooper(), mFeatureFlags);
+                mAudioManager, mTestThread.getLooper(), mFeatureFlags, mCommunicationDeviceTracker);
         sm.setCallAudioManager(mCallAudioManager);
         sm.sendMessage(CallAudioModeStateMachine.ABANDON_FOCUS_FOR_TESTING);
         waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
@@ -291,7 +293,7 @@
     @Test
     public void testStartRingingAfterHfpConnectedIfNotAlreadyPlaying() {
         CallAudioModeStateMachine sm = new CallAudioModeStateMachine(mSystemStateHelper,
-                mAudioManager, mTestThread.getLooper(), mFeatureFlags);
+                mAudioManager, mTestThread.getLooper(), mFeatureFlags, mCommunicationDeviceTracker);
         sm.setCallAudioManager(mCallAudioManager);
         sm.sendMessage(CallAudioModeStateMachine.ABANDON_FOCUS_FOR_TESTING);
         waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
@@ -329,7 +331,7 @@
     @Test
     public void testAudioFocusRequestWithResolveHiddenDependencies() {
         CallAudioModeStateMachine sm = new CallAudioModeStateMachine(mSystemStateHelper,
-                mAudioManager, mTestThread.getLooper(), mFeatureFlags);
+                mAudioManager, mTestThread.getLooper(), mFeatureFlags, mCommunicationDeviceTracker);
         when(mFeatureFlags.telecomResolveHiddenDependencies()).thenReturn(true);
         ArgumentCaptor<AudioFocusRequest> captor = ArgumentCaptor.forClass(AudioFocusRequest.class);
         sm.setCallAudioManager(mCallAudioManager);
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioModeTransitionTests.java b/tests/src/com/android/server/telecom/tests/CallAudioModeTransitionTests.java
index 21b5312..3690d5f 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioModeTransitionTests.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioModeTransitionTests.java
@@ -24,6 +24,7 @@
 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;
@@ -108,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;
 
@@ -135,7 +137,7 @@
     @SmallTest
     public void modeTransitionTest() {
         CallAudioModeStateMachine sm = new CallAudioModeStateMachine(mSystemStateHelper,
-                mAudioManager, mTestThread.getLooper(), mFeatureFlags);
+                mAudioManager, mTestThread.getLooper(), mFeatureFlags, mCommunicationDeviceTracker);
         sm.setCallAudioManager(mCallAudioManager);
         sm.sendMessage(mParams.initialAudioState);
         waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
index 3641405..19dfe83 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
@@ -16,12 +16,32 @@
 
 package com.android.server.telecom.tests;
 
+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;
+import static org.mockito.Matchers.same;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+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.content.pm.PackageManager;
 import android.media.AudioDeviceInfo;
 import android.media.AudioManager;
 import android.media.IAudioService;
@@ -30,16 +50,16 @@
 import android.test.suitebuilder.annotation.MediumTest;
 import android.test.suitebuilder.annotation.SmallTest;
 
-import com.android.server.telecom.CallAudioCommunicationDeviceTracker;
-import com.android.server.telecom.bluetooth.BluetoothRouteManager;
 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.CallAudioManager;
 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;
@@ -60,26 +80,6 @@
 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.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;
-import static org.mockito.Matchers.same;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
 @RunWith(JUnit4.class)
 public class CallAudioRouteStateMachineTest extends TelecomTestCase {
 
@@ -99,6 +99,7 @@
     @Mock Call fakeSelfManagedCall;
     @Mock Call fakeCall;
     @Mock CallAudioManager mockCallAudioManager;
+    @Mock BluetoothDevice mockWatchDevice;
 
     private CallAudioManager.AudioServiceFactory mAudioServiceFactory;
     private static final int TEST_TIMEOUT = 500;
@@ -137,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
@@ -163,7 +167,8 @@
                 CallAudioRouteStateMachine.EARPIECE_AUTO_DETECT,
                 mThreadHandler.getLooper(),
                 Runnable::run /** do async stuff sync for test purposes */,
-                mCommunicationDeviceTracker);
+                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
@@ -184,7 +189,8 @@
                 CallAudioRouteStateMachine.EARPIECE_AUTO_DETECT,
                 mThreadHandler.getLooper(),
                 Runnable::run /** do async stuff sync for test purposes */,
-                mCommunicationDeviceTracker);
+                mCommunicationDeviceTracker,
+                mFeatureFlags);
         stateMachine.setCallAudioManager(mockCallAudioManager);
 
         Set<Call> trackedCalls = new HashSet<>(Arrays.asList(fakeCall, fakeSelfManagedCall));
@@ -207,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(),
@@ -220,7 +226,7 @@
 
     @SmallTest
     @Test
-    public void testSystemAudioStateIsUpdated() {
+    public void testSystemAudioStateIsNotUpdatedFlagOff() {
         CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
                 mContext,
                 mockCallsManager,
@@ -231,11 +237,13 @@
                 CallAudioRouteStateMachine.EARPIECE_AUTO_DETECT,
                 mThreadHandler.getLooper(),
                 Runnable::run /** do async stuff sync for test purposes */,
-                mCommunicationDeviceTracker);
+                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,
@@ -266,6 +274,59 @@
         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());
     }
 
@@ -282,7 +343,8 @@
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
                 mThreadHandler.getLooper(),
                 Runnable::run /** do async stuff sync for test purposes */,
-                mCommunicationDeviceTracker);
+                mCommunicationDeviceTracker,
+                mFeatureFlags);
         stateMachine.setCallAudioManager(mockCallAudioManager);
         CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_SPEAKER,
                 CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER);
@@ -328,7 +390,8 @@
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
                 mThreadHandler.getLooper(),
                 Runnable::run /** do async stuff sync for test purposes */,
-                mCommunicationDeviceTracker);
+                mCommunicationDeviceTracker,
+                mFeatureFlags);
 
         when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
         when(mockBluetoothRouteManager.isBluetoothAvailable()).thenReturn(true);
@@ -351,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);
     }
 
@@ -375,7 +438,8 @@
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
                 mThreadHandler.getLooper(),
                 Runnable::run /** do async stuff sync for test purposes */,
-                mCommunicationDeviceTracker);
+                mCommunicationDeviceTracker,
+                mFeatureFlags);
         stateMachine.setCallAudioManager(mockCallAudioManager);
 
         when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
@@ -396,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(
@@ -404,7 +468,7 @@
         stateMachine.sendMessageWithSessionInfo(
                 CallAudioRouteStateMachine.BT_ACTIVE_DEVICE_PRESENT);
 
-        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+        waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
         assertEquals(expectedEndState, stateMachine.getCurrentCallAudioState());
     }
 
@@ -421,7 +485,8 @@
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
                 mThreadHandler.getLooper(),
                 Runnable::run /** do async stuff sync for test purposes */,
-                mCommunicationDeviceTracker);
+                mCommunicationDeviceTracker,
+                mFeatureFlags);
         stateMachine.setCallAudioManager(mockCallAudioManager);
         Collection<BluetoothDevice> availableDevices = Collections.singleton(bluetoothDevice1);
 
@@ -446,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
@@ -469,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(
@@ -481,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());
     }
@@ -501,7 +566,8 @@
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
                 mThreadHandler.getLooper(),
                 Runnable::run /** do async stuff sync for test purposes */,
-                mCommunicationDeviceTracker);
+                mCommunicationDeviceTracker,
+                mFeatureFlags);
         stateMachine.setCallAudioManager(mockCallAudioManager);
 
         when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
@@ -516,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));
     }
 
@@ -539,7 +605,8 @@
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
                 mThreadHandler.getLooper(),
                 Runnable::run /** do async stuff sync for test purposes */,
-                mCommunicationDeviceTracker);
+                mCommunicationDeviceTracker,
+                mFeatureFlags);
         stateMachine.setCallAudioManager(mockCallAudioManager);
         setInBandRing(false);
         when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
@@ -553,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())
@@ -562,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,
@@ -573,13 +640,13 @@
 
         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);
+        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.
@@ -599,7 +666,8 @@
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
                 mThreadHandler.getLooper(),
                 Runnable::run /** do async stuff sync for test purposes */,
-                mCommunicationDeviceTracker);
+                mCommunicationDeviceTracker,
+                mFeatureFlags);
         stateMachine.setCallAudioManager(mockCallAudioManager);
         List<BluetoothDevice> availableDevices =
                 Arrays.asList(bluetoothDevice1, bluetoothDevice2, bluetoothDevice3);
@@ -625,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
@@ -651,7 +782,8 @@
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
                 mThreadHandler.getLooper(),
                 Runnable::run /** do async stuff sync for test purposes */,
-                mCommunicationDeviceTracker);
+                mCommunicationDeviceTracker,
+                mFeatureFlags);
         stateMachine.setCallAudioManager(mockCallAudioManager);
         when(mockAudioManager.isSpeakerphoneOn()).thenReturn(false);
         CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_SPEAKER,
@@ -660,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));
     }
@@ -684,7 +816,8 @@
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
                 mThreadHandler.getLooper(),
                 Runnable::run /** do async stuff sync for test purposes */,
-                mCommunicationDeviceTracker);
+                mCommunicationDeviceTracker,
+                mFeatureFlags);
         stateMachine.setCallAudioManager(mockCallAudioManager);
 
         when(mockAudioManager.isSpeakerphoneOn()).thenReturn(false);
@@ -696,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
@@ -720,7 +853,8 @@
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
                 mThreadHandler.getLooper(),
                 Runnable::run /** do async stuff sync for test purposes */,
-                mCommunicationDeviceTracker);
+                mCommunicationDeviceTracker,
+                mFeatureFlags);
         stateMachine.setCallAudioManager(mockCallAudioManager);
         List<BluetoothDevice> availableDevices =
                 Arrays.asList(bluetoothDevice1, bluetoothDevice2);
@@ -742,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.
@@ -753,7 +887,7 @@
         // Switch to inactive, pretending that the call disconnected.
         stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
                 CallAudioRouteStateMachine.NO_FOCUS);
-        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+        waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
 
         // Make sure that we've successfully switched to the quiescent BT route
         assertEquals(CallAudioState.ROUTE_BLUETOOTH,
@@ -764,6 +898,7 @@
     @SmallTest
     @Test
     public void testSetAndClearEarpieceCommunicationDevice() {
+        when(mFeatureFlags.callAudioCommunicationDeviceRefactor()).thenReturn(true);
         CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
                 mContext,
                 mockCallsManager,
@@ -774,7 +909,8 @@
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
                 mThreadHandler.getLooper(),
                 Runnable::run /** do async stuff sync for test purposes */,
-                mCommunicationDeviceTracker);
+                mCommunicationDeviceTracker,
+                mFeatureFlags);
         stateMachine.setCallAudioManager(mockCallAudioManager);
 
         AudioDeviceInfo earpiece = mock(AudioDeviceInfo.class);
@@ -797,7 +933,7 @@
         // Switch to active
         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 earpiece and that we set the
         // communication device.
@@ -811,7 +947,7 @@
         // Route earpiece to speaker
         stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_SPEAKER,
                 CallAudioRouteStateMachine.SPEAKER_ON);
-        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+        waitForHandlerAction(stateMachine.getAdapterHandler(), TEST_TIMEOUT);
 
         // Assert that communication device was cleared
         verify(mockAudioManager).clearCommunicationDevice();
@@ -820,18 +956,21 @@
     @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,
@@ -842,7 +981,8 @@
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
                 mThreadHandler.getLooper(),
                 Runnable::run /** do async stuff sync for test purposes */,
-                mCommunicationDeviceTracker);
+                mCommunicationDeviceTracker,
+                mFeatureFlags);
         stateMachine.setCallAudioManager(mockCallAudioManager);
 
         // Start the route in quiescent and ensure that a switch to ACTIVE_FOCUS transitions to
@@ -855,7 +995,7 @@
         // Switch to active
         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 route on BT
         assertTrue(stateMachine.isInActiveState());
@@ -947,7 +1087,8 @@
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
                 mThreadHandler.getLooper(),
                 Runnable::run /** do async stuff sync for test purposes */,
-                mCommunicationDeviceTracker);
+                mCommunicationDeviceTracker,
+                mFeatureFlags);
         stateMachine.initialize();
         assertEquals(expectedState, stateMachine.getCurrentCallAudioState());
     }
@@ -965,7 +1106,8 @@
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
                 mThreadHandler.getLooper(),
                 Runnable::run /** do async stuff sync for test purposes */,
-                mCommunicationDeviceTracker);
+                mCommunicationDeviceTracker,
+                mFeatureFlags);
         stateMachine.setCallAudioManager(mockCallAudioManager);
 
         CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_EARPIECE,
@@ -976,12 +1118,12 @@
         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());
     }
 
@@ -1002,7 +1144,8 @@
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
                 mThreadHandler.getLooper(),
                 Runnable::run /** do async stuff sync for test purposes */,
-                mCommunicationDeviceTracker);
+                mCommunicationDeviceTracker,
+                mFeatureFlags);
         stateMachine.setCallAudioManager(mockCallAudioManager);
 
         CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_SPEAKER,
@@ -1027,6 +1170,74 @@
         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());
+    }
+
     private void initializationTestHelper(CallAudioState expectedState,
             int earpieceControl) {
         when(mockWiredHeadsetManager.isPluggedIn()).thenReturn(
@@ -1046,7 +1257,8 @@
                 earpieceControl,
                 mThreadHandler.getLooper(),
                 Runnable::run /** do async stuff sync for test purposes */,
-                mCommunicationDeviceTracker);
+                mCommunicationDeviceTracker,
+                mFeatureFlags);
         stateMachine.initialize();
         assertEquals(expectedState, stateMachine.getCurrentCallAudioState());
     }
@@ -1098,7 +1310,8 @@
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
                 mThreadHandler.getLooper(),
                 Runnable::run /** do async stuff sync for test purposes */,
-                mCommunicationDeviceTracker);
+                mCommunicationDeviceTracker,
+                mFeatureFlags);
         stateMachine.setCallAudioManager(mockCallAudioManager);
 
         AudioDeviceInfo headset = mock(AudioDeviceInfo.class);
@@ -1121,7 +1334,7 @@
         // Switch to active
         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 headset and that we set the
         // communication device.
@@ -1135,7 +1348,7 @@
         stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
                 CallAudioRouteStateMachine.ACTIVE_FOCUS);
         stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.USER_SWITCH_EARPIECE);
-        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+        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 804ef17..25c4e9f 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java
@@ -276,7 +276,8 @@
                 mParams.earpieceControl,
                 mHandlerThread.getLooper(),
                 Runnable::run /** do async stuff sync for test purposes */,
-                mCommunicationDeviceTracker);
+                mCommunicationDeviceTracker,
+                mFeatureFlags);
         stateMachine.setCallAudioManager(mockCallAudioManager);
 
         setupMocksForParams(stateMachine, mParams);
@@ -294,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();
 
@@ -374,7 +375,8 @@
                 mParams.earpieceControl,
                 mHandlerThread.getLooper(),
                 Runnable::run /** do async stuff sync for test purposes */,
-                mCommunicationDeviceTracker);
+                mCommunicationDeviceTracker,
+                mFeatureFlags);
         stateMachine.setCallAudioManager(mockCallAudioManager);
 
         // Set up bluetooth and speakerphone state
@@ -395,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/CallLogManagerTest.java b/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java
index 94709cd..83d1043 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;
@@ -368,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);
@@ -377,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(
@@ -393,6 +395,7 @@
                 VIA_NUMBER_STRING, // viaNumber
                 null
         );
+        when(mFeatureFlags.addCallUriForMissedCalls()).thenReturn(false);
 
         mCallLogManager.onCallStateChanged(fakeMissedCall, CallState.ACTIVE,
                 CallState.DISCONNECTED);
@@ -401,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
diff --git a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
index d4c62f7..c02a0a8 100644
--- a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
@@ -17,10 +17,8 @@
 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;
@@ -43,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;
@@ -54,6 +53,7 @@
 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;
@@ -61,6 +61,7 @@
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.provider.BlockedNumberContract;
 import android.telecom.CallException;
 import android.telecom.CallScreeningService;
@@ -80,6 +81,7 @@
 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;
@@ -98,6 +100,7 @@
 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;
@@ -124,8 +127,9 @@
 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.flags.FeatureFlags;
 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;
@@ -134,6 +138,7 @@
 
 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;
@@ -284,7 +289,8 @@
     @Mock private FeatureFlags mFeatureFlags;
 
     @Mock private IncomingCallFilterGraph mIncomingCallFilterGraph;
-
+    @Mock private IConnectionService mIConnectionService;
+    @Rule public SetFlagsRule mSetRlagsRule = new SetFlagsRule();
     private CallsManager mCallsManager;
 
     @Override
@@ -303,8 +309,8 @@
         when(mCallEndpointControllerFactory.create(any(), any(), any())).thenReturn(
                 mCallEndpointController);
         when(mCallAudioRouteStateMachineFactory.create(any(), any(), any(), any(), any(), any(),
-                anyInt(), any(), any())).thenReturn(mCallAudioRouteStateMachine);
-        when(mCallAudioModeStateMachineFactory.create(any(), 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());
@@ -372,11 +378,17 @@
                 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();
     }
 
@@ -2437,7 +2449,7 @@
 
         mCallsManager.onCallFilteringComplete(callSpy, result, false /* timeout */);
         verify(mMissedCallNotifier).showMissedCallNotification(
-                any(MissedCallNotifier.CallInfo.class));
+                any(MissedCallNotifier.CallInfo.class), /* uri= */ eq(null));
     }
 
     @Test
@@ -2547,6 +2559,7 @@
     @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());
@@ -2559,6 +2572,7 @@
     @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());
@@ -2822,6 +2836,36 @@
         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);
+        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() {
@@ -3417,4 +3461,19 @@
         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/InCallControllerTests.java b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
index 683a5e2..1b5677e 100644
--- a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
+++ b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
@@ -28,7 +28,6 @@
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.ArgumentMatchers.anyObject;
 import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.matches;
 import static org.mockito.ArgumentMatchers.nullable;
@@ -36,7 +35,6 @@
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
@@ -236,7 +234,7 @@
                 "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);
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/TelecomSystemTest.java b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
index 07cb9e2..33b0068 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;
@@ -121,7 +122,7 @@
 /**
  * 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
@@ -169,7 +170,7 @@
         }
 
         @Override
-        public void showMissedCallNotification(CallInfo call) {
+        public void showMissedCallNotification(CallInfo call, @Nullable Uri uri) {
             missedCallsNotified.add(call);
         }
 
@@ -499,9 +500,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,
@@ -525,7 +528,8 @@
                             CallAudioManager.AudioServiceFactory audioServiceFactory,
                             int earpieceControl,
                             Executor asyncTaskExecutor,
-                            CallAudioCommunicationDeviceTracker communicationDeviceTracker) {
+                            CallAudioCommunicationDeviceTracker communicationDeviceTracker,
+                            FeatureFlags featureFlags) {
                         return new CallAudioRouteStateMachine(context,
                                 callsManager,
                                 bluetoothManager,
@@ -536,15 +540,19 @@
                                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
                                 mHandlerThread.getLooper(),
                                 Runnable::run /* async tasks as now sync for testing! */,
-                                communicationDeviceTracker);
+                                communicationDeviceTracker,
+                                featureFlags);
                     }
                 },
                 new CallAudioModeStateMachine.Factory() {
                     @Override
                     public CallAudioModeStateMachine create(SystemStateHelper systemStateHelper,
-                            AudioManager am, FeatureFlags featureFlags) {
+                            AudioManager am, FeatureFlags featureFlags,
+                            CallAudioCommunicationDeviceTracker callAudioCommunicationDeviceTracker
+                    ) {
                         return new CallAudioModeStateMachine(systemStateHelper, am,
-                                mHandlerThread.getLooper(), featureFlags);
+                                mHandlerThread.getLooper(), featureFlags,
+                                callAudioCommunicationDeviceTracker);
                     }
                 },
                 mClockProxy,
diff --git a/tests/src/com/android/server/telecom/tests/VideoCallTests.java b/tests/src/com/android/server/telecom/tests/VideoCallTests.java
index 84beedc..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;
@@ -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]);
     }
 }