Resolve call audio refactor test failures

There were test breakages found in trunk staging in BT and Telecom CTS
tests which are being resolved as part of this CL. Ensure that we don't
try to set the communication device for a null device and update the
system audio state when we receive UPDATE_SYSTEM_AUDIO_ROUTE in the
controller.

This CL also resolves an issue with audio routing to the base route when
playing the end tone (which may override a user's previously requested
route). For instance, when a wired headset is plugged in and the user
switches to speaker, we receive ACTIVE_FOCUS when playing the end tone
which causes audio to route back to wired headset. Audio incorrectly
plays on wired headset in this instance so we can opt to ignore falling
back on the base route for end tones to resolve this.

Bug: 349394420
Bug: 349060651
Flag: com.android.server.telecom.flags.use_refactored_audio_route_switching
Test: atest CtsTelecomTestCases, atest BluetoothInstrumentationTests
Change-Id: If9c977d9c32f4ae1436007b7132cc3d9d52be605
diff --git a/src/com/android/server/telecom/CallAudioManager.java b/src/com/android/server/telecom/CallAudioManager.java
index 1f1ca9d..35ff8b0 100644
--- a/src/com/android/server/telecom/CallAudioManager.java
+++ b/src/com/android/server/telecom/CallAudioManager.java
@@ -576,8 +576,25 @@
 
     @VisibleForTesting
     public void setCallAudioRouteFocusState(int focusState) {
-        mCallAudioRouteAdapter.sendMessageWithSessionInfo(
-                CallAudioRouteStateMachine.SWITCH_FOCUS, focusState);
+        if (mFeatureFlags.useRefactoredAudioRouteSwitching()) {
+            mCallAudioRouteAdapter.sendMessageWithSessionInfo(
+                    CallAudioRouteStateMachine.SWITCH_FOCUS, focusState, 0);
+        } else {
+            mCallAudioRouteAdapter.sendMessageWithSessionInfo(
+                    CallAudioRouteStateMachine.SWITCH_FOCUS, focusState);
+        }
+    }
+
+    public void setCallAudioRouteFocusStateForEndTone() {
+        if (mFeatureFlags.useRefactoredAudioRouteSwitching()) {
+            mCallAudioRouteAdapter.sendMessageWithSessionInfo(
+                    CallAudioRouteStateMachine.SWITCH_FOCUS,
+                    CallAudioRouteStateMachine.ACTIVE_FOCUS, 1);
+        } else {
+            mCallAudioRouteAdapter.sendMessageWithSessionInfo(
+                    CallAudioRouteStateMachine.SWITCH_FOCUS,
+                    CallAudioRouteStateMachine.ACTIVE_FOCUS);
+        }
     }
 
     public void notifyAudioOperationsComplete() {
diff --git a/src/com/android/server/telecom/CallAudioModeStateMachine.java b/src/com/android/server/telecom/CallAudioModeStateMachine.java
index 6420f2e..3c9c6ac 100644
--- a/src/com/android/server/telecom/CallAudioModeStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioModeStateMachine.java
@@ -824,7 +824,7 @@
             }
             mAudioManager.setMode(mMostRecentMode);
             mLocalLog.log("Mode " + mMostRecentMode);
-            mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.ACTIVE_FOCUS);
+            mCallAudioManager.setCallAudioRouteFocusStateForEndTone();
         }
 
         @Override
diff --git a/src/com/android/server/telecom/CallAudioRouteAdapter.java b/src/com/android/server/telecom/CallAudioRouteAdapter.java
index 9927c22..b23851d 100644
--- a/src/com/android/server/telecom/CallAudioRouteAdapter.java
+++ b/src/com/android/server/telecom/CallAudioRouteAdapter.java
@@ -128,6 +128,7 @@
     void sendMessageWithSessionInfo(int message);
     void sendMessageWithSessionInfo(int message, int arg);
     void sendMessageWithSessionInfo(int message, int arg, String data);
+    void sendMessageWithSessionInfo(int message, int arg, int data);
     void sendMessageWithSessionInfo(int message, int arg, BluetoothDevice bluetoothDevice);
     void sendMessage(int message, Runnable r);
     void setCallAudioManager(CallAudioManager callAudioManager);
