Loading flags/telecom_callaudioroutestatemachine_flags.aconfig +11 −0 Original line number Diff line number Diff line Loading @@ -118,3 +118,14 @@ flag { 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 } } src/com/android/server/telecom/CallAudioRouteController.java +24 −16 Original line number Diff line number Diff line Loading @@ -306,12 +306,12 @@ public class CallAudioRouteController implements CallAudioRouteAdapter { 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(); Loading Loading @@ -889,7 +889,7 @@ public class CallAudioRouteController implements CallAudioRouteAdapter { // 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 Loading Loading @@ -1010,7 +1010,8 @@ public class CallAudioRouteController implements CallAudioRouteAdapter { } } 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 Loading @@ -1028,7 +1029,8 @@ public class CallAudioRouteController implements CallAudioRouteAdapter { 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)); } } Loading Loading @@ -1239,14 +1241,19 @@ public class CallAudioRouteController implements CallAudioRouteAdapter { 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(); 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. AudioRoute activeWatchOrNonWatchDeviceRoute = Loading Loading @@ -1345,7 +1352,7 @@ public class CallAudioRouteController implements CallAudioRouteAdapter { 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; Loading @@ -1354,8 +1361,9 @@ public class CallAudioRouteController implements CallAudioRouteAdapter { 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; Loading src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java +6 −2 Original line number Diff line number Diff line Loading @@ -270,12 +270,16 @@ public class BluetoothStateReceiver extends BroadcastReceiver { 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. Loading tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java +41 −0 Original line number Diff line number Diff line Loading @@ -37,6 +37,7 @@ import static com.android.server.telecom.CallAudioRouteAdapter.STREAMING_FORCE_D 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; Loading Loading @@ -194,6 +195,7 @@ public class CallAudioRouteControllerTest extends TelecomTestCase { when(mFeatureFlags.useRefactoredAudioRouteSwitching()).thenReturn(true); when(mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()).thenReturn(false); when(mFeatureFlags.newAudioPathSpeakerBroadcastAndUnfocusedRouting()).thenReturn(false); when(mFeatureFlags.fixUserRequestBaselineRouteVideoCall()).thenReturn(false); } @After Loading Loading @@ -1058,6 +1060,45 @@ public class CallAudioRouteControllerTest extends TelecomTestCase { 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); Loading Loading
flags/telecom_callaudioroutestatemachine_flags.aconfig +11 −0 Original line number Diff line number Diff line Loading @@ -118,3 +118,14 @@ flag { 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 } }
src/com/android/server/telecom/CallAudioRouteController.java +24 −16 Original line number Diff line number Diff line Loading @@ -306,12 +306,12 @@ public class CallAudioRouteController implements CallAudioRouteAdapter { 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(); Loading Loading @@ -889,7 +889,7 @@ public class CallAudioRouteController implements CallAudioRouteAdapter { // 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 Loading Loading @@ -1010,7 +1010,8 @@ public class CallAudioRouteController implements CallAudioRouteAdapter { } } 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 Loading @@ -1028,7 +1029,8 @@ public class CallAudioRouteController implements CallAudioRouteAdapter { 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)); } } Loading Loading @@ -1239,14 +1241,19 @@ public class CallAudioRouteController implements CallAudioRouteAdapter { 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(); 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. AudioRoute activeWatchOrNonWatchDeviceRoute = Loading Loading @@ -1345,7 +1352,7 @@ public class CallAudioRouteController implements CallAudioRouteAdapter { 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; Loading @@ -1354,8 +1361,9 @@ public class CallAudioRouteController implements CallAudioRouteAdapter { 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; Loading
src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java +6 −2 Original line number Diff line number Diff line Loading @@ -270,12 +270,16 @@ public class BluetoothStateReceiver extends BroadcastReceiver { 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. Loading
tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java +41 −0 Original line number Diff line number Diff line Loading @@ -37,6 +37,7 @@ import static com.android.server.telecom.CallAudioRouteAdapter.STREAMING_FORCE_D 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; Loading Loading @@ -194,6 +195,7 @@ public class CallAudioRouteControllerTest extends TelecomTestCase { when(mFeatureFlags.useRefactoredAudioRouteSwitching()).thenReturn(true); when(mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()).thenReturn(false); when(mFeatureFlags.newAudioPathSpeakerBroadcastAndUnfocusedRouting()).thenReturn(false); when(mFeatureFlags.fixUserRequestBaselineRouteVideoCall()).thenReturn(false); } @After Loading Loading @@ -1058,6 +1060,45 @@ public class CallAudioRouteControllerTest extends TelecomTestCase { 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); Loading