Merge "Resolve update system audio route NPE and foreground updates" into main
diff --git a/src/com/android/server/telecom/CallAudioRouteController.java b/src/com/android/server/telecom/CallAudioRouteController.java
index 5fb4a3a..6235ec9 100644
--- a/src/com/android/server/telecom/CallAudioRouteController.java
+++ b/src/com/android/server/telecom/CallAudioRouteController.java
@@ -89,6 +89,7 @@
private final Handler mHandler;
private final WiredHeadsetManager mWiredHeadsetManager;
private Set<AudioRoute> mAvailableRoutes;
+ private Set<AudioRoute> mCallSupportedRoutes;
private AudioRoute mCurrentRoute;
private AudioRoute mEarpieceWiredRoute;
private AudioRoute mSpeakerDockRoute;
@@ -104,6 +105,7 @@
private StatusBarNotifier mStatusBarNotifier;
private FeatureFlags mFeatureFlags;
private int mFocusType;
+ private int mCallSupportedRouteMask = -1;
private boolean mIsScoAudioConnected;
private final Object mLock = new Object();
private final TelecomSystem.SyncRoot mTelecomLock;
@@ -314,6 +316,9 @@
handleExitPendingRoute();
break;
case UPDATE_SYSTEM_AUDIO_ROUTE:
+ // Based on the available routes for foreground call, adjust routing.
+ updateRouteForForeground();
+ // Force update to notify all ICS/CS.
updateCallAudioState(new CallAudioState(mIsMute,
mCallAudioState.getRoute(),
mCallAudioState.getSupportedRouteMask(),
@@ -330,6 +335,7 @@
@Override
public void initialize() {
mAvailableRoutes = new HashSet<>();
+ mCallSupportedRoutes = new HashSet<>();
mBluetoothRoutes = new LinkedHashMap<>();
mActiveDeviceCache = new HashMap<>();
mActiveDeviceCache.put(AudioRoute.TYPE_BLUETOOTH_SCO, null);
@@ -485,7 +491,8 @@
}
private void routeTo(boolean active, AudioRoute destRoute) {
- if (!destRoute.equals(mStreamingRoute) && !getAvailableRoutes().contains(destRoute)) {
+ if (destRoute == null || (!destRoute.equals(mStreamingRoute)
+ && !getCallSupportedRoutes().contains(destRoute))) {
Log.i(this, "Ignore routing to unavailable route: %s", destRoute);
return;
}
@@ -510,7 +517,7 @@
Log.i(this, "Enter pending route, orig%s(active=%b), dest%s(active=%b)", mCurrentRoute,
mIsActive, destRoute, active);
// route to pending route
- if (getAvailableRoutes().contains(mCurrentRoute)) {
+ if (getCallSupportedRoutes().contains(mCurrentRoute)) {
mPendingAudioRoute.setOrigRoute(mIsActive, mCurrentRoute);
} else {
// Avoid waiting for pending messages for an unavailable route
@@ -841,7 +848,7 @@
public void handleSwitchEarpiece() {
AudioRoute earpieceRoute = mTypeRoutes.get(AudioRoute.TYPE_EARPIECE);
- if (earpieceRoute != null && getAvailableRoutes().contains(earpieceRoute)) {
+ if (earpieceRoute != null && getCallSupportedRoutes().contains(earpieceRoute)) {
routeTo(mIsActive, earpieceRoute);
} else {
Log.i(this, "ignore switch earpiece request");
@@ -856,7 +863,7 @@
bluetoothRoute = getArbitraryBluetoothDevice();
bluetoothDevice = mBluetoothRoutes.get(bluetoothRoute);
} else {
- for (AudioRoute route : getAvailableRoutes()) {
+ for (AudioRoute route : getCallSupportedRoutes()) {
if (Objects.equals(address, route.getBluetoothAddress())) {
bluetoothRoute = route;
bluetoothDevice = mBluetoothRoutes.get(route);
@@ -894,7 +901,7 @@
private void handleSwitchHeadset() {
AudioRoute headsetRoute = mTypeRoutes.get(AudioRoute.TYPE_WIRED);
- if (headsetRoute != null && getAvailableRoutes().contains(headsetRoute)) {
+ if (headsetRoute != null && getCallSupportedRoutes().contains(headsetRoute)) {
routeTo(mIsActive, headsetRoute);
} else {
Log.i(this, "ignore switch headset request");
@@ -902,7 +909,7 @@
}
private void handleSwitchSpeaker() {
- if (mSpeakerDockRoute != null && getAvailableRoutes().contains(mSpeakerDockRoute)) {
+ if (mSpeakerDockRoute != null && getCallSupportedRoutes().contains(mSpeakerDockRoute)) {
routeTo(mIsActive, mSpeakerDockRoute);
} else {
Log.i(this, "ignore switch speaker request");
@@ -920,7 +927,8 @@
// Update status bar notification if we are in a call.
mStatusBarNotifier.notifySpeakerphone(mCallsManager.hasAnyCalls());
} else {
- if (mSpeakerDockRoute != null && getAvailableRoutes().contains(mSpeakerDockRoute)) {
+ if (mSpeakerDockRoute != null && getCallSupportedRoutes()
+ .contains(mSpeakerDockRoute)) {
routeTo(mIsActive, mSpeakerDockRoute);
// Since the route switching triggered by this message, we need to manually send it
// again so that we won't stuck in the pending route
@@ -984,7 +992,7 @@
synchronized (mLock) {
int routeMask = 0;
Set<BluetoothDevice> availableBluetoothDevices = new HashSet<>();
- for (AudioRoute route : getAvailableRoutes()) {
+ for (AudioRoute route : getCallSupportedRoutes()) {
routeMask |= ROUTE_MAP.get(route.getType());
if (BT_AUDIO_ROUTE_TYPES.contains(route.getType())) {
BluetoothDevice deviceToAdd = mBluetoothRoutes.get(route);
@@ -1004,6 +1012,7 @@
}
}
}
+
updateCallAudioState(new CallAudioState(mIsMute, mCallAudioState.getRoute(), routeMask,
mCallAudioState.getActiveBluetoothDevice(), availableBluetoothDevices));
}
@@ -1015,14 +1024,58 @@
mCallAudioState.getSupportedBluetoothDevices()));
}
+ /**
+ * Retrieves the current call's supported audio route and adjusts the audio routing if the
+ * current route isn't supported.
+ */
+ private void updateRouteForForeground() {
+ boolean updatedRouteForCall = updateCallSupportedAudioRoutes();
+ // Ensure that current call audio state has updated routes for current call.
+ if (updatedRouteForCall) {
+ mCallAudioState = new CallAudioState(mIsMute, mCallAudioState.getRoute(),
+ mCallSupportedRouteMask, mCallAudioState.getActiveBluetoothDevice(),
+ mCallAudioState.getSupportedBluetoothDevices());
+ // Update audio route if foreground call doesn't support the current route.
+ if ((mCallSupportedRouteMask & mCallAudioState.getRoute()) == 0) {
+ routeTo(mIsActive, getBaseRoute(true, null));
+ }
+ }
+ }
+
+ /**
+ * Update supported audio routes for the foreground call if present.
+ */
+ private boolean updateCallSupportedAudioRoutes() {
+ int availableRouteMask = 0;
+ Call foregroundCall = mCallsManager.getForegroundCall();
+ if (foregroundCall != null) {
+ int foregroundCallSupportedRouteMask = foregroundCall.getSupportedAudioRoutes();
+ for (AudioRoute route : getAvailableRoutes()) {
+ int routeType = ROUTE_MAP.get(route.getType());
+ availableRouteMask |= routeType;
+ if ((routeType & foregroundCallSupportedRouteMask) == routeType) {
+ mCallSupportedRoutes.add(route);
+ }
+ }
+ mCallSupportedRouteMask = availableRouteMask & foregroundCallSupportedRouteMask;
+ return true;
+ } else {
+ mCallSupportedRoutes.clear();
+ mCallSupportedRouteMask = -1;
+ return false;
+ }
+ }
+
private void updateCallAudioState(CallAudioState newCallAudioState) {
- Log.i(this, "updateCallAudioState: updating call audio state to %s", newCallAudioState);
- CallAudioState oldState = mCallAudioState;
- mCallAudioState = newCallAudioState;
- // Update status bar notification
- mStatusBarNotifier.notifyMute(newCallAudioState.isMuted());
- mCallsManager.onCallAudioStateChanged(oldState, mCallAudioState);
- updateAudioStateForTrackedCalls(mCallAudioState);
+ synchronized (mTelecomLock) {
+ Log.i(this, "updateCallAudioState: updating call audio state to %s", newCallAudioState);
+ CallAudioState oldState = mCallAudioState;
+ mCallAudioState = newCallAudioState;
+ // Update status bar notification
+ mStatusBarNotifier.notifyMute(newCallAudioState.isMuted());
+ mCallsManager.onCallAudioStateChanged(oldState, mCallAudioState);
+ updateAudioStateForTrackedCalls(mCallAudioState);
+ }
}
private void updateAudioStateForTrackedCalls(CallAudioState newCallAudioState) {
@@ -1080,11 +1133,17 @@
// are only wearables available.
AudioRoute activeWatchOrNonWatchDeviceRoute =
getActiveWatchOrNonWatchDeviceRoute(btAddressToExclude);
- if (mBluetoothRoutes.isEmpty() || !includeBluetooth
- || activeWatchOrNonWatchDeviceRoute == null) {
+ if ((!mCallSupportedRoutes.isEmpty() && (mCallSupportedRouteMask
+ & CallAudioState.ROUTE_BLUETOOTH) == 0) || mBluetoothRoutes.isEmpty()
+ || !includeBluetooth || activeWatchOrNonWatchDeviceRoute == null) {
Log.i(this, "getPreferredAudioRouteFromDefault: Audio routing defaulting to "
+ "available non-BT route.");
- AudioRoute defaultRoute = mEarpieceWiredRoute != null
+ boolean callSupportsEarpieceWiredRoute = mCallSupportedRoutes.isEmpty()
+ || mCallSupportedRoutes.contains(mEarpieceWiredRoute);
+ // If call supported route doesn't contain earpiece/wired/BT, it should have speaker
+ // enabled. Otherwise, no routes would be supported for the call which should never be
+ // the case.
+ AudioRoute defaultRoute = mEarpieceWiredRoute != null && callSupportsEarpieceWiredRoute
? mEarpieceWiredRoute
: mSpeakerDockRoute;
// Ensure that we default to speaker route if we're in a video call, but disregard it if
@@ -1136,6 +1195,14 @@
}
}
+ public Set<AudioRoute> getCallSupportedRoutes() {
+ if (mCurrentRoute.equals(mStreamingRoute)) {
+ return mStreamingRoutes;
+ } else {
+ return mCallSupportedRoutes.isEmpty() ? mAvailableRoutes : mCallSupportedRoutes;
+ }
+ }
+
public AudioRoute getCurrentRoute() {
return mCurrentRoute;
}
@@ -1155,7 +1222,7 @@
if (destRoute == null || (destRoute.getBluetoothAddress() != null && !includeBluetooth)) {
destRoute = getPreferredAudioRouteFromDefault(includeBluetooth, btAddressToExclude);
}
- if (destRoute != null && !getAvailableRoutes().contains(destRoute)) {
+ if (destRoute != null && !getCallSupportedRoutes().contains(destRoute)) {
destRoute = null;
}
Log.i(this, "getBaseRoute - audio routing to %s", destRoute);
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java
index f8cc857..942eb18 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java
@@ -72,6 +72,7 @@
import com.android.server.telecom.Call;
import com.android.server.telecom.CallAudioManager;
import com.android.server.telecom.CallAudioRouteController;
+import com.android.server.telecom.CallAudioRouteStateMachine;
import com.android.server.telecom.CallsManager;
import com.android.server.telecom.PendingAudioRoute;
import com.android.server.telecom.StatusBarNotifier;
@@ -152,6 +153,7 @@
when(mCallsManager.getCurrentUserHandle()).thenReturn(
new UserHandle(UserHandle.USER_SYSTEM));
when(mCallsManager.getLock()).thenReturn(mLock);
+ when(mCallsManager.getForegroundCall()).thenReturn(mCall);
when(mBluetoothRouteManager.getDeviceManager()).thenReturn(mBluetoothDeviceManager);
when(mBluetoothDeviceManager.connectAudio(any(BluetoothDevice.class), anyInt()))
.thenReturn(true);
@@ -172,6 +174,7 @@
mController.setCallAudioManager(mCallAudioManager);
when(mCallAudioManager.getForegroundCall()).thenReturn(mCall);
when(mCall.getVideoState()).thenReturn(VideoProfile.STATE_AUDIO_ONLY);
+ when(mCall.getSupportedAudioRoutes()).thenReturn(CallAudioState.ROUTE_ALL);
when(mFeatureFlags.ignoreAutoRouteToWatchDevice()).thenReturn(false);
when(mFeatureFlags.useRefactoredAudioRouteSwitching()).thenReturn(true);
}
@@ -799,6 +802,35 @@
any(CallAudioState.class), eq(expectedState));
}
+ @SmallTest
+ @Test
+ public void testUpdateRouteForForeground() {
+ mController.initialize();
+ mController.sendMessageWithSessionInfo(BT_DEVICE_ADDED, AudioRoute.TYPE_BLUETOOTH_SCO,
+ BLUETOOTH_DEVICE_1);
+
+ CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH,
+ CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH
+ | CallAudioState.ROUTE_SPEAKER, BLUETOOTH_DEVICE_1, BLUETOOTH_DEVICES);
+ mController.sendMessageWithSessionInfo(BT_ACTIVE_DEVICE_PRESENT,
+ AudioRoute.TYPE_BLUETOOTH_SCO, BT_ADDRESS_1);
+ verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+ any(CallAudioState.class), eq(expectedState));
+
+ // Ensure that supported routes is updated along with the current route to reflect the
+ // foreground call's supported audio routes.
+ when(mCall.getSupportedAudioRoutes()).thenReturn(CallAudioState.ROUTE_SPEAKER);
+ mController.sendMessageWithSessionInfo(
+ CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE);
+ mController.sendMessageWithSessionInfo(SPEAKER_ON);
+ expectedState = new CallAudioState(false, CallAudioState.ROUTE_SPEAKER,
+ CallAudioState.ROUTE_SPEAKER, null, BLUETOOTH_DEVICES);
+ verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+ any(CallAudioState.class), eq(expectedState));
+ assertEquals(3, mController.getAvailableRoutes().size());
+ assertEquals(1, mController.getCallSupportedRoutes().size());
+ }
+
private void verifyConnectBluetoothDevice(int audioType) {
mController.initialize();
mController.setActive(true);