diff --git a/src/com/android/server/telecom/CallAudioRouteController.java b/src/com/android/server/telecom/CallAudioRouteController.java
index 76555c3..de3975f 100644
--- a/src/com/android/server/telecom/CallAudioRouteController.java
+++ b/src/com/android/server/telecom/CallAudioRouteController.java
@@ -213,6 +213,7 @@
                     String address;
                     BluetoothDevice bluetoothDevice;
                     int focus;
+                    int handleEndTone;
                     @AudioRoute.AudioRouteType int type;
                     switch (msg.what) {
                         case CONNECT_WIRED_HEADSET:
@@ -305,11 +306,18 @@
                             break;
                         case SWITCH_FOCUS:
                             focus = msg.arg1;
-                            handleSwitchFocus(focus);
+                            handleEndTone = (int) ((SomeArgs) msg.obj).arg2;
+                            handleSwitchFocus(focus, handleEndTone);
                             break;
                         case EXIT_PENDING_ROUTE:
                             handleExitPendingRoute();
                             break;
+                        case UPDATE_SYSTEM_AUDIO_ROUTE:
+                            updateCallAudioState(new CallAudioState(mIsMute,
+                                    mCallAudioState.getRoute(),
+                                    mCallAudioState.getSupportedRouteMask(),
+                                    mCallAudioState.getActiveBluetoothDevice(),
+                                    mCallAudioState.getSupportedBluetoothDevices()));
                         default:
                             break;
                     }
@@ -398,6 +406,14 @@
     }
 
     @Override
+    public void sendMessageWithSessionInfo(int message, int arg, int data) {
+        SomeArgs args = SomeArgs.obtain();
+        args.arg1 = Log.createSubsession();
+        args.arg2 = data;
+        sendMessage(message, arg, 0, args);
+    }
+
+    @Override
     public void sendMessageWithSessionInfo(int message, int arg, BluetoothDevice bluetoothDevice) {
         SomeArgs args = SomeArgs.obtain();
         args.arg1 = Log.createSubsession();
@@ -766,7 +782,7 @@
         onMuteStateChanged(mIsMute);
     }
 
-    private void handleSwitchFocus(int focus) {
+    private void handleSwitchFocus(int focus, int handleEndTone) {
         Log.i(this, "handleSwitchFocus: focus (%s)", focus);
         mFocusType = focus;
         switch (focus) {
@@ -782,8 +798,12 @@
             }
             case ACTIVE_FOCUS -> {
                 // Route to active baseline route (we may need to change audio route in the case
-                // when a video call is put on hold).
-                routeTo(true, getBaseRoute(true, null));
+                // when a video call is put on hold). Ignore route changes if we're handling playing
+                // the end tone. Otherwise, it's possible that we'll override the route a client has
+                // previously requested.
+                if (handleEndTone == 0) {
+                    routeTo(true, getBaseRoute(true, null));
+                }
             }
             case RINGING_FOCUS -> {
                 if (!mIsActive) {
diff --git a/src/com/android/server/telecom/CallAudioRouteStateMachine.java b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
index 74d23a9..0a99903 100644
--- a/src/com/android/server/telecom/CallAudioRouteStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
@@ -1685,6 +1685,10 @@
         sendMessage(message, arg, 0, args);
     }
 
+    public void sendMessageWithSessionInfo(int message, int arg, int data) {
+        // ignore, only used in CallAudioRouteController
+    }
+
     public void sendMessageWithSessionInfo(int message, int arg, BluetoothDevice bluetoothDevice) {
         // ignore, only used in CallAudioRouteController
     }
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java b/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
index 3c97d41..c6fd9ae 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
@@ -746,10 +746,8 @@
             }
         }
 
-        if (!mAudioManager.getCommunicationDevice().equals(deviceInfo)) {
-            return mAudioManager.setCommunicationDevice(deviceInfo);
-        }
-        return true;
+        return deviceInfo != null && (mAudioManager.getCommunicationDevice().equals(deviceInfo)
+                || mAudioManager.setCommunicationDevice(deviceInfo));
     }
 
     // Connect audio to the bluetooth device at address, checking to see whether it's
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java b/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
index f76391c..b4c3d8d 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
@@ -149,7 +149,7 @@
                 }
                 break;
             case BluetoothHeadset.STATE_AUDIO_DISCONNECTED:
