Use communication device callback and fix reinitialization routing

This CL replaces the speaker broadcasts with using the provided
AudioManager onCommunicationDeviceChanged callback in order to listen to
updates to the communication device. This is done with the hope of
seeing improved performance with when Telecom receives indication of
when the speaker toggles on/off. We have seen cases where when the
speaker is toggled on/off quickly, the broadcasts can be received out of
order and cause Telecom to show the audio route as speaker when audio is
routing to earpiece, for example. This occurs as a result of switching
from earpiece -> speaker -> earpiece and Telecom receiving SPEAKER_ON
after SPEAKER_OFF.

There is also a fix done to ensure that the audio routing is
reinitialized at the end of the call. We've gotten reports from user
that if the audio is routed to speaker and another call is placed, then
there is an audio flicker from speaker to earpiece in the UI at the
beginning of the call.

Bug: 353419513
Bug: 371625143
Flag:
com.android.server.telecom.flags.new_audio_path_speaker_broadcast_and_unfocused_routing
Test: Added unit test for verifying call audio route reintialization
Test: Manual to confirm that onCommunicationDeviceChanged callback is
being received for speaker phone switches. Tested with switching between
earpiece, speaker, and LEHS.

Change-Id: If51cc560791cbc81734a2ff6e3e57067910531cb
diff --git a/flags/telecom_callaudioroutestatemachine_flags.aconfig b/flags/telecom_callaudioroutestatemachine_flags.aconfig
index 62b8bdb..3dd5c9d 100644
--- a/flags/telecom_callaudioroutestatemachine_flags.aconfig
+++ b/flags/telecom_callaudioroutestatemachine_flags.aconfig
@@ -107,3 +107,14 @@
     purpose: PURPOSE_BUGFIX
   }
 }
