Resolve user switch baseline route for video calls

Ensure that when there's an explicit user request to switch baseline
route that we don't skip earpiece (i.e. during a video call). This stems
from the user not being able to turn speaker off during a carrier video
call.

Bug: 374037591
Flag: com.android.server.telecom.flags.fix_user_request_baseline_route_video_call
Test: atest CallAudioRouteControllerTest
Change-Id: Iae9d1ae7720f0ddde565a05bdd2d5b341b9f3dd7
diff --git a/flags/telecom_callaudioroutestatemachine_flags.aconfig b/flags/telecom_callaudioroutestatemachine_flags.aconfig
index 3dd5c9d..a60c0f1 100644
--- a/flags/telecom_callaudioroutestatemachine_flags.aconfig
+++ b/flags/telecom_callaudioroutestatemachine_flags.aconfig
@@ -118,3 +118,14 @@
     purpose: PURPOSE_BUGFIX
   }
 }
+
+# OWNER=pmadapurmath TARGET=25Q2
+flag {
+  name: "fix_user_request_baseline_route_video_call"
+  namespace: "telecom"
+  description: "Ensure that audio is routed out of speaker in a video call when we receive USER_SWITCH_BASELINE_ROUTE."
+  bug: "374037591"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
diff --git a/src/com/android/server/telecom/CallAudioRouteController.java b/src/com/android/server/telecom/CallAudioRouteController.java
index 5c5f3f0..04229b6 100644
--- a/src/com/android/server/telecom/CallAudioRouteController.java
+++ b/src/com/android/server/telecom/CallAudioRouteController.java
@@ -310,12 +310,12 @@
                             break;
                         case SWITCH_BASELINE_ROUTE:
                             address = (String) ((SomeArgs) msg.obj).arg2;
-                            handleSwitchBaselineRoute(msg.arg1 == INCLUDE_BLUETOOTH_IN_BASELINE,
-                                    address);
+                            handleSwitchBaselineRoute(false,
+                                    msg.arg1 == INCLUDE_BLUETOOTH_IN_BASELINE, address);
                             break;
                         case USER_SWITCH_BASELINE_ROUTE:
-                            handleSwitchBaselineRoute(msg.arg1 == INCLUDE_BLUETOOTH_IN_BASELINE,
-                                    null);
+                            handleSwitchBaselineRoute(true,
+                                    msg.arg1 == INCLUDE_BLUETOOTH_IN_BASELINE, null);
                             break;
                         case SPEAKER_ON:
                             handleSpeakerOn();
@@ -888,7 +888,7 @@
                     // speaker, route back to earpiece). If we're on BT, remain on BT if it's still
                     // connected.
                     AudioRoute route = mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()
-                            ? calculateBaselineRoute(true, null)
+                            ? calculateBaselineRoute(false, true, null)
                             : mCurrentRoute;
                     routeTo(false, route);
                     // Clear pending messages
@@ -1009,7 +1009,8 @@
         }
     }
 
-    private void handleSwitchBaselineRoute(boolean includeBluetooth, String btAddressToExclude) {
+    private void handleSwitchBaselineRoute(boolean isExplicitUserRequest, boolean includeBluetooth,
+            String btAddressToExclude) {
         Log.i(this, "handleSwitchBaselineRoute: includeBluetooth: %b, "
                 + "btAddressToExclude: %s", includeBluetooth, btAddressToExclude);
         boolean areExcludedBtAndDestBtSame = btAddressToExclude != null
@@ -1027,7 +1028,8 @@
             Log.i(this, "BT device with address (%s) is currently connecting/connected. "
                     + "Ignore route switch.");
         } else {
-            routeTo(mIsActive, calculateBaselineRoute(includeBluetooth, btAddressToExclude));
+            routeTo(mIsActive, calculateBaselineRoute(isExplicitUserRequest, includeBluetooth,
+                    btAddressToExclude));
         }
     }
 
@@ -1238,13 +1240,18 @@
         return mAudioManager.getPreferredDeviceForStrategy(strategy);
     }
 