-                if (Flags.useRefactoredAudioRouteSwitching()) {
+                if (mFeatureFlags.useRefactoredAudioRouteSwitching()) {
                     CallAudioRouteController audioRouteController =
                             (CallAudioRouteController) mCallAudioRouteAdapter;
                     audioRouteController.setIsScoAudioConnected(false);
@@ -195,7 +195,7 @@
                 device.getAddress(), bluetoothHeadsetState);
 
         if (bluetoothHeadsetState == BluetoothProfile.STATE_CONNECTED) {
-            if (Flags.useRefactoredAudioRouteSwitching()) {
+            if (mFeatureFlags.useRefactoredAudioRouteSwitching()) {
                 mCallAudioRouteAdapter.sendMessageWithSessionInfo(BT_DEVICE_ADDED,
                         audioRouteType, device);
             } else {
@@ -203,7 +203,7 @@
             }
         } else if (bluetoothHeadsetState == BluetoothProfile.STATE_DISCONNECTED
                 || bluetoothHeadsetState == BluetoothProfile.STATE_DISCONNECTING) {
-            if (Flags.useRefactoredAudioRouteSwitching()) {
+            if (mFeatureFlags.useRefactoredAudioRouteSwitching()) {
                 mCallAudioRouteAdapter.sendMessageWithSessionInfo(BT_DEVICE_REMOVED,
                         audioRouteType, device);
             } else {
@@ -235,7 +235,7 @@
         Log.i(LOG_TAG, "Device %s is now the preferred BT device for %s", device,
                 BluetoothDeviceManager.getDeviceTypeString(deviceType));
 
-        if (Flags.useRefactoredAudioRouteSwitching()) {
+        if (mFeatureFlags.useRefactoredAudioRouteSwitching()) {
             CallAudioRouteController audioRouteController = (CallAudioRouteController)
                     mCallAudioRouteAdapter;
             if (device == null) {
diff --git a/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java b/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java
index d5e903b..ac4a94e 100644
--- a/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java
@@ -48,6 +48,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.server.telecom.CallAudioCommunicationDeviceTracker;
+import com.android.server.telecom.CallAudioRouteAdapter;
 import com.android.server.telecom.bluetooth.BluetoothDeviceManager;
 import com.android.server.telecom.bluetooth.BluetoothRouteManager;
 import com.android.server.telecom.bluetooth.BluetoothStateReceiver;
@@ -138,6 +139,7 @@
 
         when(mSpeakerInfo.getType()).thenReturn(TYPE_BUILTIN_SPEAKER);
         when(mFeatureFlags.callAudioCommunicationDeviceRefactor()).thenReturn(false);
+        when(mFeatureFlags.useRefactoredAudioRouteSwitching()).thenReturn(false);
     }
 
     @Override
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java
index 59473bd..b56a37b 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java
@@ -172,6 +172,7 @@
         when(mCallAudioManager.getForegroundCall()).thenReturn(mCall);
         when(mCall.getVideoState()).thenReturn(VideoProfile.STATE_AUDIO_ONLY);
         when(mFeatureFlags.ignoreAutoRouteToWatchDevice()).thenReturn(false);
+        when(mFeatureFlags.useRefactoredAudioRouteSwitching()).thenReturn(true);
     }
 
     @After
@@ -215,7 +216,7 @@
     @Test
     public void testNormalCallRouteToEarpiece() {
         mController.initialize();
-        mController.sendMessageWithSessionInfo(SWITCH_FOCUS, ACTIVE_FOCUS);
+        mController.sendMessageWithSessionInfo(SWITCH_FOCUS, ACTIVE_FOCUS, 0);
         // Verify that pending audio destination route is set to speaker. This will trigger pending
         // message to wait for SPEAKER_ON message once communication device is set before routing.
         waitForHandlerAction(mController.getAdapterHandler(), TEST_TIMEOUT);
@@ -231,9 +232,52 @@
 
     @SmallTest
     @Test
+    public void testActiveFocusAudioRouting() {
+        mController.initialize();
+        // Connect wired headset
+        mController.sendMessageWithSessionInfo(CONNECT_WIRED_HEADSET);
+        CallAudioState 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));
+
+        // Explicitly switch to 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));
+        // Expect that active focus received from a new active call will force route to baseline
+        // (in this case, this should be the wired headset).
+        mController.sendMessageWithSessionInfo(SWITCH_FOCUS, ACTIVE_FOCUS, 0);
+        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));
+
+        // Switch back to speaker and send active focus for end tone to confirm that audio routing
+        // doesn't fall back onto the baseline.
+        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));
+        mController.sendMessageWithSessionInfo(SWITCH_FOCUS, ACTIVE_FOCUS, 1);
+        verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+                any(CallAudioState.class), eq(expectedState));
+    }
+
+    @SmallTest
+    @Test
     public void testVideoCallHoldRouteToEarpiece() {
         mController.initialize();
-        mController.sendMessageWithSessionInfo(SWITCH_FOCUS, ACTIVE_FOCUS);
+        mController.sendMessageWithSessionInfo(SWITCH_FOCUS, ACTIVE_FOCUS, 0);
         // Verify that pending audio destination route is not defaulted to speaker when a video call
         // is not the foreground call.
         waitForHandlerAction(mController.getAdapterHandler(), TEST_TIMEOUT);
@@ -246,7 +290,7 @@
     public void testVideoCallRouteToSpeaker() {
         when(mCall.getVideoState()).thenReturn(VideoProfile.STATE_BIDIRECTIONAL);
         mController.initialize();
-        mController.sendMessageWithSessionInfo(SWITCH_FOCUS, ACTIVE_FOCUS);
+        mController.sendMessageWithSessionInfo(SWITCH_FOCUS, ACTIVE_FOCUS, 0);
         // Verify that pending audio destination route is set to speaker. This will trigger pending
         // message to wait for SPEAKER_ON message once communication device is set before routing.
         waitForHandlerAction(mController.getAdapterHandler(), TEST_TIMEOUT);
@@ -314,15 +358,15 @@
                 any(CallAudioState.class), eq(expectedState));
         assertFalse(mController.isActive());
 
-        mController.sendMessageWithSessionInfo(SWITCH_FOCUS, RINGING_FOCUS);
+        mController.sendMessageWithSessionInfo(SWITCH_FOCUS, RINGING_FOCUS, 0);
         verify(mBluetoothDeviceManager, timeout(TEST_TIMEOUT))
                 .connectAudio(BLUETOOTH_DEVICE_1, AudioRoute.TYPE_BLUETOOTH_SCO);
         assertTrue(mController.isActive());
 
-        mController.sendMessageWithSessionInfo(SWITCH_FOCUS, ACTIVE_FOCUS);
+        mController.sendMessageWithSessionInfo(SWITCH_FOCUS, ACTIVE_FOCUS, 0);
         assertTrue(mController.isActive());
 
-        mController.sendMessageWithSessionInfo(SWITCH_FOCUS, NO_FOCUS);
+        mController.sendMessageWithSessionInfo(SWITCH_FOCUS, NO_FOCUS, 0);
         verify(mBluetoothDeviceManager, timeout(TEST_TIMEOUT).atLeastOnce()).disconnectSco();
         assertFalse(mController.isActive());
     }
@@ -463,7 +507,7 @@
 
     @SmallTest
     @Test
-    public void tesetSwitchSpeakerAndHeadset() {
+    public void testSwitchSpeakerAndHeadset() {
         mController.initialize();
         mController.sendMessageWithSessionInfo(CONNECT_WIRED_HEADSET);
         CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_WIRED_HEADSET,
@@ -560,7 +604,7 @@
 
         // Switch to NO_FOCUS to indicate call termination and verify mute is reset.
         when(mAudioManager.isMicrophoneMute()).thenReturn(true);
-        mController.sendMessageWithSessionInfo(SWITCH_FOCUS, NO_FOCUS);
+        mController.sendMessageWithSessionInfo(SWITCH_FOCUS, NO_FOCUS, 0);
         expectedState = new CallAudioState(false, CallAudioState.ROUTE_EARPIECE,
                 CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER, null,
                 new HashSet<>());