Loading flags/telecom_callaudioroutestatemachine_flags.aconfig +8 −0 Original line number Diff line number Diff line Loading @@ -17,6 +17,14 @@ flag { bug: "306395598" } # OWNER=pmadapurmath TARGET=25Q1 flag { name: "resolve_active_bt_routing_and_bt_timing_issue" namespace: "telecom" description: "Resolve the active BT device routing and flaky timing issues noted in BT routing." bug: "372029371" } # OWNER=tgunn TARGET=24Q3 flag { name: "ensure_audio_mode_updates_on_foreground_call_change" Loading src/com/android/server/telecom/AudioRoute.java +23 −5 Original line number Diff line number Diff line Loading @@ -320,13 +320,13 @@ public class AudioRoute { AudioManager audioManager, BluetoothRouteManager bluetoothRouteManager) { Log.i(this, "onOrigRouteAsPendingRoute: active (%b), type (%d)", active, mAudioRouteType); if (active) { if (mAudioRouteType == TYPE_SPEAKER) { pendingAudioRoute.addMessage(SPEAKER_OFF, null); } int result = clearCommunicationDevice(pendingAudioRoute, bluetoothRouteManager, audioManager); if (mAudioRouteType == TYPE_SPEAKER) { pendingAudioRoute.addMessage(SPEAKER_OFF, null); } else if (mAudioRouteType == TYPE_BLUETOOTH_SCO && result == BluetoothStatusCodes.SUCCESS) { // Only send BT_AUDIO_DISCONNECTED for SCO if disconnect was successful. if (mAudioRouteType == TYPE_BLUETOOTH_SCO && result == BluetoothStatusCodes.SUCCESS) { pendingAudioRoute.addMessage(BT_AUDIO_DISCONNECTED, mBluetoothAddress); } } Loading Loading @@ -407,8 +407,26 @@ public class AudioRoute { } if (result == BluetoothStatusCodes.SUCCESS) { if (pendingAudioRoute.getFeatureFlags().resolveActiveBtRoutingAndBtTimingIssue()) { maybeClearConnectedPendingMessages(pendingAudioRoute); } pendingAudioRoute.setCommunicationDeviceType(AudioRoute.TYPE_INVALID); } return result; } private void maybeClearConnectedPendingMessages(PendingAudioRoute pendingAudioRoute) { // If we're still waiting on BT_AUDIO_CONNECTED/SPEAKER_ON but have routed out of it // since and disconnected the device, then remove that message so we aren't waiting for // it in the message queue. if (mAudioRouteType == TYPE_BLUETOOTH_SCO) { Log.i(this, "clearCommunicationDevice: Clearing pending " + "BT_AUDIO_CONNECTED messages."); pendingAudioRoute.clearPendingMessage( new Pair<>(BT_AUDIO_CONNECTED, mBluetoothAddress)); } else if (mAudioRouteType == TYPE_SPEAKER) { Log.i(this, "clearCommunicationDevice: Clearing pending SPEAKER_ON messages."); pendingAudioRoute.clearPendingMessage(new Pair<>(SPEAKER_ON, null)); } } } src/com/android/server/telecom/CallAudioRouteController.java +39 −4 Original line number Diff line number Diff line Loading @@ -522,7 +522,8 @@ public class CallAudioRouteController implements CallAudioRouteAdapter { + "%s(active=%b)", mPendingAudioRoute.getDestRoute(), mIsActive, destRoute, active); // Ensure we don't keep waiting for SPEAKER_ON if dest route gets overridden. if (active && mPendingAudioRoute.getDestRoute().getType() == TYPE_SPEAKER) { if (!mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue() && active && mPendingAudioRoute.getDestRoute().getType() == TYPE_SPEAKER) { mPendingAudioRoute.clearPendingMessage(new Pair<>(SPEAKER_ON, null)); } // override pending route while keep waiting for still pending messages for the Loading Loading @@ -930,8 +931,26 @@ public class CallAudioRouteController implements CallAudioRouteAdapter { } private void handleSwitchBaselineRoute(boolean includeBluetooth, String btAddressToExclude) { Log.i(this, "handleSwitchBaselineRoute: includeBluetooth: %b, " + "btAddressToExclude: %s", includeBluetooth, btAddressToExclude); boolean areExcludedBtAndDestBtSame = btAddressToExclude != null && Objects.equals(btAddressToExclude, mPendingAudioRoute.getDestRoute() .getBluetoothAddress()); Pair<Integer, String> btDevicePendingMsg = new Pair<>(BT_AUDIO_CONNECTED, btAddressToExclude); // If SCO is once again connected or there's a pending message for BT_AUDIO_CONNECTED, then // we know that the device has reconnected or is in the middle of connecting. Ignore routing // out of this BT device. if (mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue() && areExcludedBtAndDestBtSame && (mIsScoAudioConnected || mPendingAudioRoute.getPendingMessages() .contains(btDevicePendingMsg))) { Log.i(this, "BT device with address (%s) is currently connecting/connected. " + "Ignore route switch."); } else { routeTo(mIsActive, calculateBaselineRoute(includeBluetooth, btAddressToExclude)); } } private void handleSpeakerOn() { if (isPending()) { Loading Loading @@ -1322,7 +1341,7 @@ public class CallAudioRouteController implements CallAudioRouteAdapter { return getMostRecentlyActiveBtRoute(btAddressToExclude); } List<AudioRoute> bluetoothRoutes = mBluetoothRoutes.keySet().stream().toList(); List<AudioRoute> bluetoothRoutes = getAvailableBluetoothDevicesForRouting(); // Traverse the routes from the most recently active recorded devices first. AudioRoute nonWatchDeviceRoute = null; for (int i = bluetoothRoutes.size() - 1; i >= 0; i--) { Loading @@ -1341,7 +1360,7 @@ public class CallAudioRouteController implements CallAudioRouteAdapter { return bluetoothRoutes.get(0); } // Record the first occurrence of a non-watch device route if found. if (!mBluetoothRouteManager.isWatch(device) && nonWatchDeviceRoute == null) { if (!mBluetoothRouteManager.isWatch(device)) { nonWatchDeviceRoute = route; break; } Loading @@ -1351,6 +1370,22 @@ public class CallAudioRouteController implements CallAudioRouteAdapter { return nonWatchDeviceRoute; } private List<AudioRoute> getAvailableBluetoothDevicesForRouting() { List<AudioRoute> bluetoothRoutes = new ArrayList<>(mBluetoothRoutes.keySet()); if (!mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()) { return bluetoothRoutes; } // Consider the active device (BT_ACTIVE_DEVICE_PRESENT) if it exists first. AudioRoute activeDeviceRoute = getArbitraryBluetoothDevice(); if (activeDeviceRoute != null && (bluetoothRoutes.isEmpty() || !bluetoothRoutes.get(bluetoothRoutes.size() - 1).equals(activeDeviceRoute))) { Log.i(this, "getActiveWatchOrNonWatchDeviceRoute: active BT device (%s) present." + "Considering this device for selection first.", activeDeviceRoute); bluetoothRoutes.add(activeDeviceRoute); } return bluetoothRoutes; } /** * Returns the most actively reported bluetooth route excluding the passed in route. */ Loading src/com/android/server/telecom/PendingAudioRoute.java +8 −0 Original line number Diff line number Diff line Loading @@ -130,6 +130,10 @@ public class PendingAudioRoute { mPendingMessages.remove(message); } public Set<Pair<Integer, String>> getPendingMessages() { return mPendingMessages; } public boolean isActive() { return mActive; } Loading @@ -146,4 +150,8 @@ public class PendingAudioRoute { public void overrideDestRoute(AudioRoute route) { mDestRoute = route; } public FeatureFlags getFeatureFlags() { return mFeatureFlags; } } tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java +123 −0 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ import static com.android.server.telecom.CallAudioRouteAdapter.ACTIVE_FOCUS; import static com.android.server.telecom.CallAudioRouteAdapter.BT_ACTIVE_DEVICE_GONE; import static com.android.server.telecom.CallAudioRouteAdapter.BT_ACTIVE_DEVICE_PRESENT; import static com.android.server.telecom.CallAudioRouteAdapter.BT_AUDIO_CONNECTED; import static com.android.server.telecom.CallAudioRouteAdapter.BT_AUDIO_DISCONNECTED; import static com.android.server.telecom.CallAudioRouteAdapter.BT_DEVICE_ADDED; import static com.android.server.telecom.CallAudioRouteAdapter.BT_DEVICE_REMOVED; import static com.android.server.telecom.CallAudioRouteAdapter.CONNECT_DOCK; Loading @@ -40,6 +41,8 @@ import static com.android.server.telecom.CallAudioRouteAdapter.USER_SWITCH_BLUET import static com.android.server.telecom.CallAudioRouteAdapter.USER_SWITCH_EARPIECE; import static com.android.server.telecom.CallAudioRouteAdapter.USER_SWITCH_HEADSET; import static com.android.server.telecom.CallAudioRouteAdapter.USER_SWITCH_SPEAKER; import static com.android.server.telecom.CallAudioRouteController.INCLUDE_BLUETOOTH_IN_BASELINE; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; Loading Loading @@ -189,6 +192,7 @@ public class CallAudioRouteControllerTest extends TelecomTestCase { when(mCall.getSupportedAudioRoutes()).thenReturn(CallAudioState.ROUTE_ALL); when(mFeatureFlags.ignoreAutoRouteToWatchDevice()).thenReturn(false); when(mFeatureFlags.useRefactoredAudioRouteSwitching()).thenReturn(true); when(mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()).thenReturn(false); } @After Loading Loading @@ -908,6 +912,125 @@ public class CallAudioRouteControllerTest extends TelecomTestCase { } @SmallTest @Test public void testMimicVoiceDialWithBt() { when(mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()).thenReturn(true); mController.initialize(); mController.setActive(true); mController.sendMessageWithSessionInfo(BT_DEVICE_ADDED, AudioRoute.TYPE_BLUETOOTH_SCO, BLUETOOTH_DEVICE_1); CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_EARPIECE, CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH | CallAudioState.ROUTE_SPEAKER, null, BLUETOOTH_DEVICES); verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged( any(CallAudioState.class), eq(expectedState)); mController.sendMessageWithSessionInfo(SWITCH_FOCUS, ACTIVE_FOCUS, 0); // Mimic behavior of controller processing BT_AUDIO_DISCONNECTED mController.sendMessageWithSessionInfo(SWITCH_BASELINE_ROUTE, INCLUDE_BLUETOOTH_IN_BASELINE, BLUETOOTH_DEVICE_1.getAddress()); // Process BT_AUDIO_CONNECTED from connecting to BT device in active focus request. mController.setIsScoAudioConnected(true); mController.sendMessageWithSessionInfo(BT_AUDIO_CONNECTED, 0, BLUETOOTH_DEVICE_1); // Verify SCO not disconnected and route stays on connected BT device. verify(mBluetoothDeviceManager, timeout(TEST_TIMEOUT).times(0)).disconnectSco(); expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH, CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH | CallAudioState.ROUTE_SPEAKER, BLUETOOTH_DEVICE_1, BLUETOOTH_DEVICES); verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged( any(CallAudioState.class), eq(expectedState)); } @SmallTest @Test public void testTransactionalCallBtConnectingAndSwitchCallEndpoint() { when(mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()).thenReturn(true); mController.initialize(); mController.setActive(true); mController.sendMessageWithSessionInfo(BT_DEVICE_ADDED, AudioRoute.TYPE_BLUETOOTH_SCO, BLUETOOTH_DEVICE_1); CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_EARPIECE, CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH | CallAudioState.ROUTE_SPEAKER, null, BLUETOOTH_DEVICES); verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged( any(CallAudioState.class), eq(expectedState)); mController.sendMessageWithSessionInfo(BT_ACTIVE_DEVICE_PRESENT, AudioRoute.TYPE_BLUETOOTH_SCO, BT_ADDRESS_1); // Omit sending BT_AUDIO_CONNECTED to mimic scenario where BT is still connecting and user // switches to speaker. mController.sendMessageWithSessionInfo(USER_SWITCH_SPEAKER); mController.sendMessageWithSessionInfo(SPEAKER_ON); mController.sendMessageWithSessionInfo(BT_AUDIO_DISCONNECTED, 0, BLUETOOTH_DEVICE_1); // Verify SCO disconnected verify(mBluetoothDeviceManager, timeout(TEST_TIMEOUT)).disconnectSco(); // Verify audio properly routes into speaker. expectedState = new CallAudioState(false, CallAudioState.ROUTE_SPEAKER, CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH | CallAudioState.ROUTE_SPEAKER, null, BLUETOOTH_DEVICES); verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged( any(CallAudioState.class), eq(expectedState)); } @Test @SmallTest public void testBluetoothRouteToActiveDevice() { when(mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()).thenReturn(true); // Connect first BT device. verifyConnectBluetoothDevice(AudioRoute.TYPE_BLUETOOTH_SCO); // Connect another BT device. String scoDeviceAddress = "00:00:00:00:00:03"; BluetoothDevice scoDevice = BluetoothRouteManagerTest.makeBluetoothDevice(scoDeviceAddress); BLUETOOTH_DEVICES.add(scoDevice); mController.sendMessageWithSessionInfo(BT_DEVICE_ADDED, AudioRoute.TYPE_BLUETOOTH_SCO, scoDevice); mController.sendMessageWithSessionInfo(BT_ACTIVE_DEVICE_PRESENT, AudioRoute.TYPE_BLUETOOTH_SCO, scoDeviceAddress); mController.sendMessageWithSessionInfo(BT_AUDIO_DISCONNECTED, 0, BLUETOOTH_DEVICE_1); mController.sendMessageWithSessionInfo(BT_AUDIO_CONNECTED, 0, scoDevice); CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH, CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH | CallAudioState.ROUTE_SPEAKER, scoDevice, BLUETOOTH_DEVICES); verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged( any(CallAudioState.class), eq(expectedState)); // Mimic behavior when inactive headset is used to answer the call (i.e. tap headset). In // this case, the inactive BT device will become the active device (reported to us from BT // stack to controller via BT_ACTIVE_DEVICE_PRESENT). mController.sendMessageWithSessionInfo(BT_ACTIVE_DEVICE_PRESENT, AudioRoute.TYPE_BLUETOOTH_SCO, BLUETOOTH_DEVICE_1.getAddress()); mController.sendMessageWithSessionInfo(BT_AUDIO_DISCONNECTED, 0, scoDevice); mController.sendMessageWithSessionInfo(BT_AUDIO_CONNECTED, 0, BLUETOOTH_DEVICE_1); // Verify audio routed to BLUETOOTH_DEVICE_1 expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH, CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH | CallAudioState.ROUTE_SPEAKER, BLUETOOTH_DEVICE_1, BLUETOOTH_DEVICES); verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged( any(CallAudioState.class), eq(expectedState)); // Now switch call to active focus so that base route can be recalculated. mController.sendMessageWithSessionInfo(SWITCH_FOCUS, ACTIVE_FOCUS, 0); expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH, CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH | CallAudioState.ROUTE_SPEAKER, BLUETOOTH_DEVICE_1, BLUETOOTH_DEVICES); // Verify that audio is still routed into BLUETOOTH_DEVICE_1 and not the 2nd BT device. verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged( any(CallAudioState.class), eq(expectedState)); // Clean up BLUETOOTH_DEVICES for subsequent tests. BLUETOOTH_DEVICES.remove(scoDevice); } private void verifyConnectBluetoothDevice(int audioType) { mController.initialize(); mController.setActive(true); Loading Loading
flags/telecom_callaudioroutestatemachine_flags.aconfig +8 −0 Original line number Diff line number Diff line Loading @@ -17,6 +17,14 @@ flag { bug: "306395598" } # OWNER=pmadapurmath TARGET=25Q1 flag { name: "resolve_active_bt_routing_and_bt_timing_issue" namespace: "telecom" description: "Resolve the active BT device routing and flaky timing issues noted in BT routing." bug: "372029371" } # OWNER=tgunn TARGET=24Q3 flag { name: "ensure_audio_mode_updates_on_foreground_call_change" Loading
src/com/android/server/telecom/AudioRoute.java +23 −5 Original line number Diff line number Diff line Loading @@ -320,13 +320,13 @@ public class AudioRoute { AudioManager audioManager, BluetoothRouteManager bluetoothRouteManager) { Log.i(this, "onOrigRouteAsPendingRoute: active (%b), type (%d)", active, mAudioRouteType); if (active) { if (mAudioRouteType == TYPE_SPEAKER) { pendingAudioRoute.addMessage(SPEAKER_OFF, null); } int result = clearCommunicationDevice(pendingAudioRoute, bluetoothRouteManager, audioManager); if (mAudioRouteType == TYPE_SPEAKER) { pendingAudioRoute.addMessage(SPEAKER_OFF, null); } else if (mAudioRouteType == TYPE_BLUETOOTH_SCO && result == BluetoothStatusCodes.SUCCESS) { // Only send BT_AUDIO_DISCONNECTED for SCO if disconnect was successful. if (mAudioRouteType == TYPE_BLUETOOTH_SCO && result == BluetoothStatusCodes.SUCCESS) { pendingAudioRoute.addMessage(BT_AUDIO_DISCONNECTED, mBluetoothAddress); } } Loading Loading @@ -407,8 +407,26 @@ public class AudioRoute { } if (result == BluetoothStatusCodes.SUCCESS) { if (pendingAudioRoute.getFeatureFlags().resolveActiveBtRoutingAndBtTimingIssue()) { maybeClearConnectedPendingMessages(pendingAudioRoute); } pendingAudioRoute.setCommunicationDeviceType(AudioRoute.TYPE_INVALID); } return result; } private void maybeClearConnectedPendingMessages(PendingAudioRoute pendingAudioRoute) { // If we're still waiting on BT_AUDIO_CONNECTED/SPEAKER_ON but have routed out of it // since and disconnected the device, then remove that message so we aren't waiting for // it in the message queue. if (mAudioRouteType == TYPE_BLUETOOTH_SCO) { Log.i(this, "clearCommunicationDevice: Clearing pending " + "BT_AUDIO_CONNECTED messages."); pendingAudioRoute.clearPendingMessage( new Pair<>(BT_AUDIO_CONNECTED, mBluetoothAddress)); } else if (mAudioRouteType == TYPE_SPEAKER) { Log.i(this, "clearCommunicationDevice: Clearing pending SPEAKER_ON messages."); pendingAudioRoute.clearPendingMessage(new Pair<>(SPEAKER_ON, null)); } } }
src/com/android/server/telecom/CallAudioRouteController.java +39 −4 Original line number Diff line number Diff line Loading @@ -522,7 +522,8 @@ public class CallAudioRouteController implements CallAudioRouteAdapter { + "%s(active=%b)", mPendingAudioRoute.getDestRoute(), mIsActive, destRoute, active); // Ensure we don't keep waiting for SPEAKER_ON if dest route gets overridden. if (active && mPendingAudioRoute.getDestRoute().getType() == TYPE_SPEAKER) { if (!mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue() && active && mPendingAudioRoute.getDestRoute().getType() == TYPE_SPEAKER) { mPendingAudioRoute.clearPendingMessage(new Pair<>(SPEAKER_ON, null)); } // override pending route while keep waiting for still pending messages for the Loading Loading @@ -930,8 +931,26 @@ public class CallAudioRouteController implements CallAudioRouteAdapter { } private void handleSwitchBaselineRoute(boolean includeBluetooth, String btAddressToExclude) { Log.i(this, "handleSwitchBaselineRoute: includeBluetooth: %b, " + "btAddressToExclude: %s", includeBluetooth, btAddressToExclude); boolean areExcludedBtAndDestBtSame = btAddressToExclude != null && Objects.equals(btAddressToExclude, mPendingAudioRoute.getDestRoute() .getBluetoothAddress()); Pair<Integer, String> btDevicePendingMsg = new Pair<>(BT_AUDIO_CONNECTED, btAddressToExclude); // If SCO is once again connected or there's a pending message for BT_AUDIO_CONNECTED, then // we know that the device has reconnected or is in the middle of connecting. Ignore routing // out of this BT device. if (mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue() && areExcludedBtAndDestBtSame && (mIsScoAudioConnected || mPendingAudioRoute.getPendingMessages() .contains(btDevicePendingMsg))) { Log.i(this, "BT device with address (%s) is currently connecting/connected. " + "Ignore route switch."); } else { routeTo(mIsActive, calculateBaselineRoute(includeBluetooth, btAddressToExclude)); } } private void handleSpeakerOn() { if (isPending()) { Loading Loading @@ -1322,7 +1341,7 @@ public class CallAudioRouteController implements CallAudioRouteAdapter { return getMostRecentlyActiveBtRoute(btAddressToExclude); } List<AudioRoute> bluetoothRoutes = mBluetoothRoutes.keySet().stream().toList(); List<AudioRoute> bluetoothRoutes = getAvailableBluetoothDevicesForRouting(); // Traverse the routes from the most recently active recorded devices first. AudioRoute nonWatchDeviceRoute = null; for (int i = bluetoothRoutes.size() - 1; i >= 0; i--) { Loading @@ -1341,7 +1360,7 @@ public class CallAudioRouteController implements CallAudioRouteAdapter { return bluetoothRoutes.get(0); } // Record the first occurrence of a non-watch device route if found. if (!mBluetoothRouteManager.isWatch(device) && nonWatchDeviceRoute == null) { if (!mBluetoothRouteManager.isWatch(device)) { nonWatchDeviceRoute = route; break; } Loading @@ -1351,6 +1370,22 @@ public class CallAudioRouteController implements CallAudioRouteAdapter { return nonWatchDeviceRoute; } private List<AudioRoute> getAvailableBluetoothDevicesForRouting() { List<AudioRoute> bluetoothRoutes = new ArrayList<>(mBluetoothRoutes.keySet()); if (!mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()) { return bluetoothRoutes; } // Consider the active device (BT_ACTIVE_DEVICE_PRESENT) if it exists first. AudioRoute activeDeviceRoute = getArbitraryBluetoothDevice(); if (activeDeviceRoute != null && (bluetoothRoutes.isEmpty() || !bluetoothRoutes.get(bluetoothRoutes.size() - 1).equals(activeDeviceRoute))) { Log.i(this, "getActiveWatchOrNonWatchDeviceRoute: active BT device (%s) present." + "Considering this device for selection first.", activeDeviceRoute); bluetoothRoutes.add(activeDeviceRoute); } return bluetoothRoutes; } /** * Returns the most actively reported bluetooth route excluding the passed in route. */ Loading
src/com/android/server/telecom/PendingAudioRoute.java +8 −0 Original line number Diff line number Diff line Loading @@ -130,6 +130,10 @@ public class PendingAudioRoute { mPendingMessages.remove(message); } public Set<Pair<Integer, String>> getPendingMessages() { return mPendingMessages; } public boolean isActive() { return mActive; } Loading @@ -146,4 +150,8 @@ public class PendingAudioRoute { public void overrideDestRoute(AudioRoute route) { mDestRoute = route; } public FeatureFlags getFeatureFlags() { return mFeatureFlags; } }
tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java +123 −0 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ import static com.android.server.telecom.CallAudioRouteAdapter.ACTIVE_FOCUS; import static com.android.server.telecom.CallAudioRouteAdapter.BT_ACTIVE_DEVICE_GONE; import static com.android.server.telecom.CallAudioRouteAdapter.BT_ACTIVE_DEVICE_PRESENT; import static com.android.server.telecom.CallAudioRouteAdapter.BT_AUDIO_CONNECTED; import static com.android.server.telecom.CallAudioRouteAdapter.BT_AUDIO_DISCONNECTED; import static com.android.server.telecom.CallAudioRouteAdapter.BT_DEVICE_ADDED; import static com.android.server.telecom.CallAudioRouteAdapter.BT_DEVICE_REMOVED; import static com.android.server.telecom.CallAudioRouteAdapter.CONNECT_DOCK; Loading @@ -40,6 +41,8 @@ import static com.android.server.telecom.CallAudioRouteAdapter.USER_SWITCH_BLUET import static com.android.server.telecom.CallAudioRouteAdapter.USER_SWITCH_EARPIECE; import static com.android.server.telecom.CallAudioRouteAdapter.USER_SWITCH_HEADSET; import static com.android.server.telecom.CallAudioRouteAdapter.USER_SWITCH_SPEAKER; import static com.android.server.telecom.CallAudioRouteController.INCLUDE_BLUETOOTH_IN_BASELINE; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; Loading Loading @@ -189,6 +192,7 @@ public class CallAudioRouteControllerTest extends TelecomTestCase { when(mCall.getSupportedAudioRoutes()).thenReturn(CallAudioState.ROUTE_ALL); when(mFeatureFlags.ignoreAutoRouteToWatchDevice()).thenReturn(false); when(mFeatureFlags.useRefactoredAudioRouteSwitching()).thenReturn(true); when(mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()).thenReturn(false); } @After Loading Loading @@ -908,6 +912,125 @@ public class CallAudioRouteControllerTest extends TelecomTestCase { } @SmallTest @Test public void testMimicVoiceDialWithBt() { when(mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()).thenReturn(true); mController.initialize(); mController.setActive(true); mController.sendMessageWithSessionInfo(BT_DEVICE_ADDED, AudioRoute.TYPE_BLUETOOTH_SCO, BLUETOOTH_DEVICE_1); CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_EARPIECE, CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH | CallAudioState.ROUTE_SPEAKER, null, BLUETOOTH_DEVICES); verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged( any(CallAudioState.class), eq(expectedState)); mController.sendMessageWithSessionInfo(SWITCH_FOCUS, ACTIVE_FOCUS, 0); // Mimic behavior of controller processing BT_AUDIO_DISCONNECTED mController.sendMessageWithSessionInfo(SWITCH_BASELINE_ROUTE, INCLUDE_BLUETOOTH_IN_BASELINE, BLUETOOTH_DEVICE_1.getAddress()); // Process BT_AUDIO_CONNECTED from connecting to BT device in active focus request. mController.setIsScoAudioConnected(true); mController.sendMessageWithSessionInfo(BT_AUDIO_CONNECTED, 0, BLUETOOTH_DEVICE_1); // Verify SCO not disconnected and route stays on connected BT device. verify(mBluetoothDeviceManager, timeout(TEST_TIMEOUT).times(0)).disconnectSco(); expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH, CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH | CallAudioState.ROUTE_SPEAKER, BLUETOOTH_DEVICE_1, BLUETOOTH_DEVICES); verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged( any(CallAudioState.class), eq(expectedState)); } @SmallTest @Test public void testTransactionalCallBtConnectingAndSwitchCallEndpoint() { when(mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()).thenReturn(true); mController.initialize(); mController.setActive(true); mController.sendMessageWithSessionInfo(BT_DEVICE_ADDED, AudioRoute.TYPE_BLUETOOTH_SCO, BLUETOOTH_DEVICE_1); CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_EARPIECE, CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH | CallAudioState.ROUTE_SPEAKER, null, BLUETOOTH_DEVICES); verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged( any(CallAudioState.class), eq(expectedState)); mController.sendMessageWithSessionInfo(BT_ACTIVE_DEVICE_PRESENT, AudioRoute.TYPE_BLUETOOTH_SCO, BT_ADDRESS_1); // Omit sending BT_AUDIO_CONNECTED to mimic scenario where BT is still connecting and user // switches to speaker. mController.sendMessageWithSessionInfo(USER_SWITCH_SPEAKER); mController.sendMessageWithSessionInfo(SPEAKER_ON); mController.sendMessageWithSessionInfo(BT_AUDIO_DISCONNECTED, 0, BLUETOOTH_DEVICE_1); // Verify SCO disconnected verify(mBluetoothDeviceManager, timeout(TEST_TIMEOUT)).disconnectSco(); // Verify audio properly routes into speaker. expectedState = new CallAudioState(false, CallAudioState.ROUTE_SPEAKER, CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH | CallAudioState.ROUTE_SPEAKER, null, BLUETOOTH_DEVICES); verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged( any(CallAudioState.class), eq(expectedState)); } @Test @SmallTest public void testBluetoothRouteToActiveDevice() { when(mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()).thenReturn(true); // Connect first BT device. verifyConnectBluetoothDevice(AudioRoute.TYPE_BLUETOOTH_SCO); // Connect another BT device. String scoDeviceAddress = "00:00:00:00:00:03"; BluetoothDevice scoDevice = BluetoothRouteManagerTest.makeBluetoothDevice(scoDeviceAddress); BLUETOOTH_DEVICES.add(scoDevice); mController.sendMessageWithSessionInfo(BT_DEVICE_ADDED, AudioRoute.TYPE_BLUETOOTH_SCO, scoDevice); mController.sendMessageWithSessionInfo(BT_ACTIVE_DEVICE_PRESENT, AudioRoute.TYPE_BLUETOOTH_SCO, scoDeviceAddress); mController.sendMessageWithSessionInfo(BT_AUDIO_DISCONNECTED, 0, BLUETOOTH_DEVICE_1); mController.sendMessageWithSessionInfo(BT_AUDIO_CONNECTED, 0, scoDevice); CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH, CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH | CallAudioState.ROUTE_SPEAKER, scoDevice, BLUETOOTH_DEVICES); verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged( any(CallAudioState.class), eq(expectedState)); // Mimic behavior when inactive headset is used to answer the call (i.e. tap headset). In // this case, the inactive BT device will become the active device (reported to us from BT // stack to controller via BT_ACTIVE_DEVICE_PRESENT). mController.sendMessageWithSessionInfo(BT_ACTIVE_DEVICE_PRESENT, AudioRoute.TYPE_BLUETOOTH_SCO, BLUETOOTH_DEVICE_1.getAddress()); mController.sendMessageWithSessionInfo(BT_AUDIO_DISCONNECTED, 0, scoDevice); mController.sendMessageWithSessionInfo(BT_AUDIO_CONNECTED, 0, BLUETOOTH_DEVICE_1); // Verify audio routed to BLUETOOTH_DEVICE_1 expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH, CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH | CallAudioState.ROUTE_SPEAKER, BLUETOOTH_DEVICE_1, BLUETOOTH_DEVICES); verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged( any(CallAudioState.class), eq(expectedState)); // Now switch call to active focus so that base route can be recalculated. mController.sendMessageWithSessionInfo(SWITCH_FOCUS, ACTIVE_FOCUS, 0); expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH, CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH | CallAudioState.ROUTE_SPEAKER, BLUETOOTH_DEVICE_1, BLUETOOTH_DEVICES); // Verify that audio is still routed into BLUETOOTH_DEVICE_1 and not the 2nd BT device. verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged( any(CallAudioState.class), eq(expectedState)); // Clean up BLUETOOTH_DEVICES for subsequent tests. BLUETOOTH_DEVICES.remove(scoDevice); } private void verifyConnectBluetoothDevice(int audioType) { mController.initialize(); mController.setActive(true); Loading