Loading src/com/android/server/telecom/CallAudioRouteController.java +25 −14 Original line number Diff line number Diff line Loading @@ -800,7 +800,6 @@ public class CallAudioRouteController implements CallAudioRouteAdapter { if (bluetoothRoute != null) { Log.i(this, "request to route to bluetooth route: %s (active=%b)", bluetoothRoute, mIsActive); updateActiveBluetoothDevice(new Pair<>(type, deviceAddress)); routeTo(mIsActive, bluetoothRoute); } else { Log.i(this, "request to route to unavailable bluetooth route - type (%s), address (%s)", Loading Loading @@ -844,10 +843,6 @@ public class CallAudioRouteController implements CallAudioRouteAdapter { // Fallback to an available route excluding the previously active device. routeTo(mIsActive, getBaseRoute(true, previouslyActiveDeviceAddress)); } // Clear out the active device for the BT audio type. if (mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()) { updateActiveBluetoothDevice(new Pair(type, null)); } } private void handleMuteChanged(boolean mute) { Loading Loading @@ -1023,16 +1018,29 @@ public class CallAudioRouteController implements CallAudioRouteAdapter { // 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 boolean isExcludedDeviceConnectingOrConnected = areExcludedBtAndDestBtSame && (mIsScoAudioConnected || mPendingAudioRoute.getPendingMessages() .contains(btDevicePendingMsg))) { .contains(btDevicePendingMsg)); // Check if the pending audio route or current route is already different from the route // including the BT device that should be excluded from route selection. boolean isCurrentOrDestRouteDifferent = btAddressToExclude != null && ((mIsPending && !btAddressToExclude.equals(mPendingAudioRoute.getDestRoute() .getBluetoothAddress())) || (!mIsPending && !btAddressToExclude.equals( mCurrentRoute.getBluetoothAddress()))); if (mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()) { if (isExcludedDeviceConnectingOrConnected) { Log.i(this, "BT device with address (%s) is currently connecting/connected. " + "Ignore route switch."); } else { + "Ignoring route switch.", btAddressToExclude); return; } else if (isCurrentOrDestRouteDifferent) { Log.i(this, "Current or pending audio route isn't routed to device with address " + "(%s). Ignoring route switch.", btAddressToExclude); return; } } routeTo(mIsActive, calculateBaselineRoute(isExplicitUserRequest, includeBluetooth, btAddressToExclude)); } } private void handleSpeakerOn() { if (isPending()) { Loading Loading @@ -1441,8 +1449,11 @@ public class CallAudioRouteController implements CallAudioRouteAdapter { continue; } // Check if the most recently active device is a watch device. if (i == (bluetoothRoutes.size() - 1) && device.equals(mCallAudioState .getActiveBluetoothDevice()) && mBluetoothRouteManager.isWatch(device)) { boolean isActiveDevice = mActiveBluetoothDevice != null && device.getAddress().equals(mActiveBluetoothDevice.second); if (i == (bluetoothRoutes.size() - 1) && mBluetoothRouteManager.isWatch(device) && (device.equals(mCallAudioState.getActiveBluetoothDevice()) || isActiveDevice)) { Log.i(this, "getActiveWatchOrNonWatchDeviceRoute: Routing to active watch - %s", bluetoothRoutes.get(0)); return bluetoothRoutes.get(0); Loading src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java +5 −8 Original line number Diff line number Diff line Loading @@ -252,17 +252,14 @@ public class BluetoothStateReceiver extends BroadcastReceiver { CallAudioRouteController audioRouteController = (CallAudioRouteController) mCallAudioRouteAdapter; if (device == null) { if (!mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()) { audioRouteController.updateActiveBluetoothDevice( new Pair(audioRouteType, null)); } // Update the active device cache immediately. audioRouteController.updateActiveBluetoothDevice(new Pair(audioRouteType, null)); mCallAudioRouteAdapter.sendMessageWithSessionInfo(BT_ACTIVE_DEVICE_GONE, audioRouteType); } else { if (!mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()) { // Update the active device cache immediately. audioRouteController.updateActiveBluetoothDevice( new Pair(audioRouteType, device.getAddress())); } mCallAudioRouteAdapter.sendMessageWithSessionInfo(BT_ACTIVE_DEVICE_PRESENT, audioRouteType, device.getAddress()); if (deviceType == BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID Loading tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java +58 −0 Original line number Diff line number Diff line Loading @@ -74,6 +74,7 @@ import android.media.audiopolicy.AudioProductStrategy; import android.os.UserHandle; import android.telecom.CallAudioState; import android.telecom.VideoProfile; import android.util.Pair; import androidx.test.filters.SmallTest; Loading Loading @@ -1099,6 +1100,63 @@ public class CallAudioRouteControllerTest extends TelecomTestCase { any(CallAudioState.class), eq(expectedState)); } @Test @SmallTest public void testRouteToWatchWhenCallAnsweredOnWatch_MultipleBtDevices() { 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 watchDevice = BluetoothRouteManagerTest.makeBluetoothDevice(scoDeviceAddress); when(mBluetoothRouteManager.isWatch(eq(watchDevice))).thenReturn(true); BLUETOOTH_DEVICES.add(watchDevice); mController.sendMessageWithSessionInfo(BT_DEVICE_ADDED, AudioRoute.TYPE_BLUETOOTH_SCO, watchDevice); CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH, CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER | CallAudioState.ROUTE_BLUETOOTH, BLUETOOTH_DEVICE_1, BLUETOOTH_DEVICES); verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged( any(CallAudioState.class), eq(expectedState)); // Signal that watch is now the active device. This is done in BluetoothStateReceiver and // then BT_ACTIVE_DEVICE_PRESENT will be sent to the controller to be processed. mController.updateActiveBluetoothDevice( new Pair<>(AudioRoute.TYPE_BLUETOOTH_SCO, watchDevice.getAddress())); // Emulate scenario with call answered on watch. Ensure at this point that audio was routed // into watch mController.sendMessageWithSessionInfo(SWITCH_FOCUS, ACTIVE_FOCUS, 0); mController.sendMessageWithSessionInfo(BT_AUDIO_CONNECTED, 0, watchDevice); mController.sendMessageWithSessionInfo(BT_AUDIO_DISCONNECTED, 0, BLUETOOTH_DEVICE_1); expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH, CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER | CallAudioState.ROUTE_BLUETOOTH, watchDevice, BLUETOOTH_DEVICES); verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged( any(CallAudioState.class), eq(expectedState)); // Hardcode signal from BT stack signaling to Telecom that watch is now the active device. // This should just be a no-op since audio was already routed when processing active focus. mController.sendMessageWithSessionInfo(BT_ACTIVE_DEVICE_PRESENT, AudioRoute.TYPE_BLUETOOTH_SCO, scoDeviceAddress); verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged( any(CallAudioState.class), eq(expectedState)); // Mimic behavior of controller processing BT_AUDIO_DISCONNECTED for BLUETOOTH_DEVICE_1 and // verify that audio remains routed to the watch and not routed to earpiece (this should // be taking into account what the BT active device is as reported to us by the BT stack). mController.sendMessageWithSessionInfo(SWITCH_BASELINE_ROUTE, INCLUDE_BLUETOOTH_IN_BASELINE, BLUETOOTH_DEVICE_1.getAddress()); verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged( any(CallAudioState.class), eq(expectedState)); BLUETOOTH_DEVICES.remove(watchDevice); } @Test @SmallTest public void testAbandonCallAudioFocusAfterCallEnd() { Loading Loading
src/com/android/server/telecom/CallAudioRouteController.java +25 −14 Original line number Diff line number Diff line Loading @@ -800,7 +800,6 @@ public class CallAudioRouteController implements CallAudioRouteAdapter { if (bluetoothRoute != null) { Log.i(this, "request to route to bluetooth route: %s (active=%b)", bluetoothRoute, mIsActive); updateActiveBluetoothDevice(new Pair<>(type, deviceAddress)); routeTo(mIsActive, bluetoothRoute); } else { Log.i(this, "request to route to unavailable bluetooth route - type (%s), address (%s)", Loading Loading @@ -844,10 +843,6 @@ public class CallAudioRouteController implements CallAudioRouteAdapter { // Fallback to an available route excluding the previously active device. routeTo(mIsActive, getBaseRoute(true, previouslyActiveDeviceAddress)); } // Clear out the active device for the BT audio type. if (mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()) { updateActiveBluetoothDevice(new Pair(type, null)); } } private void handleMuteChanged(boolean mute) { Loading Loading @@ -1023,16 +1018,29 @@ public class CallAudioRouteController implements CallAudioRouteAdapter { // 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 boolean isExcludedDeviceConnectingOrConnected = areExcludedBtAndDestBtSame && (mIsScoAudioConnected || mPendingAudioRoute.getPendingMessages() .contains(btDevicePendingMsg))) { .contains(btDevicePendingMsg)); // Check if the pending audio route or current route is already different from the route // including the BT device that should be excluded from route selection. boolean isCurrentOrDestRouteDifferent = btAddressToExclude != null && ((mIsPending && !btAddressToExclude.equals(mPendingAudioRoute.getDestRoute() .getBluetoothAddress())) || (!mIsPending && !btAddressToExclude.equals( mCurrentRoute.getBluetoothAddress()))); if (mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()) { if (isExcludedDeviceConnectingOrConnected) { Log.i(this, "BT device with address (%s) is currently connecting/connected. " + "Ignore route switch."); } else { + "Ignoring route switch.", btAddressToExclude); return; } else if (isCurrentOrDestRouteDifferent) { Log.i(this, "Current or pending audio route isn't routed to device with address " + "(%s). Ignoring route switch.", btAddressToExclude); return; } } routeTo(mIsActive, calculateBaselineRoute(isExplicitUserRequest, includeBluetooth, btAddressToExclude)); } } private void handleSpeakerOn() { if (isPending()) { Loading Loading @@ -1441,8 +1449,11 @@ public class CallAudioRouteController implements CallAudioRouteAdapter { continue; } // Check if the most recently active device is a watch device. if (i == (bluetoothRoutes.size() - 1) && device.equals(mCallAudioState .getActiveBluetoothDevice()) && mBluetoothRouteManager.isWatch(device)) { boolean isActiveDevice = mActiveBluetoothDevice != null && device.getAddress().equals(mActiveBluetoothDevice.second); if (i == (bluetoothRoutes.size() - 1) && mBluetoothRouteManager.isWatch(device) && (device.equals(mCallAudioState.getActiveBluetoothDevice()) || isActiveDevice)) { Log.i(this, "getActiveWatchOrNonWatchDeviceRoute: Routing to active watch - %s", bluetoothRoutes.get(0)); return bluetoothRoutes.get(0); Loading
src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java +5 −8 Original line number Diff line number Diff line Loading @@ -252,17 +252,14 @@ public class BluetoothStateReceiver extends BroadcastReceiver { CallAudioRouteController audioRouteController = (CallAudioRouteController) mCallAudioRouteAdapter; if (device == null) { if (!mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()) { audioRouteController.updateActiveBluetoothDevice( new Pair(audioRouteType, null)); } // Update the active device cache immediately. audioRouteController.updateActiveBluetoothDevice(new Pair(audioRouteType, null)); mCallAudioRouteAdapter.sendMessageWithSessionInfo(BT_ACTIVE_DEVICE_GONE, audioRouteType); } else { if (!mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()) { // Update the active device cache immediately. audioRouteController.updateActiveBluetoothDevice( new Pair(audioRouteType, device.getAddress())); } mCallAudioRouteAdapter.sendMessageWithSessionInfo(BT_ACTIVE_DEVICE_PRESENT, audioRouteType, device.getAddress()); if (deviceType == BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID Loading
tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java +58 −0 Original line number Diff line number Diff line Loading @@ -74,6 +74,7 @@ import android.media.audiopolicy.AudioProductStrategy; import android.os.UserHandle; import android.telecom.CallAudioState; import android.telecom.VideoProfile; import android.util.Pair; import androidx.test.filters.SmallTest; Loading Loading @@ -1099,6 +1100,63 @@ public class CallAudioRouteControllerTest extends TelecomTestCase { any(CallAudioState.class), eq(expectedState)); } @Test @SmallTest public void testRouteToWatchWhenCallAnsweredOnWatch_MultipleBtDevices() { 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 watchDevice = BluetoothRouteManagerTest.makeBluetoothDevice(scoDeviceAddress); when(mBluetoothRouteManager.isWatch(eq(watchDevice))).thenReturn(true); BLUETOOTH_DEVICES.add(watchDevice); mController.sendMessageWithSessionInfo(BT_DEVICE_ADDED, AudioRoute.TYPE_BLUETOOTH_SCO, watchDevice); CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH, CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER | CallAudioState.ROUTE_BLUETOOTH, BLUETOOTH_DEVICE_1, BLUETOOTH_DEVICES); verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged( any(CallAudioState.class), eq(expectedState)); // Signal that watch is now the active device. This is done in BluetoothStateReceiver and // then BT_ACTIVE_DEVICE_PRESENT will be sent to the controller to be processed. mController.updateActiveBluetoothDevice( new Pair<>(AudioRoute.TYPE_BLUETOOTH_SCO, watchDevice.getAddress())); // Emulate scenario with call answered on watch. Ensure at this point that audio was routed // into watch mController.sendMessageWithSessionInfo(SWITCH_FOCUS, ACTIVE_FOCUS, 0); mController.sendMessageWithSessionInfo(BT_AUDIO_CONNECTED, 0, watchDevice); mController.sendMessageWithSessionInfo(BT_AUDIO_DISCONNECTED, 0, BLUETOOTH_DEVICE_1); expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH, CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER | CallAudioState.ROUTE_BLUETOOTH, watchDevice, BLUETOOTH_DEVICES); verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged( any(CallAudioState.class), eq(expectedState)); // Hardcode signal from BT stack signaling to Telecom that watch is now the active device. // This should just be a no-op since audio was already routed when processing active focus. mController.sendMessageWithSessionInfo(BT_ACTIVE_DEVICE_PRESENT, AudioRoute.TYPE_BLUETOOTH_SCO, scoDeviceAddress); verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged( any(CallAudioState.class), eq(expectedState)); // Mimic behavior of controller processing BT_AUDIO_DISCONNECTED for BLUETOOTH_DEVICE_1 and // verify that audio remains routed to the watch and not routed to earpiece (this should // be taking into account what the BT active device is as reported to us by the BT stack). mController.sendMessageWithSessionInfo(SWITCH_BASELINE_ROUTE, INCLUDE_BLUETOOTH_IN_BASELINE, BLUETOOTH_DEVICE_1.getAddress()); verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged( any(CallAudioState.class), eq(expectedState)); BLUETOOTH_DEVICES.remove(watchDevice); } @Test @SmallTest public void testAbandonCallAudioFocusAfterCallEnd() { Loading