-    private AudioRoute getPreferredAudioRouteFromDefault(boolean includeBluetooth,
-            String btAddressToExclude) {
-        boolean skipEarpiece;
+    private AudioRoute getPreferredAudioRouteFromDefault(boolean isExplicitUserRequest,
+            boolean includeBluetooth, String btAddressToExclude) {
+        boolean skipEarpiece = false;
         Call foregroundCall = mCallAudioManager.getForegroundCall();
-        synchronized (mTelecomLock) {
-            skipEarpiece = foregroundCall != null
-                    && VideoProfile.isVideo(foregroundCall.getVideoState());
+        if (!mFeatureFlags.fixUserRequestBaselineRouteVideoCall()) {
+            isExplicitUserRequest = false;
+        }
+        if (!isExplicitUserRequest) {
+            synchronized (mTelecomLock) {
+                skipEarpiece = foregroundCall != null
+                        && VideoProfile.isVideo(foregroundCall.getVideoState());
+            }
         }
         // Route to earpiece, wired, or speaker route if there are not bluetooth routes or if there
         // are only wearables available.
@@ -1344,7 +1351,7 @@
         Log.i(this, "getBaseRoute: preferred audio route is %s", destRoute);
         if (destRoute == null || (destRoute.getBluetoothAddress() != null && (!includeBluetooth
                 || destRoute.getBluetoothAddress().equals(btAddressToExclude)))) {
-            destRoute = getPreferredAudioRouteFromDefault(includeBluetooth, btAddressToExclude);
+            destRoute = getPreferredAudioRouteFromDefault(false, includeBluetooth, btAddressToExclude);
         }
         if (destRoute != null && !getCallSupportedRoutes().contains(destRoute)) {
             destRoute = null;
@@ -1353,8 +1360,9 @@
         return destRoute;
     }
 
-    private AudioRoute calculateBaselineRoute(boolean includeBluetooth, String btAddressToExclude) {
-        AudioRoute destRoute = getPreferredAudioRouteFromDefault(
+    private AudioRoute calculateBaselineRoute(boolean isExplicitUserRequest,
+            boolean includeBluetooth, String btAddressToExclude) {
+        AudioRoute destRoute = getPreferredAudioRouteFromDefault(isExplicitUserRequest,
                 includeBluetooth, btAddressToExclude);
         if (destRoute != null && !getCallSupportedRoutes().contains(destRoute)) {
             destRoute = null;
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java b/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
index bb1a745..7667ebc 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
@@ -270,12 +270,16 @@
                     if (!mBluetoothDeviceManager.setCommunicationDeviceForAddress(
                             device.getAddress())) {
                         Log.i(this, "handleActiveDeviceChanged: Failed to set "
-                                + "communication device for %s. Sending PENDING_ROUTE_FAILED to "
-                                + "pending audio route.", device);
+                                + "communication device for %s.", device);
                         if (!mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()) {
+                            Log.i(this, "Sending PENDING_ROUTE_FAILED "
+                                    + "to pending audio route.");
                             mCallAudioRouteAdapter.getPendingAudioRoute()
                                     .onMessageReceived(new Pair<>(PENDING_ROUTE_FAILED,
                                             device.getAddress()), device.getAddress());
+                        } else {
+                            Log.i(this, "Refrain from sending PENDING_ROUTE_FAILED"
+                                    + " to pending audio route.");
                         }
                     } else {
                         // Track the currently set communication device.
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java
index 528585b..330e84c 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java
@@ -37,6 +37,7 @@
 import static com.android.server.telecom.CallAudioRouteAdapter.STREAMING_FORCE_ENABLED;
 import static com.android.server.telecom.CallAudioRouteAdapter.SWITCH_BASELINE_ROUTE;
 import static com.android.server.telecom.CallAudioRouteAdapter.SWITCH_FOCUS;
+import static com.android.server.telecom.CallAudioRouteAdapter.USER_SWITCH_BASELINE_ROUTE;
 import static com.android.server.telecom.CallAudioRouteAdapter.USER_SWITCH_BLUETOOTH;
 import static com.android.server.telecom.CallAudioRouteAdapter.USER_SWITCH_EARPIECE;
 import static com.android.server.telecom.CallAudioRouteAdapter.USER_SWITCH_HEADSET;
@@ -194,6 +195,7 @@
         when(mFeatureFlags.useRefactoredAudioRouteSwitching()).thenReturn(true);
         when(mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()).thenReturn(false);
         when(mFeatureFlags.newAudioPathSpeakerBroadcastAndUnfocusedRouting()).thenReturn(false);
+        when(mFeatureFlags.fixUserRequestBaselineRouteVideoCall()).thenReturn(false);
     }
 
     @After
@@ -1058,6 +1060,45 @@
                 any(CallAudioState.class), eq(expectedState));
     }
 
+    @Test
+    @SmallTest
+    public void testUserSwitchBaselineRouteVideoCall() {
+        when(mFeatureFlags.fixUserRequestBaselineRouteVideoCall()).thenReturn(true);
+        mController.initialize();
+        mController.setActive(true);
+        // Set capabilities for video call.
+        when(mCall.getVideoState()).thenReturn(VideoProfile.STATE_BIDIRECTIONAL);
+
+        // Turn on speaker
+        mController.sendMessageWithSessionInfo(SPEAKER_ON);
+        CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_SPEAKER,
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER, null,
+                new HashSet<>());
+        verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+                any(CallAudioState.class), eq(expectedState));
+
+        // USER_SWITCH_BASELINE_ROUTE (explicit user request). Verify that audio is routed back to
+        // earpiece.
+        mController.sendMessageWithSessionInfo(USER_SWITCH_BASELINE_ROUTE,
+                CallAudioRouteController.INCLUDE_BLUETOOTH_IN_BASELINE);
+        mController.sendMessageWithSessionInfo(SPEAKER_OFF);
+        expectedState = new CallAudioState(false, CallAudioState.ROUTE_EARPIECE,
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER, null,
+                new HashSet<>());
+        verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+                any(CallAudioState.class), eq(expectedState));
+
+        // SWITCH_BASELINE_ROUTE. Verify that audio is routed to speaker for non-user requests.
+        mController.sendMessageWithSessionInfo(SWITCH_BASELINE_ROUTE,
+                CallAudioRouteController.INCLUDE_BLUETOOTH_IN_BASELINE);
+        mController.sendMessageWithSessionInfo(SPEAKER_ON);
+        expectedState = new CallAudioState(false, CallAudioState.ROUTE_SPEAKER,
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER, null,
+                new HashSet<>());
+        verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+                any(CallAudioState.class), eq(expectedState));
+    }
+
     private void verifyConnectBluetoothDevice(int audioType) {
         mController.initialize();
         mController.setActive(true);