Merge "Resolve READ_PRIVILEGED_PHONE_STATE bypass" into main
diff --git a/src/com/android/server/telecom/CallAudioRouteController.java b/src/com/android/server/telecom/CallAudioRouteController.java
index 98adae9..727d300 100644
--- a/src/com/android/server/telecom/CallAudioRouteController.java
+++ b/src/com/android/server/telecom/CallAudioRouteController.java
@@ -800,7 +800,6 @@
         if (bluetoothRoute != null) {
             Log.i(this, "request to route to bluetooth route: %s (active=%b)", bluetoothRoute,
                     mIsActive);
-            updateActiveBluetoothDevice(new Pair<>(type, deviceAddress));
             routeTo(mIsActive, bluetoothRoute);
         } else {
             Log.i(this, "request to route to unavailable bluetooth route - type (%s), address (%s)",
@@ -844,10 +843,6 @@
             // Fallback to an available route excluding the previously active device.
             routeTo(mIsActive, getBaseRoute(true, previouslyActiveDeviceAddress));
         }
-        // Clear out the active device for the BT audio type.
-        if (mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()) {
-            updateActiveBluetoothDevice(new Pair(type, null));
-        }
     }
 
     private void handleMuteChanged(boolean mute) {
@@ -1023,15 +1018,28 @@
         // If SCO is once again connected or there's a pending message for BT_AUDIO_CONNECTED, then
         // we know that the device has reconnected or is in the middle of connecting. Ignore routing
         // out of this BT device.
-        if (mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue() && areExcludedBtAndDestBtSame
+        boolean isExcludedDeviceConnectingOrConnected = areExcludedBtAndDestBtSame
                 && (mIsScoAudioConnected || mPendingAudioRoute.getPendingMessages()
-                .contains(btDevicePendingMsg))) {
-            Log.i(this, "BT device with address (%s) is currently connecting/connected. "
-                    + "Ignore route switch.");
-        } else {
-            routeTo(mIsActive, calculateBaselineRoute(isExplicitUserRequest, includeBluetooth,
-                    btAddressToExclude));
+                .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
+                && ((mIsPending && !btAddressToExclude.equals(mPendingAudioRoute.getDestRoute()
+                .getBluetoothAddress())) || (!mIsPending && !btAddressToExclude.equals(
+                        mCurrentRoute.getBluetoothAddress())));
+        if (mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()) {
+            if (isExcludedDeviceConnectingOrConnected) {
+                Log.i(this, "BT device with address (%s) is currently connecting/connected. "
+                        + "Ignoring route switch.", btAddressToExclude);
+                return;
+            } else if (isCurrentOrDestRouteDifferent) {
+                Log.i(this, "Current or pending audio route isn't routed to device with address "
+                        + "(%s). Ignoring route switch.", btAddressToExclude);
+                return;
+            }
         }
+        routeTo(mIsActive, calculateBaselineRoute(isExplicitUserRequest, includeBluetooth,
+                btAddressToExclude));
     }
 
     private void handleSpeakerOn() {
@@ -1441,8 +1449,11 @@
                 continue;
             }
             // Check if the most recently active device is a watch device.
-            if (i == (bluetoothRoutes.size() - 1) && device.equals(mCallAudioState
-                    .getActiveBluetoothDevice()) && mBluetoothRouteManager.isWatch(device)) {
+            boolean isActiveDevice = mActiveBluetoothDevice != null
+                    && device.getAddress().equals(mActiveBluetoothDevice.second);
+            if (i == (bluetoothRoutes.size() - 1) && mBluetoothRouteManager.isWatch(device)
+                    && (device.equals(mCallAudioState.getActiveBluetoothDevice())
+                    || isActiveDevice)) {
                 Log.i(this, "getActiveWatchOrNonWatchDeviceRoute: Routing to active watch - %s",
                         bluetoothRoutes.get(0));
                 return bluetoothRoutes.get(0);
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java b/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
index 7667ebc..7fe8246 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
@@ -252,17 +252,14 @@
             CallAudioRouteController audioRouteController = (CallAudioRouteController)
                     mCallAudioRouteAdapter;
             if (device == null) {
-                if (!mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()) {
-                    audioRouteController.updateActiveBluetoothDevice(
-                            new Pair(audioRouteType, null));
-                }
+                // Update the active device cache immediately.
+                audioRouteController.updateActiveBluetoothDevice(new Pair(audioRouteType, null));
                 mCallAudioRouteAdapter.sendMessageWithSessionInfo(BT_ACTIVE_DEVICE_GONE,
                         audioRouteType);
             } else {
-                if (!mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()) {
-                    audioRouteController.updateActiveBluetoothDevice(
-                            new Pair(audioRouteType, device.getAddress()));
-                }
+                // Update the active device cache immediately.
+                audioRouteController.updateActiveBluetoothDevice(
+                        new Pair(audioRouteType, device.getAddress()));
                 mCallAudioRouteAdapter.sendMessageWithSessionInfo(BT_ACTIVE_DEVICE_PRESENT,
                         audioRouteType, device.getAddress());
                 if (deviceType == BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java
index ba48c64..809abb4 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java
@@ -74,6 +74,7 @@
 import android.os.UserHandle;
 import android.telecom.CallAudioState;
 import android.telecom.VideoProfile;
+import android.util.Pair;
 
 import androidx.test.filters.SmallTest;
 
@@ -1101,6 +1102,63 @@
 
     @Test
     @SmallTest
+    public void testRouteToWatchWhenCallAnsweredOnWatch_MultipleBtDevices() {
+        when(mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()).thenReturn(true);
+        // Connect first BT device.
+        verifyConnectBluetoothDevice(AudioRoute.TYPE_BLUETOOTH_SCO);
+        // Connect another BT device.
+        String scoDeviceAddress = "00:00:00:00:00:03";
+        BluetoothDevice watchDevice =
+                BluetoothRouteManagerTest.makeBluetoothDevice(scoDeviceAddress);
+        when(mBluetoothRouteManager.isWatch(eq(watchDevice))).thenReturn(true);
+        BLUETOOTH_DEVICES.add(watchDevice);
+
+        mController.sendMessageWithSessionInfo(BT_DEVICE_ADDED, AudioRoute.TYPE_BLUETOOTH_SCO,
+                watchDevice);
+        CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH,
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER
+                | CallAudioState.ROUTE_BLUETOOTH, BLUETOOTH_DEVICE_1, BLUETOOTH_DEVICES);
+        verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+                any(CallAudioState.class), eq(expectedState));
+
+        // Signal that watch is now the active device. This is done in BluetoothStateReceiver and
+        // then BT_ACTIVE_DEVICE_PRESENT will be sent to the controller to be processed.
+        mController.updateActiveBluetoothDevice(
+                new Pair<>(AudioRoute.TYPE_BLUETOOTH_SCO, watchDevice.getAddress()));
+        // Emulate scenario with call answered on watch. Ensure at this point that audio was routed
+        // into watch
+        mController.sendMessageWithSessionInfo(SWITCH_FOCUS, ACTIVE_FOCUS, 0);
+        mController.sendMessageWithSessionInfo(BT_AUDIO_CONNECTED,
+                0, watchDevice);
+        mController.sendMessageWithSessionInfo(BT_AUDIO_DISCONNECTED,
+                0, BLUETOOTH_DEVICE_1);
+        expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH,
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER
+                        | CallAudioState.ROUTE_BLUETOOTH, watchDevice, BLUETOOTH_DEVICES);
+        verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+                any(CallAudioState.class), eq(expectedState));
+
+        // Hardcode signal from BT stack signaling to Telecom that watch is now the active device.
+        // This should just be a no-op since audio was already routed when processing active focus.
+        mController.sendMessageWithSessionInfo(BT_ACTIVE_DEVICE_PRESENT,
+                AudioRoute.TYPE_BLUETOOTH_SCO, scoDeviceAddress);
+        verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+                any(CallAudioState.class), eq(expectedState));
+
+        // Mimic behavior of controller processing BT_AUDIO_DISCONNECTED for BLUETOOTH_DEVICE_1 and
+        // verify that audio remains routed to the watch and not routed to earpiece (this should
+        // be taking into account what the BT active device is as reported to us by the BT stack).
+        mController.sendMessageWithSessionInfo(SWITCH_BASELINE_ROUTE,
+                INCLUDE_BLUETOOTH_IN_BASELINE, BLUETOOTH_DEVICE_1.getAddress());
+        verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+                any(CallAudioState.class), eq(expectedState));
+
+        BLUETOOTH_DEVICES.remove(watchDevice);
+    }
+
+
+    @Test
+    @SmallTest
     public void testAbandonCallAudioFocusAfterCallEnd() {
         // Make sure in-band ringing is disabled so that route never becomes active
         when(mBluetoothRouteManager.isInbandRingEnabled(eq(BLUETOOTH_DEVICE_1))).thenReturn(false);