Merge cherrypicks of ['googleplex-android-review.googlesource.com/32668895', 'googleplex-android-review.googlesource.com/32709930'] into 25Q2-release.

Change-Id: I66387c68951c4bf25df96716bab4565ab79ee6f5
diff --git a/flags/telecom_callaudioroutestatemachine_flags.aconfig b/flags/telecom_callaudioroutestatemachine_flags.aconfig
index 96a5e38..76a7f74 100644
--- a/flags/telecom_callaudioroutestatemachine_flags.aconfig
+++ b/flags/telecom_callaudioroutestatemachine_flags.aconfig
@@ -163,17 +163,6 @@
   }
 }
 
-# OWNER=pmadapurmath TARGET=25Q2
-flag {
-  name: "default_speaker_on_wired_headset_disconnect"
-  namespace: "telecom"
-  description: "Maybe route back into speaker (if it was the previous route) when a wired headset disconnects"
-  bug: "376364368"
-  metadata {
-    purpose: PURPOSE_BUGFIX
-  }
-}
-
 # OWNER=tgunn TARGET=25Q2
 flag {
   name: "bus_device_is_a_speaker"
diff --git a/src/com/android/server/telecom/AudioRoute.java b/src/com/android/server/telecom/AudioRoute.java
index bcc0b59..2b03182 100644
--- a/src/com/android/server/telecom/AudioRoute.java
+++ b/src/com/android/server/telecom/AudioRoute.java
@@ -256,26 +256,26 @@
     // Invoked when entered pending route whose dest route is this route
     void onDestRouteAsPendingRoute(boolean active, PendingAudioRoute pendingAudioRoute,
             BluetoothDevice device, AudioManager audioManager,
-            BluetoothRouteManager bluetoothRouteManager, boolean isScoAudioConnected) {
-        Log.i(this, "onDestRouteAsPendingRoute: active (%b), type (%s)", active,
-                DEVICE_TYPE_STRINGS.get(mAudioRouteType));
+            BluetoothRouteManager bluetoothRouteManager, boolean isScoAlreadyConnected) {
+        Log.i(this, "onDestRouteAsPendingRoute: active (%b), type (%s), isScoAlreadyConnected(%s)",
+                active, DEVICE_TYPE_STRINGS.get(mAudioRouteType), isScoAlreadyConnected);
         if (pendingAudioRoute.isActive() && !active) {
             clearCommunicationDevice(pendingAudioRoute, bluetoothRouteManager, audioManager);
         } else if (active) {
             // Handle BT routing case.
             if (BT_AUDIO_ROUTE_TYPES.contains(mAudioRouteType)) {
+                // Check if the communication device was set for the device, even if
+                // BluetoothHeadset#connectAudio reports that the SCO connection wasn't
+                // successfully established.
                 boolean connectedBtAudio = connectBtAudio(pendingAudioRoute, device,
-                        audioManager, bluetoothRouteManager);
+                        audioManager, bluetoothRouteManager, isScoAlreadyConnected);
                 // Special handling for SCO case.
                 if (!mIsScoManagedByAudio && mAudioRouteType == TYPE_BLUETOOTH_SCO) {
                     // Set whether the dest route is for the watch
                     mIsDestRouteForWatch = bluetoothRouteManager.isWatch(device);
-                    // Check if the communication device was set for the device, even if
-                    // BluetoothHeadset#connectAudio reports that the SCO connection wasn't
-                    // successfully established.
-                    if (connectedBtAudio || isScoAudioConnected) {
+                    if (connectedBtAudio || isScoAlreadyConnected) {
                         pendingAudioRoute.setCommunicationDeviceType(mAudioRouteType);
-                        if (!isScoAudioConnected) {
+                        if (!isScoAlreadyConnected) {
                             pendingAudioRoute.addMessage(BT_AUDIO_CONNECTED, mBluetoothAddress);
                         }
                     } else {
@@ -311,7 +311,8 @@
                     result = audioManager.setCommunicationDevice(mInfo);
                     if (result) {
                         pendingAudioRoute.setCommunicationDeviceType(mAudioRouteType);
-                        if (mAudioRouteType == TYPE_BLUETOOTH_SCO && !isScoAudioConnected
+                        if (mAudioRouteType == TYPE_BLUETOOTH_SCO
+                                && !isScoAlreadyConnected
                                 && mIsScoManagedByAudio) {
                             pendingAudioRoute.addMessage(BT_AUDIO_CONNECTED, mBluetoothAddress);
                         }
@@ -341,10 +342,12 @@
      * @param bluetoothRouteManager The BT route manager.
      */
     void onOrigRouteAsPendingRoute(boolean wasActive, PendingAudioRoute pendingAudioRoute,
-            AudioManager audioManager, BluetoothRouteManager bluetoothRouteManager) {
-        Log.i(this, "onOrigRouteAsPendingRoute: wasActive (%b), type (%s), pending(%s)", wasActive,
-                DEVICE_TYPE_STRINGS.get(mAudioRouteType), pendingAudioRoute);
-        if (wasActive) {
+            AudioManager audioManager, BluetoothRouteManager bluetoothRouteManager,
+            boolean isScoAlreadyConnected) {
+        Log.i(this, "onOrigRouteAsPendingRoute: wasActive (%b), type (%s), pending(%s),"
+                + "isScoAlreadyConnected(%s)", wasActive, DEVICE_TYPE_STRINGS.get(mAudioRouteType),
+                pendingAudioRoute, isScoAlreadyConnected);
+        if (wasActive && !isScoAlreadyConnected) {
             int result = clearCommunicationDevice(pendingAudioRoute, bluetoothRouteManager,
                     audioManager);
             if (mAudioRouteType == TYPE_SPEAKER) {
@@ -395,11 +398,12 @@
     }
 
     private boolean connectBtAudio(PendingAudioRoute pendingAudioRoute, BluetoothDevice device,
-            AudioManager audioManager, BluetoothRouteManager bluetoothRouteManager) {
+            AudioManager audioManager, BluetoothRouteManager bluetoothRouteManager,
+            boolean isScoAlreadyConnected) {
         // Ensure that if another BT device was set, it is disconnected before connecting
         // the new one.
         AudioRoute currentRoute = pendingAudioRoute.getOrigRoute();
-        if (currentRoute.getBluetoothAddress() != null &&
+        if (!isScoAlreadyConnected && currentRoute.getBluetoothAddress() != null &&
                 !currentRoute.getBluetoothAddress().equals(device.getAddress())) {
             clearCommunicationDevice(pendingAudioRoute, bluetoothRouteManager, audioManager);
         }
diff --git a/src/com/android/server/telecom/CallAudioRouteController.java b/src/com/android/server/telecom/CallAudioRouteController.java
index ce06d55..c9e0ab3 100644
--- a/src/com/android/server/telecom/CallAudioRouteController.java
+++ b/src/com/android/server/telecom/CallAudioRouteController.java
@@ -118,7 +118,7 @@
     private FeatureFlags mFeatureFlags;
     private int mFocusType;
     private int mCallSupportedRouteMask = -1;
-    private boolean mIsScoAudioConnected;
+    private BluetoothDevice mScoAudioConnectedDevice;
     private boolean mAvailableRoutesUpdated;
     private boolean mUsePreferredDeviceStrategy;
     private AudioDeviceInfo mCurrentCommunicationDevice;
@@ -188,6 +188,7 @@
     private boolean mIsMute;
     private boolean mIsPending;
     private boolean mIsActive;
+    private boolean mWasOnSpeaker;
     private final TelecomMetricsController mMetricsController;
 
     public CallAudioRouteController(
@@ -208,8 +209,9 @@
         mFeatureFlags = featureFlags;
         mMetricsController = metricsController;
         mFocusType = NO_FOCUS;
-        mIsScoAudioConnected = false;
+        mScoAudioConnectedDevice = null;
         mUsePreferredDeviceStrategy = true;
+        mWasOnSpeaker = false;
         setCurrentCommunicationDevice(null);
 
         mTelecomLock = callsManager.getLock();
@@ -306,16 +308,16 @@
                             break;
                         case SWITCH_EARPIECE:
                         case USER_SWITCH_EARPIECE:
-                            handleSwitchEarpiece();
+                            handleSwitchEarpiece(msg.what == USER_SWITCH_EARPIECE);
                             break;
                         case SWITCH_BLUETOOTH:
                         case USER_SWITCH_BLUETOOTH:
                             address = (String) ((SomeArgs) msg.obj).arg2;
-                            handleSwitchBluetooth(address);
+                            handleSwitchBluetooth(address, msg.what == USER_SWITCH_BLUETOOTH);
                             break;
                         case SWITCH_HEADSET:
                         case USER_SWITCH_HEADSET:
-                            handleSwitchHeadset();
+                            handleSwitchHeadset(msg.what == USER_SWITCH_HEADSET);
                             break;
                         case SWITCH_SPEAKER:
                         case USER_SWITCH_SPEAKER:
@@ -568,7 +570,7 @@
         return mIsPending;
     }
 
-    private void routeTo(boolean active, AudioRoute destRoute) {
+    private void routeTo(boolean isDestRouteActive, AudioRoute destRoute) {
         if (destRoute == null || (!destRoute.equals(mStreamingRoute)
                 && !getCallSupportedRoutes().contains(destRoute))) {
             Log.i(this, "Ignore routing to unavailable route: %s", destRoute);
@@ -578,42 +580,53 @@
             }
             return;
         }
+        // If another BT device connects during RINGING_FOCUS, in-band ringing will be disabled by
+        // default. In this case, we should adjust the active routing value so that we don't try
+        // to connect to the BT device as it will fail.
+        isDestRouteActive = maybeAdjustActiveRouting(destRoute, isDestRouteActive);
+        // It's possible that there are multiple HFP devices connected and if we receive SCO audio
+        // connected for the destination route's BT device, then we shouldn't disconnect SCO when
+        // clearing the communication device for the original route if it was also a HFP device.
+        boolean isScoDeviceAlreadyConnected = mScoAudioConnectedDevice != null
+                && Objects.equals(mScoAudioConnectedDevice, mBluetoothRoutes.get(destRoute));
         if (mIsPending) {
-            if (destRoute.equals(mPendingAudioRoute.getDestRoute()) && (mIsActive == active)) {
+            if (destRoute.equals(mPendingAudioRoute.getDestRoute())
+                    && (mIsActive == isDestRouteActive)) {
                 return;
             }
             Log.i(this, "Override current pending route destination from %s(active=%b) to "
                             + "%s(active=%b)",
-                    mPendingAudioRoute.getDestRoute(), mIsActive, destRoute, active);
+                    mPendingAudioRoute.getDestRoute(), mIsActive, destRoute, isDestRouteActive);
             // Ensure we don't keep waiting for SPEAKER_ON if dest route gets overridden.
-            if (!mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue() && active
+            if (!mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue() && isDestRouteActive
                     && mPendingAudioRoute.getDestRoute().getType() == TYPE_SPEAKER) {
                 mPendingAudioRoute.clearPendingMessage(new Pair<>(SPEAKER_ON, null));
             }
             // override pending route while keep waiting for still pending messages for the
             // previous pending route
             mPendingAudioRoute.setOrigRoute(mIsActive /* origin */,
-                    mPendingAudioRoute.getDestRoute(), active /* dest */);
+                    mPendingAudioRoute.getDestRoute(), isDestRouteActive /* dest */,
+                    isScoDeviceAlreadyConnected);
         } else {
-            if (mCurrentRoute.equals(destRoute) && (mIsActive == active)) {
+            if (mCurrentRoute.equals(destRoute) && (mIsActive == isDestRouteActive)) {
                 return;
             }
             Log.i(this, "Enter pending route, orig%s(active=%b), dest%s(active=%b)", mCurrentRoute,
-                    mIsActive, destRoute, active);
+                    mIsActive, destRoute, isDestRouteActive);
             // route to pending route
             if (getCallSupportedRoutes().contains(mCurrentRoute)) {
                 mPendingAudioRoute.setOrigRoute(mIsActive /* origin */, mCurrentRoute,
-                        active /* dest */);
+                        isDestRouteActive /* dest */, isScoDeviceAlreadyConnected);
             } else {
                 // Avoid waiting for pending messages for an unavailable route
                 mPendingAudioRoute.setOrigRoute(mIsActive /* origin */, DUMMY_ROUTE,
-                        active /* dest */);
+                        isDestRouteActive /* dest */, isScoDeviceAlreadyConnected);
             }
             mIsPending = true;
         }
-        mPendingAudioRoute.setDestRoute(active, destRoute, mBluetoothRoutes.get(destRoute),
-                mIsScoAudioConnected);
-        mIsActive = active;
+        mPendingAudioRoute.setDestRoute(isDestRouteActive, destRoute,
+                mBluetoothRoutes.get(destRoute), isScoDeviceAlreadyConnected);
+        mIsActive = isDestRouteActive;
         mPendingAudioRoute.evaluatePendingState();
         if (mFeatureFlags.telecomMetricsSupport()) {
             mMetricsController.getAudioRouteStats().onRouteEnter(mPendingAudioRoute);
@@ -678,10 +691,7 @@
             // Preserve speaker routing if it was the last audio routing path when the wired headset
             // disconnects. Ignore this special cased routing when the route isn't active
             // (in other words, when we're not in a call).
-            AudioRoute route = mFeatureFlags.defaultSpeakerOnWiredHeadsetDisconnect()
-                    && mIsActive && mPendingAudioRoute.getOrigRoute() != null
-                    && mPendingAudioRoute.getOrigRoute().getType() == TYPE_SPEAKER
-                    && mSpeakerDockRoute != null
+            AudioRoute route = mWasOnSpeaker && mIsActive && mSpeakerDockRoute != null
                     && mSpeakerDockRoute.getType() == AudioRoute.TYPE_SPEAKER
                     ? mSpeakerDockRoute : getBaseRoute(true, null);
             routeTo(mIsActive, route);
@@ -894,6 +904,7 @@
             }
         }
         if ((mIsPending && pendingRouteNeedsUpdate) || (!mIsPending && currentRouteNeedsUpdate)) {
+            maybeDisableWasOnSpeaker(true);
             // Fallback to an available route excluding the previously active device.
             routeTo(mIsActive, getBaseRoute(true, previouslyActiveDeviceAddress));
         }
@@ -928,6 +939,7 @@
         mFocusType = focus;
         switch (focus) {
             case NO_FOCUS -> {
+                mWasOnSpeaker = false;
                 // Notify the CallAudioModeStateMachine that audio operations are complete so
                 // that we can relinquish audio focus.
                 mCallAudioManager.notifyAudioOperationsComplete();
@@ -993,16 +1005,17 @@
         }
     }
 
-    public void handleSwitchEarpiece() {
+    public void handleSwitchEarpiece(boolean isUserRequest) {
         AudioRoute earpieceRoute = mTypeRoutes.get(AudioRoute.TYPE_EARPIECE);
         if (earpieceRoute != null && getCallSupportedRoutes().contains(earpieceRoute)) {
+            maybeDisableWasOnSpeaker(isUserRequest);
             routeTo(mIsActive, earpieceRoute);
         } else {
             Log.i(this, "ignore switch earpiece request");
         }
     }
 
-    private void handleSwitchBluetooth(String address) {
+    private void handleSwitchBluetooth(String address, boolean isUserRequest) {
         Log.i(this, "handle switch to bluetooth with address %s", address);
         AudioRoute bluetoothRoute = null;
         BluetoothDevice bluetoothDevice = null;
@@ -1020,6 +1033,7 @@
         }
 
         if (bluetoothRoute != null && bluetoothDevice != null) {
+            maybeDisableWasOnSpeaker(isUserRequest);
             if (mFocusType == RINGING_FOCUS) {
                 routeTo(mBluetoothRouteManager
                                 .isInbandRingEnabled(bluetoothRoute.getType(), bluetoothDevice)
@@ -1051,9 +1065,10 @@
         }
     }
 
-    private void handleSwitchHeadset() {
+    private void handleSwitchHeadset(boolean isUserRequest) {
         AudioRoute headsetRoute = mTypeRoutes.get(AudioRoute.TYPE_WIRED);
         if (headsetRoute != null && getCallSupportedRoutes().contains(headsetRoute)) {
+            maybeDisableWasOnSpeaker(isUserRequest);
             routeTo(mIsActive, headsetRoute);
         } else {
             Log.i(this, "ignore switch headset request");
@@ -1073,10 +1088,10 @@
             String btAddressToExclude) {
         Log.i(this, "handleSwitchBaselineRoute: includeBluetooth: %b, "
                 + "btAddressToExclude: %s", includeBluetooth, btAddressToExclude);
+        AudioRoute pendingDestRoute = mPendingAudioRoute.getDestRoute();
         boolean areExcludedBtAndDestBtSame = btAddressToExclude != null
-                && mPendingAudioRoute.getDestRoute() != null
-                && Objects.equals(btAddressToExclude, mPendingAudioRoute.getDestRoute()
-                .getBluetoothAddress());
+                && pendingDestRoute != null
+                && Objects.equals(btAddressToExclude, pendingDestRoute.getBluetoothAddress());
         Pair<Integer, String> btDevicePendingMsg =
                 new Pair<>(BT_AUDIO_CONNECTED, btAddressToExclude);
 
@@ -1084,8 +1099,8 @@
         // we know that the device has reconnected or is in the middle of connecting. Ignore routing
         // out of this BT device.
         boolean isExcludedDeviceConnectingOrConnected = areExcludedBtAndDestBtSame
-                && (mIsScoAudioConnected || mPendingAudioRoute.getPendingMessages()
-                .contains(btDevicePendingMsg));
+                && (Objects.equals(mBluetoothRoutes.get(pendingDestRoute), mScoAudioConnectedDevice)
+                || mPendingAudioRoute.getPendingMessages().contains(btDevicePendingMsg));
         // Check if the pending audio route or current route is already different from the route
         // including the BT device that should be excluded from route selection.
         boolean isCurrentOrDestRouteDifferent = btAddressToExclude != null
@@ -1103,6 +1118,7 @@
                 return;
             }
         }
+        maybeDisableWasOnSpeaker(isExplicitUserRequest);
         routeTo(mIsActive, calculateBaselineRoute(isExplicitUserRequest, includeBluetooth,
                 btAddressToExclude));
     }
@@ -1157,6 +1173,11 @@
             mPendingAudioRoute.clearPendingMessages();
             onCurrentRouteChanged();
             if (mIsActive) {
+                // Only set mWasOnSpeaker if the routing was active. We don't want to consider this
+                // selection outside of a call.
+                if (mCurrentRoute.getType() == TYPE_SPEAKER) {
+                    mWasOnSpeaker = true;
+                }
                 // Reinitialize the audio ops complete latch since the routing went active. We
                 // should always expect operations to complete after this point.
                 if (mAudioOperationsCompleteLatch.getCount() == 0) {
@@ -1649,8 +1670,9 @@
         mIsPending = isPending;
     }
 
-    public void setIsScoAudioConnected(boolean value) {
-        mIsScoAudioConnected = value;
+    @VisibleForTesting
+    public void setScoAudioConnectedDevice(BluetoothDevice device) {
+        mScoAudioConnectedDevice = device;
     }
 
     private void clearRingingBluetoothAddress() {
@@ -1757,4 +1779,37 @@
             return mCurrentCommunicationDevice;
         }
     }
+
+    private void maybeDisableWasOnSpeaker(boolean isUserRequest) {
+        if (isUserRequest) {
+            mWasOnSpeaker = false;
+        }
+    }
+
+    /*
+     * Adjusts routing to go inactive if we're active in the case that we're processing
+     * RINGING_FOCUS and another BT headset is connected which causes in-band ringing to get
+     * disabled. If we stay in active routing, Telecom will send requests to connect to these BT
+     * devices while the call is ringing and each of these requests will fail at the BT stack side.
+     * By default, in-band ringtone is disabled when more than one BT device is paired. Instead,
+     * ringtone is played using the headset's default ringtone.
+     */
+    private boolean maybeAdjustActiveRouting(AudioRoute destRoute, boolean isDestRouteActive) {
+        BluetoothDevice device = mBluetoothRoutes.get(destRoute);
+        // If routing is active and in-band ringing is disabled while the call is ringing, move to
+        // inactive routing.
+        if (isDestRouteActive && mFocusType == RINGING_FOCUS && device != null
+                && !mBluetoothRouteManager.isInbandRingEnabled(destRoute.getType(), device)) {
+            return false;
+        }
+        else if (!isDestRouteActive && mFocusType == RINGING_FOCUS && (device == null
+                || mBluetoothRouteManager.isInbandRingEnabled(destRoute.getType(), device))) {
+            // If the routing is inactive while the call is ringing and we re-evaluate this to find
+            // that we're routing to a non-BT device or a BT device that does support in-band
+            // ringing, then re-enable active routing (i.e. second HFP headset is disconnected
+            // while call is ringing).
+            return true;
+        }
+        return isDestRouteActive;
+    }
 }
diff --git a/src/com/android/server/telecom/PendingAudioRoute.java b/src/com/android/server/telecom/PendingAudioRoute.java
index dde1d8d..37c70ad 100644
--- a/src/com/android/server/telecom/PendingAudioRoute.java
+++ b/src/com/android/server/telecom/PendingAudioRoute.java
@@ -83,10 +83,11 @@
      * @param origRoute The origin.
      * @param isDestActive Whether the destination will be active.
      */
-    void setOrigRoute(boolean isOriginActive, AudioRoute origRoute, boolean isDestActive) {
+    void setOrigRoute(boolean isOriginActive, AudioRoute origRoute, boolean isDestActive,
+            boolean isScoAlreadyConnected) {
         mActive = isDestActive;
         origRoute.onOrigRouteAsPendingRoute(isOriginActive, this, mAudioManager,
-                mBluetoothRouteManager);
+                mBluetoothRouteManager, isScoAlreadyConnected);
         mOrigRoute = origRoute;
     }
 
@@ -95,9 +96,9 @@
     }
 
     void setDestRoute(boolean active, AudioRoute destRoute, BluetoothDevice device,
-            boolean isScoAudioConnected) {
+            boolean isScoAlreadyConnected) {
         destRoute.onDestRouteAsPendingRoute(active, this, device,
-                mAudioManager, mBluetoothRouteManager, isScoAudioConnected);
+                mAudioManager, mBluetoothRouteManager, isScoAlreadyConnected);
         mActive = active;
         mDestRoute = destRoute;
     }
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java b/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
index 1330be4..0478fdc 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
@@ -52,6 +52,8 @@
 import com.android.server.telecom.CallAudioRouteController;
 import com.android.server.telecom.flags.FeatureFlags;
 
+import java.util.Objects;
+
 public class BluetoothStateReceiver extends BroadcastReceiver {
     private static final String LOG_TAG = BluetoothStateReceiver.class.getSimpleName();
     public static final IntentFilter INTENT_FILTER;
@@ -124,16 +126,17 @@
                 if (mFeatureFlags.useRefactoredAudioRouteSwitching()) {
                     CallAudioRouteController audioRouteController =
                             (CallAudioRouteController) mCallAudioRouteAdapter;
-                    audioRouteController.setIsScoAudioConnected(true);
-                    if (audioRouteController.isPending()) {
+                    audioRouteController.setScoAudioConnectedDevice(device);
+                    AudioRoute btRoute = audioRouteController.getBluetoothRoute(
+                            AudioRoute.TYPE_BLUETOOTH_SCO, device.getAddress());
+                    if (audioRouteController.isPending() && Objects.equals(audioRouteController
+                            .getPendingAudioRoute().getDestRoute(), btRoute)) {
                         mCallAudioRouteAdapter.sendMessageWithSessionInfo(BT_AUDIO_CONNECTED, 0,
                                 device);
                     } else {
                         // It's possible that the initial BT connection fails but BT_AUDIO_CONNECTED
                         // is sent later, indicating that SCO audio is on. We should route
                         // appropriately in order for the UI to reflect this state.
-                        AudioRoute btRoute = audioRouteController.getBluetoothRoute(
-                                AudioRoute.TYPE_BLUETOOTH_SCO, device.getAddress());
                         if (btRoute != null) {
                             audioRouteController.getPendingAudioRoute().overrideDestRoute(btRoute);
                             audioRouteController.overrideIsPending(true);
@@ -155,7 +158,7 @@
                 if (mFeatureFlags.useRefactoredAudioRouteSwitching()) {
                     CallAudioRouteController audioRouteController =
                             (CallAudioRouteController) mCallAudioRouteAdapter;
-                    audioRouteController.setIsScoAudioConnected(false);
+                    audioRouteController.setScoAudioConnectedDevice(null);
                     if (audioRouteController.isPending()) {
                         mCallAudioRouteAdapter.sendMessageWithSessionInfo(BT_AUDIO_DISCONNECTED, 0,
                                 device);
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java
index 1b1ca56..f6f1686 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java
@@ -530,23 +530,35 @@
     @SmallTest
     @Test
     public void testDefaultSpeakerOnWiredHeadsetDisconnect() {
-        when(mFeatureFlags.defaultSpeakerOnWiredHeadsetDisconnect()).thenReturn(true);
         mController.initialize();
         mController.setActive(true);
         verifyMaybeDefaultSpeakerOnDisconnectWiredHeadset(
-                CallAudioState.ROUTE_SPEAKER /* expectedAudioType */);
+                CallAudioState.ROUTE_SPEAKER /* expectedAudioType */,
+                false /* includeUserSwitch */);
     }
 
     @SmallTest
     @Test
     public void testIgnoreDefaultSpeakerOnWiredHeadsetDisconnect() {
-        when(mFeatureFlags.defaultSpeakerOnWiredHeadsetDisconnect()).thenReturn(true);
         // Note here that the routing isn't active to represent that we're not in a call. If a wired
         // headset is disconnected and the last route was speaker, we shouldn't switch back to
         // speaker when we're not in a call.
         mController.initialize();
         verifyMaybeDefaultSpeakerOnDisconnectWiredHeadset(
-                CallAudioState.ROUTE_EARPIECE /* expectedAudioType */);
+                CallAudioState.ROUTE_EARPIECE /* expectedAudioType */,
+                false /* includeUserSwitch */);
+    }
+
+    @SmallTest
+    @Test
+    public void testIgnoreDefaultSpeakerOnWiredHeadsetDisconnect_UserSwitchesOutOfSpeaker() {
+        mController.initialize();
+        mController.setActive(true);
+        // Verify that when we turn speaker on/off when a wired headset is plugged in and after the
+        // headset is disconnected that we don't default audio routing back to speaker.
+        verifyMaybeDefaultSpeakerOnDisconnectWiredHeadset(
+                CallAudioState.ROUTE_EARPIECE /* expectedAudioType */,
+                true /* includeUserSwitch */);
     }
 
     @SmallTest
@@ -1104,7 +1116,7 @@
         mController.sendMessageWithSessionInfo(SWITCH_BASELINE_ROUTE,
                 INCLUDE_BLUETOOTH_IN_BASELINE, BLUETOOTH_DEVICE_1.getAddress());
         // Process BT_AUDIO_CONNECTED from connecting to BT device in active focus request.
-        mController.setIsScoAudioConnected(true);
+        mController.setScoAudioConnectedDevice(BLUETOOTH_DEVICE_1);
         mController.sendMessageWithSessionInfo(BT_AUDIO_CONNECTED, 0, BLUETOOTH_DEVICE_1);
         // Verify SCO not disconnected and route stays on connected BT device.
         verify(mBluetoothDeviceManager, timeout(TEST_TIMEOUT).times(0)).disconnectSco();
@@ -1407,6 +1419,81 @@
                 any(CallAudioState.class), eq(expectedState));
     }
 
+    @Test
+    @SmallTest
+    public void testRouteToInactiveWhenInbandRingingDisabledDuringRinging() {
+        when(mBluetoothRouteManager.isInbandRingEnabled(eq(AudioRoute.TYPE_BLUETOOTH_SCO),
+                eq(BLUETOOTH_DEVICE_1))).thenReturn(true);
+        verifyConnectBluetoothDevice(AudioRoute.TYPE_BLUETOOTH_SCO);
+        mController.sendMessageWithSessionInfo(SWITCH_FOCUS, RINGING_FOCUS, 0);
+        assertTrue(mController.isActive());
+
+        // Connect another HFP device while call is still ringing
+        BluetoothDevice scoDevice =
+                BluetoothRouteManagerTest.makeBluetoothDevice("00:00:00:00:00:03");
+        BLUETOOTH_DEVICES.add(scoDevice);
+
+        // Add SCO device.
+        mController.sendMessageWithSessionInfo(BT_DEVICE_ADDED, AudioRoute.TYPE_BLUETOOTH_SCO,
+                scoDevice);
+        CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH,
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH
+                        | CallAudioState.ROUTE_SPEAKER, BLUETOOTH_DEVICE_1, BLUETOOTH_DEVICES);
+        verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+                any(CallAudioState.class), eq(expectedState));
+
+        when(mBluetoothRouteManager.isInbandRingEnabled(eq(AudioRoute.TYPE_BLUETOOTH_SCO),
+                any(BluetoothDevice.class))).thenReturn(false);
+        // Emulate second device becoming active and first device getting disconnected as in-band
+        // ringing is disabled.
+        mController.sendMessageWithSessionInfo(BT_ACTIVE_DEVICE_PRESENT,
+                AudioRoute.TYPE_BLUETOOTH_SCO, scoDevice.getAddress());
+        mController.sendMessageWithSessionInfo(BT_AUDIO_DISCONNECTED, 0,
+                BLUETOOTH_DEVICE_1);
+        expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH,
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH
+                        | CallAudioState.ROUTE_SPEAKER, scoDevice, BLUETOOTH_DEVICES);
+        // Verify routing goes to the new HFP device but that the routing is now inactive.
+        verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+                any(CallAudioState.class), eq(expectedState));
+        assertFalse(mController.isActive());
+    }
+
+    @Test
+    @SmallTest
+    public void testSkipConnectBluetoothWhenScoAudioAlreadyConnected() {
+        verifyConnectBluetoothDevice(AudioRoute.TYPE_BLUETOOTH_SCO);
+        // Connect another HFP device while call is still ringing
+        BluetoothDevice scoDevice =
+                BluetoothRouteManagerTest.makeBluetoothDevice("00:00:00:00:00:03");
+        BLUETOOTH_DEVICES.add(scoDevice);
+
+        // Add SCO device.
+        mController.sendMessageWithSessionInfo(BT_DEVICE_ADDED, AudioRoute.TYPE_BLUETOOTH_SCO,
+                scoDevice);
+        CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH,
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH
+                        | CallAudioState.ROUTE_SPEAKER, BLUETOOTH_DEVICE_1, BLUETOOTH_DEVICES);
+        verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+                any(CallAudioState.class), eq(expectedState));
+
+        // Emulate scenario where BT stack signals SCO audio connected for the second HFP device
+        // before Telecom finishes processing the route change to this device. We should ensure
+        // that we don't accidentally disconnect SCO in this case (thinking that we're disconnecting
+        // the first HFP device).
+        mController.setScoAudioConnectedDevice(scoDevice);
+        mController.sendMessageWithSessionInfo(BT_ACTIVE_DEVICE_PRESENT,
+                AudioRoute.TYPE_BLUETOOTH_SCO, scoDevice.getAddress());
+        expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH,
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH
+                        | CallAudioState.ROUTE_SPEAKER, scoDevice, BLUETOOTH_DEVICES);
+        // Verify routing goes to the new HFP device and we never disconnect SCO when clearing the
+        // original pending route.
+        verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+                any(CallAudioState.class), eq(expectedState));
+        verify(mBluetoothDeviceManager, timeout(TEST_TIMEOUT).times(0)).disconnectSco();
+    }
+
     private void verifyConnectBluetoothDevice(int audioType) {
         mController.initialize();
         mController.setActive(true);
@@ -1473,7 +1560,7 @@
                 any(CallAudioState.class), eq(expectedState));
     }
 
-    private void verifyMaybeDefaultSpeakerOnDisconnectWiredHeadset(int expectedAudioType) {
+    private void verifyMaybeDefaultSpeakerOnDisconnectWiredHeadset(int expectedAudioType, boolean includeUserSwitch) {
         // Ensure audio is routed to speaker initially
         mController.sendMessageWithSessionInfo(SPEAKER_ON);
         CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_SPEAKER,
@@ -1491,6 +1578,30 @@
         verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
                 any(CallAudioState.class), eq(expectedState));
 
+        // Emulate scenario with user turning on/off speaker. This is to verify that when the user
+        // switches off speaker that we don't auto route back to speaker when the wired headset
+        // disconnects.
+        if (includeUserSwitch) {
+            // Verify speaker turned on from USER_SWITCH_SPEAKER
+            mController.sendMessageWithSessionInfo(USER_SWITCH_SPEAKER);
+            mController.sendMessageWithSessionInfo(SPEAKER_ON);
+            expectedState = new CallAudioState(false, CallAudioState.ROUTE_SPEAKER,
+                    CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_SPEAKER, null,
+                    new HashSet<>());
+            verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+                    any(CallAudioState.class), eq(expectedState));
+
+            // Verify speaker turned off from turning off speaker
+            mController.sendMessageWithSessionInfo(USER_SWITCH_BASELINE_ROUTE,
+                    INCLUDE_BLUETOOTH_IN_BASELINE);
+            mController.sendMessageWithSessionInfo(SPEAKER_OFF);
+            expectedState = new CallAudioState(false, CallAudioState.ROUTE_WIRED_HEADSET,
+                    CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_SPEAKER, null,
+                    new HashSet<>());
+            verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+                    any(CallAudioState.class), eq(expectedState));
+        }
+
         // Verify that we route back into speaker once the wired headset disconnects
         mController.sendMessageWithSessionInfo(DISCONNECT_WIRED_HEADSET);
         expectedState = new CallAudioState(false, expectedAudioType,