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

Commit 5aa12b8e authored by Rongxuan Liu's avatar Rongxuan Liu
Browse files

[le audio] Broadcast control sink devices volume by setVolume

This change allows sink devices volume level by setVolume if there is no
active unicast streaming and remotes are acitively receiving broadcast
stream.

Bug: 333761868
Bug: 333761969
Test: atest BassClientServiceTest LeAudioServiceTest
Change-Id: I7dd14b96f6b994022df0cf117590fa6ea7140290
parent 7e4e72ba
Loading
Loading
Loading
Loading
+26 −1
Original line number Diff line number Diff line
@@ -2236,7 +2236,8 @@ public class BassClientService extends ProfileService {
                for (int i = 0; i < receiveState.getNumSubgroups(); i++) {
                    Long syncState = receiveState.getBisSyncState().get(i);
                    /* Not synced to BIS of failed to sync to BIG */
                    if (syncState == 0x00000000 || syncState == 0xFFFFFFFF) {
                    if (syncState == BassConstants.BIS_SYNC_NOT_SYNC_TO_BIS
                            || syncState == BassConstants.BIS_SYNC_FAILED_SYNC_TO_BIG) {
                        continue;
                    }

@@ -2248,6 +2249,30 @@ public class BassClientService extends ProfileService {
        return false;
    }

    /** Get the active broadcast sink devices receiving broadcast stream */
    public List<BluetoothDevice> getActiveBroadcastSinks() {
        List<BluetoothDevice> activeSinks = new ArrayList<>();

        for (BluetoothDevice device : getConnectedDevices()) {
            // Check if any device's source in active sync state
            if (getAllSources(device).stream()
                    .anyMatch(
                            receiveState ->
                                    (receiveState.getBisSyncState().stream()
                                            .anyMatch(
                                                    syncState ->
                                                            syncState
                                                                            != BassConstants
                                                                                    .BIS_SYNC_NOT_SYNC_TO_BIS
                                                                    && syncState
                                                                            != BassConstants
                                                                                    .BIS_SYNC_FAILED_SYNC_TO_BIG)))) {
                activeSinks.add(device);
            }
        }
        return activeSinks;
    }

    /** Handle broadcast state changed */
    public void notifyBroadcastStateChanged(int state, int broadcastId) {
        switch (state) {
+3 −0
Original line number Diff line number Diff line
@@ -81,4 +81,7 @@ public class BassConstants {
    public static final int PA_SYNC_DO_NOT_SYNC = 0x00;
    public static final int PA_SYNC_PAST_AVAILABLE = 0x01;
    public static final int PA_SYNC_PAST_NOT_AVAILABLE = 0x02;
    // BIS_Sync parameter value
    public static final long BIS_SYNC_NOT_SYNC_TO_BIS = 0x00000000L;
    public static final long BIS_SYNC_FAILED_SYNC_TO_BIG = 0xFFFFFFFFL;
}
+32 −4
Original line number Diff line number Diff line
@@ -22,7 +22,6 @@ import static android.bluetooth.IBluetoothLeAudio.LE_AUDIO_GROUP_ID_INVALID;

import static com.android.bluetooth.flags.Flags.leaudioBroadcastFeatureSupport;
import static com.android.bluetooth.flags.Flags.leaudioApiSynchronizedBlockFix;
import static com.android.bluetooth.flags.Flags.leaudioGettingActiveStateSupport;
import static com.android.bluetooth.Utils.enforceBluetoothPrivilegedPermission;
import static com.android.modules.utils.build.SdkLevel.isAtLeastU;

@@ -100,6 +99,7 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
@@ -3760,16 +3760,44 @@ public class LeAudioService extends ProfileService {
        Log.d(TAG, "SetVolume " + volume);

        int currentlyActiveGroupId = getActiveGroupId();
        List<BluetoothDevice> activeBroadcastSinks = new ArrayList<>();

        if (currentlyActiveGroupId == LE_AUDIO_GROUP_ID_INVALID) {
            if (!Flags.leaudioBroadcastVolumeControlWithSetVolume()) {
                Log.e(TAG, "There is no active group ");
                return;
            }

            BassClientService bassClientService = getBassClientService();
            if (bassClientService != null) {
                activeBroadcastSinks = bassClientService.getActiveBroadcastSinks();
            }

            if (activeBroadcastSinks.isEmpty()) {
                Log.e(TAG, "There is no active streaming group or broadcast sinks");
                return;
            }
        }

        VolumeControlService volumeControlService = getVolumeControlService();
        if (volumeControlService != null) {
            if (Flags.leaudioBroadcastVolumeControlWithSetVolume()
                    && currentlyActiveGroupId == LE_AUDIO_GROUP_ID_INVALID
                    && !activeBroadcastSinks.isEmpty()) {
                Set<Integer> broadcastGroups =
                        activeBroadcastSinks.stream()
                                .map(dev -> getGroupId(dev))
                                .filter(id -> id != IBluetoothLeAudio.LE_AUDIO_GROUP_ID_INVALID)
                                .collect(Collectors.toSet());

                Log.d(TAG, "Setting volume for broadcast sink groups: " + broadcastGroups);
                broadcastGroups.forEach(
                        groupId -> volumeControlService.setGroupVolume(groupId, volume));
            } else {
                volumeControlService.setGroupVolume(currentlyActiveGroupId, volume);
            }
        }
    }

    TbsService getTbsService() {
        if (mTbsService != null) {
+92 −0
Original line number Diff line number Diff line
@@ -1863,6 +1863,98 @@ public class BassClientServiceTest {
        assertThat(mBassClientService.isAnyReceiverReceivingBroadcast(devices)).isTrue();
    }

    @Test
    public void testGetActiveBroadcastSinks() {
        prepareConnectedDeviceGroup();
        BluetoothLeBroadcastMetadata meta = createBroadcastMetadata(TEST_BROADCAST_ID);
        verifyAddSourceForGroup(meta);
        for (BassClientStateMachine sm : mStateMachines.values()) {
            if (sm.getDevice().equals(mCurrentDevice)) {
                injectRemoteSourceStateSourceAdded(
                        sm,
                        meta,
                        TEST_SOURCE_ID,
                        BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_IDLE,
                        meta.isEncrypted()
                                ? BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING
                                : BluetoothLeBroadcastReceiveState
                                        .BIG_ENCRYPTION_STATE_NOT_ENCRYPTED,
                        null);
                injectRemoteSourceStateChanged(
                        sm,
                        meta,
                        TEST_SOURCE_ID,
                        BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCHRONIZED,
                        meta.isEncrypted()
                                ? BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING
                                : BluetoothLeBroadcastReceiveState
                                        .BIG_ENCRYPTION_STATE_NOT_ENCRYPTED,
                        null,
                        (long) 0x00000000);
            } else if (sm.getDevice().equals(mCurrentDevice1)) {
                injectRemoteSourceStateSourceAdded(
                        sm,
                        meta,
                        TEST_SOURCE_ID + 1,
                        BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_IDLE,
                        meta.isEncrypted()
                                ? BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING
                                : BluetoothLeBroadcastReceiveState
                                        .BIG_ENCRYPTION_STATE_NOT_ENCRYPTED,
                        null);
                injectRemoteSourceStateChanged(
                        sm,
                        meta,
                        TEST_SOURCE_ID + 1,
                        BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCHRONIZED,
                        meta.isEncrypted()
                                ? BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING
                                : BluetoothLeBroadcastReceiveState
                                        .BIG_ENCRYPTION_STATE_NOT_ENCRYPTED,
                        null,
                        (long) 0x00000000);
            }
        }

        // Verify isAnyReceiverReceivingBroadcast returns empty device list if no BIS synced
        assertThat(mBassClientService.getActiveBroadcastSinks().isEmpty()).isTrue();

        // Update receiver state with lost BIS sync
        for (BassClientStateMachine sm : mStateMachines.values()) {
            if (sm.getDevice().equals(mCurrentDevice)) {
                injectRemoteSourceStateChanged(
                        sm,
                        meta,
                        TEST_SOURCE_ID,
                        BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCHRONIZED,
                        meta.isEncrypted()
                                ? BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING
                                : BluetoothLeBroadcastReceiveState
                                        .BIG_ENCRYPTION_STATE_NOT_ENCRYPTED,
                        null,
                        (long) 0x00000001);
            } else if (sm.getDevice().equals(mCurrentDevice1)) {
                injectRemoteSourceStateChanged(
                        sm,
                        meta,
                        TEST_SOURCE_ID + 1,
                        BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCHRONIZED,
                        meta.isEncrypted()
                                ? BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING
                                : BluetoothLeBroadcastReceiveState
                                        .BIG_ENCRYPTION_STATE_NOT_ENCRYPTED,
                        null,
                        (long) 0x00000002);
            }
        }
        List<BluetoothDevice> activeSinks = mBassClientService.getActiveBroadcastSinks();

        // Verify isAnyReceiverReceivingBroadcast returns correct device list if BIS synced
        assertThat(activeSinks.size()).isEqualTo(2);
        assertThat(activeSinks.contains(mCurrentDevice)).isTrue();
        assertThat(activeSinks.contains(mCurrentDevice1)).isTrue();
    }

    private void prepareTwoSynchronizedDevices() {
        BluetoothLeBroadcastMetadata meta = createBroadcastMetadata(TEST_BROADCAST_ID);

+52 −0
Original line number Diff line number Diff line
@@ -83,6 +83,7 @@ import org.mockito.Spy;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -2215,6 +2216,57 @@ public class LeAudioServiceTest {
        assertThat(profileInfo.getValue().getVolume()).isEqualTo(volume);
    }

    /** Test volume setting for broadcast sink devices */
    @Test
    public void testSetVolumeForBroadcastSinks() {
        mSetFlagsRule.enableFlags(Flags.FLAG_LEAUDIO_BROADCAST_VOLUME_CONTROL_WITH_SET_VOLUME);
        int groupId = 1;
        int volume = 100;
        int newVolume = 120;
        /* AUDIO_DIRECTION_OUTPUT_BIT = 0x01 */
        int direction = 1;
        int availableContexts = 4;

        doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class));
        connectTestDevice(mLeftDevice, groupId);
        connectTestDevice(mRightDevice, groupId);
        assertThat(mService.setActiveDevice(mLeftDevice)).isFalse();

        ArgumentCaptor<BluetoothProfileConnectionInfo> profileInfo =
                ArgumentCaptor.forClass(BluetoothProfileConnectionInfo.class);

        // Add location support.
        injectAudioConfChanged(groupId, availableContexts, direction);
        assertThat(mService.setActiveDevice(mLeftDevice)).isTrue();

        doReturn(volume).when(mVolumeControlService).getAudioDeviceGroupVolume(groupId);
        // Set group and device as active.
        injectGroupStatusChange(groupId, LeAudioStackEvent.GROUP_STATUS_ACTIVE);

        verify(mAudioManager, times(1))
                .handleBluetoothActiveDeviceChanged(any(), eq(null), profileInfo.capture());
        assertThat(profileInfo.getValue().getVolume()).isEqualTo(volume);

        // Set group to inactive, only keep them connected as broadcast sink devices.
        injectGroupStatusChange(groupId, LeAudioStackEvent.GROUP_STATUS_INACTIVE);

        verify(mAudioManager, times(1))
                .handleBluetoothActiveDeviceChanged(
                        eq(null), any(), any(BluetoothProfileConnectionInfo.class));

        // Verify setGroupVolume will not be called if no active sinks
        doReturn(new ArrayList<>()).when(mBassClientService).getActiveBroadcastSinks();
        mService.setVolume(newVolume);
        verify(mVolumeControlService, times(0)).setGroupVolume(groupId, newVolume);

        // Verify setGroupVolume will be called if active sinks
        doReturn(List.of(mLeftDevice, mRightDevice))
                .when(mBassClientService)
                .getActiveBroadcastSinks();
        mService.setVolume(newVolume);
        verify(mVolumeControlService, times(1)).setGroupVolume(groupId, newVolume);
    }

    @Test
    public void testGetAudioDeviceGroupVolume_whenVolumeControlServiceIsNull() {
        mService.mVolumeControlService = null;