Loading services/core/java/com/android/server/media/AudioManagerRouteController.java +16 −10 Original line number Diff line number Diff line Loading @@ -323,11 +323,6 @@ import java.util.concurrent.CopyOnWriteArrayList; return; } if (com.android.media.flags.Flags.enableOutputSwitcherPersonalAudioSharing()) { // We need to stop broadcast when we transfer to another route stopBroadcastForTransferIfCurrentlySelected(routeId); } MediaRoute2InfoHolder mediaRoute2InfoHolder; synchronized (this) { mediaRoute2InfoHolder = mRouteIdToAvailableDeviceRoutes.get(routeId); Loading @@ -336,6 +331,21 @@ import java.util.concurrent.CopyOnWriteArrayList; Slog.w(TAG, "transferTo: Ignoring transfer request to unknown route id : " + routeId); return; } // We need to stop broadcast when we transfer to another route boolean currentOutputIsBLEBroadcast = com.android.media.flags.Flags.enableOutputSwitcherPersonalAudioSharing() && currentOutputIsBLEBroadcast(); if (currentOutputIsBLEBroadcast) { boolean isBtRoute = mBluetoothRouteController.isBtRoute(mediaRoute2InfoHolder.mMediaRoute2Info); stopBroadcastForTransfer(isBtRoute ? routeId : null); if (isBtRoute) { Slog.d(TAG, "transferTo: Skip transfer action for BT route in broadcast"); return; } } Runnable transferAction = getTransferActionForRoute(mediaRoute2InfoHolder); Runnable guardedTransferAction = () -> { Loading Loading @@ -387,11 +397,7 @@ import java.util.concurrent.CopyOnWriteArrayList; mHandler.post(() -> mBluetoothRouteController.removeRouteFromBroadcast(routeId)); } private void stopBroadcastForTransferIfCurrentlySelected(@NonNull String routeId) { if (!currentOutputIsBLEBroadcast()) { return; } private void stopBroadcastForTransfer(@Nullable String routeId) { mHandler.post(() -> mBluetoothRouteController.stopBroadcast(routeId)); } Loading services/core/java/com/android/server/media/BluetoothDeviceRoutesManager.java +20 −7 Original line number Diff line number Diff line Loading @@ -69,6 +69,11 @@ import java.util.stream.Collectors; private static final String HEARING_AID_ROUTE_ID_PREFIX = "HEARING_AID_"; private static final String LE_AUDIO_ROUTE_ID_PREFIX = "LE_AUDIO_"; private static final List<Integer> BT_DEVICE_TYPES = List.of( MediaRoute2Info.TYPE_BLE_HEADSET, MediaRoute2Info.TYPE_HEARING_AID, MediaRoute2Info.TYPE_BLUETOOTH_A2DP); /** Interface for receiving events about Bluetooth routes changes. */ interface BluetoothRoutesUpdatedListener { Loading Loading @@ -301,15 +306,23 @@ import java.util.stream.Collectors; mBluetoothProfileMonitor.stopBroadcast(); } /** Returns whether {@link MediaRoute2Info} is info of Bluetooth route. */ protected boolean isBtRoute(@NonNull MediaRoute2Info mediaRoute2Info) { return BT_DEVICE_TYPES.contains(mediaRoute2Info.getType()); } /** * Trigger {@link BluetoothProfileMonitor} to stop the broadcast, optionally making a new LEA * Trigger {@link BluetoothProfileMonitor} to stop the broadcast, optionally making a new BT * device active. * * @param routeId id of the LEA Bluetooth route to be set as active after broadcast stops. * @param routeId id of the Bluetooth route to be set as active after broadcast stops. */ protected void stopBroadcast(@Nullable String routeId) { Slog.d(TAG, "stopBroadcast: for route id " + routeId); BluetoothDevice bluetoothDevice = mBluetoothRoutes.values().stream() routeId == null ? null : mBluetoothRoutes.values().stream() .filter(routeInfo -> routeInfo.mRoute.getId().equals(routeId)) .findFirst() .map(routeInfo -> routeInfo.mBtDevice) Loading services/core/java/com/android/server/media/BluetoothProfileMonitor.java +14 −5 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.server.media; import static android.bluetooth.BluetoothAdapter.ACTIVE_DEVICE_AUDIO; import android.annotation.NonNull; import android.annotation.Nullable; import android.bluetooth.BluetoothA2dp; Loading Loading @@ -247,13 +249,13 @@ import java.util.concurrent.ThreadLocalRandom; } /** * Stops the broadcast, optionally making a new LEA BT device active. * Stops the broadcast, optionally making a new BT device active. * * <p>This method is expected to use the given device to determine which unicast fallback group * should be set when the broadcast stops. * should be set or which classic device should be active when the broadcast stops. * * @param device LEA device that should become active once the broadcast stops, or null if no * LEA device should become active once broadcast stops. * @param device BT device that should become active once the broadcast stops, or null if no BT * device should become active once broadcast stops. */ public synchronized void stopBroadcast(@Nullable BluetoothDevice device) { if (mBroadcastProfile == null) { Loading @@ -263,12 +265,19 @@ import java.util.concurrent.ThreadLocalRandom; if (mLeAudioProfile == null) { Slog.e(TAG, "Fail to set fall back group, LeProfile is null"); } else { // if no valid group id, set the fallback to -1, no LEA BT device should become active // if no valid group id, set the fallback to -1, no LEA device should become active // once broadcast stops int groupId = (device == null || !isProfileSupported(BluetoothProfile.LE_AUDIO, device)) ? BluetoothLeAudio.GROUP_ID_INVALID : (int) getGroupId(BluetoothProfile.LE_AUDIO, device); if (device != null && groupId == BluetoothLeAudio.GROUP_ID_INVALID) { // for classic device, we need set active for it explicitly, because when broadcast // stops, bt stack will only deal with fallback LEA device. Slog.d(TAG, "stopBroadcast: set active device to " + device.getAnonymizedAddress()); mBluetoothAdapter.setActiveDevice(device, ACTIVE_DEVICE_AUDIO); } Slog.d(TAG, "stopBroadcast: set broadcast fallabck group to " + groupId); mLeAudioProfile.setBroadcastToUnicastFallbackGroup(groupId); } mBroadcastProfile.stopBroadcast(mBroadcastId); Loading services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioManagerRouteControllerTest.java +71 −0 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; Loading Loading @@ -297,6 +298,76 @@ public class AudioManagerRouteControllerTest { AudioDeviceInfo.TYPE_WIRED_HEADSET, /* address= */ "")); } @Test @EnableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_PERSONAL_AUDIO_SHARING) public void transferToNonBtDevice_inBroadcast_stopsBroadcastAndSetsTheExpectedRoutingPolicy() { setUpControllerAndLEAudioMocks(); when(mMockBluetoothDeviceRoutesManager.isLEAudioBroadcastSupported()).thenReturn(true); when(mMockBluetoothDeviceRoutesManager.isBtRoute(any())).thenReturn(false); when(mMockBluetoothDeviceRoutesManager.getBroadcastingDeviceRoutes()) .thenReturn( List.of( createMediaRoute2Info( /* id= */ FAKE_AUDIO_DEVICE_LE_HEADSET_1_ID, /* name= */ FAKE_AUDIO_DEVICE_LE_HEADSET_1_NAME, /* address= */ FAKE_AUDIO_DEVICE_LE_HEADSET_1_ADDRESS, /* volume= */ 0))); addAvailableAudioDeviceInfo( /* newSelectedDevice= */ FAKE_AUDIO_DEVICE_LE_HEADSET_1_BROADCASTING, /* newAvailableDevices...= */ FAKE_AUDIO_DEVICE_LE_HEADSET_1_BROADCASTING, FAKE_AUDIO_DEVICE_LE_HEADSET_2); clearInvocations(mMockAudioManager); MediaRoute2Info builtInSpeakerRoute = getAvailableRouteWithType(MediaRoute2Info.TYPE_BUILTIN_SPEAKER); mControllerUnderTest.transferTo(/* requestId= */ 0L, builtInSpeakerRoute.getId()); mLooperManager.execute(mLooperManager.next()); verify(mMockBluetoothDeviceRoutesManager).stopBroadcast(null); mLooperManager.execute(mLooperManager.next()); verify(mMockAudioManager) .setPreferredDeviceForStrategy( mMediaAudioProductStrategy, createAudioDeviceAttribute( AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, /* address= */ "")); } @Test @EnableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_PERSONAL_AUDIO_SHARING) public void transferToBtDevice_inBroadcast_stopsBroadcastWithoutSettingRoutingPolicy() { setUpControllerAndLEAudioMocks(); when(mMockBluetoothDeviceRoutesManager.isLEAudioBroadcastSupported()).thenReturn(true); when(mMockBluetoothDeviceRoutesManager.isBtRoute(any())).thenReturn(true); addAvailableAudioDeviceInfo( /* newSelectedDevice= */ FAKE_AUDIO_DEVICE_INFO_BLUETOOTH_A2DP, /* newAvailableDevices...= */ FAKE_AUDIO_DEVICE_INFO_BLUETOOTH_A2DP, FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET); when(mMockBluetoothDeviceRoutesManager.getBroadcastingDeviceRoutes()) .thenReturn( List.of( createMediaRoute2Info( /* id= */ FAKE_AUDIO_DEVICE_LE_HEADSET_1_ID, /* name= */ FAKE_AUDIO_DEVICE_LE_HEADSET_1_NAME, /* address= */ FAKE_AUDIO_DEVICE_LE_HEADSET_1_ADDRESS, /* volume= */ 0))); addAvailableAudioDeviceInfo( /* newSelectedDevice= */ FAKE_AUDIO_DEVICE_LE_HEADSET_1_BROADCASTING, /* newAvailableDevices...= */ FAKE_AUDIO_DEVICE_LE_HEADSET_1_BROADCASTING, FAKE_AUDIO_DEVICE_LE_HEADSET_2); clearInvocations(mMockAudioManager); MediaRoute2Info a2dpBluetoothRoute = getAvailableRouteWithType(MediaRoute2Info.TYPE_BLUETOOTH_A2DP); mControllerUnderTest.transferTo(/* requestId= */ 0L, a2dpBluetoothRoute.getId()); mLooperManager.execute(mLooperManager.next()); verify(mMockBluetoothDeviceRoutesManager).stopBroadcast(a2dpBluetoothRoute.getId()); verify(mMockBluetoothDeviceRoutesManager, never()) .activateBluetoothDeviceWithAddress(a2dpBluetoothRoute.getAddress()); verify(mMockAudioManager, never()) .removePreferredDeviceForStrategy(mMediaAudioProductStrategy); } @Test public void updateVolume_propagatesCorrectlyToRouteInfo() { setUpControllerUnderTest(/* useMockBluetoothDeviceRoutesManager= */ false); Loading Loading
services/core/java/com/android/server/media/AudioManagerRouteController.java +16 −10 Original line number Diff line number Diff line Loading @@ -323,11 +323,6 @@ import java.util.concurrent.CopyOnWriteArrayList; return; } if (com.android.media.flags.Flags.enableOutputSwitcherPersonalAudioSharing()) { // We need to stop broadcast when we transfer to another route stopBroadcastForTransferIfCurrentlySelected(routeId); } MediaRoute2InfoHolder mediaRoute2InfoHolder; synchronized (this) { mediaRoute2InfoHolder = mRouteIdToAvailableDeviceRoutes.get(routeId); Loading @@ -336,6 +331,21 @@ import java.util.concurrent.CopyOnWriteArrayList; Slog.w(TAG, "transferTo: Ignoring transfer request to unknown route id : " + routeId); return; } // We need to stop broadcast when we transfer to another route boolean currentOutputIsBLEBroadcast = com.android.media.flags.Flags.enableOutputSwitcherPersonalAudioSharing() && currentOutputIsBLEBroadcast(); if (currentOutputIsBLEBroadcast) { boolean isBtRoute = mBluetoothRouteController.isBtRoute(mediaRoute2InfoHolder.mMediaRoute2Info); stopBroadcastForTransfer(isBtRoute ? routeId : null); if (isBtRoute) { Slog.d(TAG, "transferTo: Skip transfer action for BT route in broadcast"); return; } } Runnable transferAction = getTransferActionForRoute(mediaRoute2InfoHolder); Runnable guardedTransferAction = () -> { Loading Loading @@ -387,11 +397,7 @@ import java.util.concurrent.CopyOnWriteArrayList; mHandler.post(() -> mBluetoothRouteController.removeRouteFromBroadcast(routeId)); } private void stopBroadcastForTransferIfCurrentlySelected(@NonNull String routeId) { if (!currentOutputIsBLEBroadcast()) { return; } private void stopBroadcastForTransfer(@Nullable String routeId) { mHandler.post(() -> mBluetoothRouteController.stopBroadcast(routeId)); } Loading
services/core/java/com/android/server/media/BluetoothDeviceRoutesManager.java +20 −7 Original line number Diff line number Diff line Loading @@ -69,6 +69,11 @@ import java.util.stream.Collectors; private static final String HEARING_AID_ROUTE_ID_PREFIX = "HEARING_AID_"; private static final String LE_AUDIO_ROUTE_ID_PREFIX = "LE_AUDIO_"; private static final List<Integer> BT_DEVICE_TYPES = List.of( MediaRoute2Info.TYPE_BLE_HEADSET, MediaRoute2Info.TYPE_HEARING_AID, MediaRoute2Info.TYPE_BLUETOOTH_A2DP); /** Interface for receiving events about Bluetooth routes changes. */ interface BluetoothRoutesUpdatedListener { Loading Loading @@ -301,15 +306,23 @@ import java.util.stream.Collectors; mBluetoothProfileMonitor.stopBroadcast(); } /** Returns whether {@link MediaRoute2Info} is info of Bluetooth route. */ protected boolean isBtRoute(@NonNull MediaRoute2Info mediaRoute2Info) { return BT_DEVICE_TYPES.contains(mediaRoute2Info.getType()); } /** * Trigger {@link BluetoothProfileMonitor} to stop the broadcast, optionally making a new LEA * Trigger {@link BluetoothProfileMonitor} to stop the broadcast, optionally making a new BT * device active. * * @param routeId id of the LEA Bluetooth route to be set as active after broadcast stops. * @param routeId id of the Bluetooth route to be set as active after broadcast stops. */ protected void stopBroadcast(@Nullable String routeId) { Slog.d(TAG, "stopBroadcast: for route id " + routeId); BluetoothDevice bluetoothDevice = mBluetoothRoutes.values().stream() routeId == null ? null : mBluetoothRoutes.values().stream() .filter(routeInfo -> routeInfo.mRoute.getId().equals(routeId)) .findFirst() .map(routeInfo -> routeInfo.mBtDevice) Loading
services/core/java/com/android/server/media/BluetoothProfileMonitor.java +14 −5 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.server.media; import static android.bluetooth.BluetoothAdapter.ACTIVE_DEVICE_AUDIO; import android.annotation.NonNull; import android.annotation.Nullable; import android.bluetooth.BluetoothA2dp; Loading Loading @@ -247,13 +249,13 @@ import java.util.concurrent.ThreadLocalRandom; } /** * Stops the broadcast, optionally making a new LEA BT device active. * Stops the broadcast, optionally making a new BT device active. * * <p>This method is expected to use the given device to determine which unicast fallback group * should be set when the broadcast stops. * should be set or which classic device should be active when the broadcast stops. * * @param device LEA device that should become active once the broadcast stops, or null if no * LEA device should become active once broadcast stops. * @param device BT device that should become active once the broadcast stops, or null if no BT * device should become active once broadcast stops. */ public synchronized void stopBroadcast(@Nullable BluetoothDevice device) { if (mBroadcastProfile == null) { Loading @@ -263,12 +265,19 @@ import java.util.concurrent.ThreadLocalRandom; if (mLeAudioProfile == null) { Slog.e(TAG, "Fail to set fall back group, LeProfile is null"); } else { // if no valid group id, set the fallback to -1, no LEA BT device should become active // if no valid group id, set the fallback to -1, no LEA device should become active // once broadcast stops int groupId = (device == null || !isProfileSupported(BluetoothProfile.LE_AUDIO, device)) ? BluetoothLeAudio.GROUP_ID_INVALID : (int) getGroupId(BluetoothProfile.LE_AUDIO, device); if (device != null && groupId == BluetoothLeAudio.GROUP_ID_INVALID) { // for classic device, we need set active for it explicitly, because when broadcast // stops, bt stack will only deal with fallback LEA device. Slog.d(TAG, "stopBroadcast: set active device to " + device.getAnonymizedAddress()); mBluetoothAdapter.setActiveDevice(device, ACTIVE_DEVICE_AUDIO); } Slog.d(TAG, "stopBroadcast: set broadcast fallabck group to " + groupId); mLeAudioProfile.setBroadcastToUnicastFallbackGroup(groupId); } mBroadcastProfile.stopBroadcast(mBroadcastId); Loading
services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioManagerRouteControllerTest.java +71 −0 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; Loading Loading @@ -297,6 +298,76 @@ public class AudioManagerRouteControllerTest { AudioDeviceInfo.TYPE_WIRED_HEADSET, /* address= */ "")); } @Test @EnableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_PERSONAL_AUDIO_SHARING) public void transferToNonBtDevice_inBroadcast_stopsBroadcastAndSetsTheExpectedRoutingPolicy() { setUpControllerAndLEAudioMocks(); when(mMockBluetoothDeviceRoutesManager.isLEAudioBroadcastSupported()).thenReturn(true); when(mMockBluetoothDeviceRoutesManager.isBtRoute(any())).thenReturn(false); when(mMockBluetoothDeviceRoutesManager.getBroadcastingDeviceRoutes()) .thenReturn( List.of( createMediaRoute2Info( /* id= */ FAKE_AUDIO_DEVICE_LE_HEADSET_1_ID, /* name= */ FAKE_AUDIO_DEVICE_LE_HEADSET_1_NAME, /* address= */ FAKE_AUDIO_DEVICE_LE_HEADSET_1_ADDRESS, /* volume= */ 0))); addAvailableAudioDeviceInfo( /* newSelectedDevice= */ FAKE_AUDIO_DEVICE_LE_HEADSET_1_BROADCASTING, /* newAvailableDevices...= */ FAKE_AUDIO_DEVICE_LE_HEADSET_1_BROADCASTING, FAKE_AUDIO_DEVICE_LE_HEADSET_2); clearInvocations(mMockAudioManager); MediaRoute2Info builtInSpeakerRoute = getAvailableRouteWithType(MediaRoute2Info.TYPE_BUILTIN_SPEAKER); mControllerUnderTest.transferTo(/* requestId= */ 0L, builtInSpeakerRoute.getId()); mLooperManager.execute(mLooperManager.next()); verify(mMockBluetoothDeviceRoutesManager).stopBroadcast(null); mLooperManager.execute(mLooperManager.next()); verify(mMockAudioManager) .setPreferredDeviceForStrategy( mMediaAudioProductStrategy, createAudioDeviceAttribute( AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, /* address= */ "")); } @Test @EnableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_PERSONAL_AUDIO_SHARING) public void transferToBtDevice_inBroadcast_stopsBroadcastWithoutSettingRoutingPolicy() { setUpControllerAndLEAudioMocks(); when(mMockBluetoothDeviceRoutesManager.isLEAudioBroadcastSupported()).thenReturn(true); when(mMockBluetoothDeviceRoutesManager.isBtRoute(any())).thenReturn(true); addAvailableAudioDeviceInfo( /* newSelectedDevice= */ FAKE_AUDIO_DEVICE_INFO_BLUETOOTH_A2DP, /* newAvailableDevices...= */ FAKE_AUDIO_DEVICE_INFO_BLUETOOTH_A2DP, FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET); when(mMockBluetoothDeviceRoutesManager.getBroadcastingDeviceRoutes()) .thenReturn( List.of( createMediaRoute2Info( /* id= */ FAKE_AUDIO_DEVICE_LE_HEADSET_1_ID, /* name= */ FAKE_AUDIO_DEVICE_LE_HEADSET_1_NAME, /* address= */ FAKE_AUDIO_DEVICE_LE_HEADSET_1_ADDRESS, /* volume= */ 0))); addAvailableAudioDeviceInfo( /* newSelectedDevice= */ FAKE_AUDIO_DEVICE_LE_HEADSET_1_BROADCASTING, /* newAvailableDevices...= */ FAKE_AUDIO_DEVICE_LE_HEADSET_1_BROADCASTING, FAKE_AUDIO_DEVICE_LE_HEADSET_2); clearInvocations(mMockAudioManager); MediaRoute2Info a2dpBluetoothRoute = getAvailableRouteWithType(MediaRoute2Info.TYPE_BLUETOOTH_A2DP); mControllerUnderTest.transferTo(/* requestId= */ 0L, a2dpBluetoothRoute.getId()); mLooperManager.execute(mLooperManager.next()); verify(mMockBluetoothDeviceRoutesManager).stopBroadcast(a2dpBluetoothRoute.getId()); verify(mMockBluetoothDeviceRoutesManager, never()) .activateBluetoothDeviceWithAddress(a2dpBluetoothRoute.getAddress()); verify(mMockAudioManager, never()) .removePreferredDeviceForStrategy(mMediaAudioProductStrategy); } @Test public void updateVolume_propagatesCorrectlyToRouteInfo() { setUpControllerUnderTest(/* useMockBluetoothDeviceRoutesManager= */ false); Loading