Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit bcd480d7 authored by Yiyi Shen's avatar Yiyi Shen Committed by Android (Google) Code Review
Browse files

Merge "[MR2] Improve transferTo handling during broadcast" into main

parents 6f191ef5 20653520
Loading
Loading
Loading
Loading
+16 −10
Original line number Diff line number Diff line
@@ -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);
@@ -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 =
                () -> {
@@ -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));
    }

+20 −7
Original line number Diff line number Diff line
@@ -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 {
@@ -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)
+14 −5
Original line number Diff line number Diff line
@@ -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;
@@ -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) {
@@ -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);
+71 −0
Original line number Diff line number Diff line
@@ -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;
@@ -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);