Loading flags/telecom_callaudioroutestatemachine_flags.aconfig +11 −0 Original line number Diff line number Diff line Loading @@ -107,3 +107,14 @@ flag { 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 } } src/com/android/server/telecom/AudioRoute.java +2 −1 Original line number Diff line number Diff line Loading @@ -318,7 +318,8 @@ public class AudioRoute { // 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); Loading src/com/android/server/telecom/CallAudioRouteController.java +46 −9 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -63,6 +64,8 @@ import java.util.List; 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); Loading Loading @@ -107,6 +110,8 @@ public class CallAudioRouteController implements CallAudioRouteAdapter { 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; Loading Loading @@ -200,10 +205,12 @@ public class CallAudioRouteController implements CallAudioRouteAdapter { handlerThread.start(); // Register broadcast receivers 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); Loading @@ -214,6 +221,31 @@ public class CallAudioRouteController implements CallAudioRouteAdapter { 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 Loading Loading @@ -798,11 +830,11 @@ public class CallAudioRouteController implements CallAudioRouteAdapter { 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); } } Loading Loading @@ -852,8 +884,13 @@ public class CallAudioRouteController implements CallAudioRouteAdapter { // 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(); Loading Loading @@ -1173,7 +1210,7 @@ public class CallAudioRouteController implements CallAudioRouteAdapter { } // 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()); Loading tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java +27 −0 Original line number Diff line number Diff line Loading @@ -193,6 +193,7 @@ public class CallAudioRouteControllerTest extends TelecomTestCase { when(mFeatureFlags.ignoreAutoRouteToWatchDevice()).thenReturn(false); when(mFeatureFlags.useRefactoredAudioRouteSwitching()).thenReturn(true); when(mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()).thenReturn(false); when(mFeatureFlags.newAudioPathSpeakerBroadcastAndUnfocusedRouting()).thenReturn(false); } @After Loading Loading @@ -1031,6 +1032,32 @@ public class CallAudioRouteControllerTest extends TelecomTestCase { 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); Loading Loading
flags/telecom_callaudioroutestatemachine_flags.aconfig +11 −0 Original line number Diff line number Diff line Loading @@ -107,3 +107,14 @@ flag { 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 } }
src/com/android/server/telecom/AudioRoute.java +2 −1 Original line number Diff line number Diff line Loading @@ -318,7 +318,8 @@ public class AudioRoute { // 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); Loading
src/com/android/server/telecom/CallAudioRouteController.java +46 −9 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -63,6 +64,8 @@ import java.util.List; 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); Loading Loading @@ -107,6 +110,8 @@ public class CallAudioRouteController implements CallAudioRouteAdapter { 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; Loading Loading @@ -200,10 +205,12 @@ public class CallAudioRouteController implements CallAudioRouteAdapter { handlerThread.start(); // Register broadcast receivers 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); Loading @@ -214,6 +221,31 @@ public class CallAudioRouteController implements CallAudioRouteAdapter { 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 Loading Loading @@ -798,11 +830,11 @@ public class CallAudioRouteController implements CallAudioRouteAdapter { 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); } } Loading Loading @@ -852,8 +884,13 @@ public class CallAudioRouteController implements CallAudioRouteAdapter { // 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(); Loading Loading @@ -1173,7 +1210,7 @@ public class CallAudioRouteController implements CallAudioRouteAdapter { } // 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()); Loading
tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java +27 −0 Original line number Diff line number Diff line Loading @@ -193,6 +193,7 @@ public class CallAudioRouteControllerTest extends TelecomTestCase { when(mFeatureFlags.ignoreAutoRouteToWatchDevice()).thenReturn(false); when(mFeatureFlags.useRefactoredAudioRouteSwitching()).thenReturn(true); when(mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()).thenReturn(false); when(mFeatureFlags.newAudioPathSpeakerBroadcastAndUnfocusedRouting()).thenReturn(false); } @After Loading Loading @@ -1031,6 +1032,32 @@ public class CallAudioRouteControllerTest extends TelecomTestCase { 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); Loading