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

Commit 854daee2 authored by Treehugger Robot's avatar Treehugger Robot Committed by Gerrit Code Review
Browse files

Merge "[le audio] Broadcast control sink devices volume by setVolume" into main

parents b974c6c6 5aa12b8e
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 −3
Original line number Diff line number Diff line
@@ -99,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;
@@ -3823,16 +3824,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;
@@ -2255,6 +2256,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;