+
+# OWNER=pmadapurmath TARGET=25Q1
+flag {
+  name: "new_audio_path_speaker_broadcast_and_unfocused_routing"
+  namespace: "telecom"
+  description: "Replace the speaker broadcasts with the communication device changed listener and resolve baseline routing issues when a call ends."
+  bug: "353419513"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
diff --git a/src/com/android/server/telecom/AudioRoute.java b/src/com/android/server/telecom/AudioRoute.java
index 6dc64ba..d3ed77d 100644
--- a/src/com/android/server/telecom/AudioRoute.java
+++ b/src/com/android/server/telecom/AudioRoute.java
@@ -318,7 +318,8 @@
     // sending SPEAKER_OFF, or disconnecting SCO).
     void onOrigRouteAsPendingRoute(boolean active, PendingAudioRoute pendingAudioRoute,
             AudioManager audioManager, BluetoothRouteManager bluetoothRouteManager) {
-        Log.i(this, "onOrigRouteAsPendingRoute: active (%b), type (%d)", active, mAudioRouteType);
+        Log.i(this, "onOrigRouteAsPendingRoute: active (%b), type (%s)", active,
+                DEVICE_TYPE_STRINGS.get(mAudioRouteType));
         if (active) {
             int result = clearCommunicationDevice(pendingAudioRoute, bluetoothRouteManager,
                     audioManager);
diff --git a/src/com/android/server/telecom/CallAudioRouteController.java b/src/com/android/server/telecom/CallAudioRouteController.java
index 7422286..5c5f3f0 100644
--- a/src/com/android/server/telecom/CallAudioRouteController.java
+++ b/src/com/android/server/telecom/CallAudioRouteController.java
@@ -17,6 +17,7 @@
 package com.android.server.telecom;
 
 import static com.android.server.telecom.AudioRoute.BT_AUDIO_ROUTE_TYPES;
+import static com.android.server.telecom.AudioRoute.DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE;
 import static com.android.server.telecom.AudioRoute.TYPE_INVALID;
 import static com.android.server.telecom.AudioRoute.TYPE_SPEAKER;
 
@@ -63,6 +64,8 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 
 public class CallAudioRouteController implements CallAudioRouteAdapter {
     private static final AudioRoute DUMMY_ROUTE = new AudioRoute(TYPE_INVALID, null, null);
@@ -107,6 +110,8 @@
     private PendingAudioRoute mPendingAudioRoute;
     private AudioRoute.Factory mAudioRouteFactory;
     private StatusBarNotifier mStatusBarNotifier;
+    private AudioManager.OnCommunicationDeviceChangedListener mCommunicationDeviceListener;
+    private ExecutorService mCommunicationDeviceChangedExecutor;
     private FeatureFlags mFeatureFlags;
     private int mFocusType;
     private int mCallSupportedRouteMask = -1;
@@ -200,10 +205,12 @@
         handlerThread.start();
 
         // Register broadcast receivers
-        IntentFilter speakerChangedFilter = new IntentFilter(
-                AudioManager.ACTION_SPEAKERPHONE_STATE_CHANGED);
-        speakerChangedFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
-        context.registerReceiver(mSpeakerPhoneChangeReceiver, speakerChangedFilter);
+        if (!mFeatureFlags.newAudioPathSpeakerBroadcastAndUnfocusedRouting()) {
+            IntentFilter speakerChangedFilter = new IntentFilter(
+                    AudioManager.ACTION_SPEAKERPHONE_STATE_CHANGED);
+            speakerChangedFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
+            context.registerReceiver(mSpeakerPhoneChangeReceiver, speakerChangedFilter);
+        }
 
         IntentFilter micMuteChangedFilter = new IntentFilter(
                 AudioManager.ACTION_MICROPHONE_MUTE_CHANGED);
@@ -214,6 +221,31 @@
         muteChangedFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
         context.registerReceiver(mMuteChangeReceiver, muteChangedFilter);
 
+        // Register AudioManager#onCommunicationDeviceChangedListener listener to receive updates
+        // to communication device (via AudioManager#setCommunicationDevice). This is a replacement
+        // to using broadcasts in the hopes of improving performance.
+        mCommunicationDeviceChangedExecutor = Executors.newSingleThreadExecutor();
+        mCommunicationDeviceListener = new AudioManager.OnCommunicationDeviceChangedListener() {
+            @Override
+            public void onCommunicationDeviceChanged(AudioDeviceInfo device) {
+                @AudioRoute.AudioRouteType int audioType = device != null
+                        ? DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.get(device.getType())
+                        : TYPE_INVALID;
+                Log.i(this, "onCommunicationDeviceChanged: %d", audioType);
+                if (device != null && device.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) {
+                    sendMessageWithSessionInfo(SPEAKER_ON);
+                } else if (mPendingAudioRoute != null && mPendingAudioRoute.getOrigRoute() != null
+                        && mPendingAudioRoute.getOrigRoute().getType() == AudioRoute.TYPE_SPEAKER) {
+                    sendMessageWithSessionInfo(SPEAKER_OFF);
+                }
+            }
+        };
+        if (mFeatureFlags.newAudioPathSpeakerBroadcastAndUnfocusedRouting()) {
+            mAudioManager.addOnCommunicationDeviceChangedListener(
+                    mCommunicationDeviceChangedExecutor,
+                    mCommunicationDeviceListener);
+        }
+
         // Create handler
         mHandler = new Handler(handlerThread.getLooper()) {
             @Override
@@ -798,11 +830,11 @@
         boolean currentRouteNeedsUpdate = mCurrentRoute.getType() == type;
         if (mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()) {
             if (pendingRouteNeedsUpdate) {
-                pendingRouteNeedsUpdate &= mPendingAudioRoute.getDestRoute().getBluetoothAddress()
+                pendingRouteNeedsUpdate = mPendingAudioRoute.getDestRoute().getBluetoothAddress()
                         .equals(previouslyActiveDeviceAddress);
             }
             if (currentRouteNeedsUpdate) {
-                currentRouteNeedsUpdate &= mCurrentRoute.getBluetoothAddress()
+                currentRouteNeedsUpdate = mCurrentRoute.getBluetoothAddress()
                         .equals(previouslyActiveDeviceAddress);
             }
         }
@@ -852,8 +884,13 @@
 
                     // Reset mute state after call ends.
                     handleMuteChanged(false);
-                    // Route back to inactive route.
-                    routeTo(false, mCurrentRoute);
+                    // Ensure we reset call audio state at the end of the call (i.e. if we're on
+                    // 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)
+                            : mCurrentRoute;
+                    routeTo(false, route);
                     // Clear pending messages
                     mPendingAudioRoute.clearPendingMessages();
                     clearRingingBluetoothAddress();
@@ -1173,7 +1210,7 @@
         }
 
         // Get corresponding audio route
-        @AudioRoute.AudioRouteType int type = AudioRoute.DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.get(
+        @AudioRoute.AudioRouteType int type = DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.get(
                 deviceAttr.getType());
         if (BT_AUDIO_ROUTE_TYPES.contains(type)) {
             return getBluetoothRoute(type, deviceAttr.getAddress());
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java
index c0e9904..528585b 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java
@@ -193,6 +193,7 @@
         when(mFeatureFlags.ignoreAutoRouteToWatchDevice()).thenReturn(false);
         when(mFeatureFlags.useRefactoredAudioRouteSwitching()).thenReturn(true);
         when(mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()).thenReturn(false);
+        when(mFeatureFlags.newAudioPathSpeakerBroadcastAndUnfocusedRouting()).thenReturn(false);
     }
 
     @After
@@ -1031,6 +1032,32 @@
         BLUETOOTH_DEVICES.remove(scoDevice);
     }
 
+    @Test
+    @SmallTest
+    public void verifyRouteReinitializedAfterCallEnd() {
+        when(mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()).thenReturn(true);
+        mController.initialize();
+        mController.setActive(true);
+
+        // Switch to 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));
+
+        // Verify that call audio route is reinitialized to default (in this case, earpiece) when
+        // call audio focus is lost.
+        mController.sendMessageWithSessionInfo(SWITCH_FOCUS, NO_FOCUS, 0);
+        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));
+    }
+
     private void verifyConnectBluetoothDevice(int audioType) {
         mController.initialize();
         mController.setActive(true);