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

Commit cd0c19ca authored by Rongxuan Liu's avatar Rongxuan Liu Committed by Gerrit Code Review
Browse files

Merge "le_audio: Avoid sound effects while adding/switching broadcast source" into main

parents ad52f187 a27e935b
Loading
Loading
Loading
Loading
+43 −12
Original line number Original line Diff line number Diff line
@@ -902,6 +902,40 @@ public class BassClientService extends ProfileService {
        return false;
        return false;
    }
    }


    private boolean isAnyConnectedDeviceSwitchingSource() {
        for (BluetoothDevice device : getConnectedDevices()) {
            synchronized (mStateMachines) {
                BassClientStateMachine sm = getOrCreateStateMachine(device);
                // Need to check both mPendingSourceToSwitch and mPendingMetadata
                // to guard the whole source switching flow
                if (sm != null
                        && (sm.hasPendingSwitchingSourceOperation()
                                || sm.hasPendingSourceOperation())) {
                    return true;
                }
            }
        }
        return false;
    }

    private void checkAndSetGroupAllowedContextMask(BluetoothDevice sink) {
        LeAudioService leAudioService = mServiceFactory.getLeAudioService();
        if (leAudioService == null) {
            return;
        }

        if (leaudioAllowedContextMask()) {
            /* Don't bother active group (external broadcaster scenario) with SOUND EFFECTS */
            if (!mIsAllowedContextOfActiveGroupModified && isDevicePartOfActiveUnicastGroup(sink)) {
                leAudioService.setActiveGroupAllowedContextMask(
                        BluetoothLeAudio.CONTEXTS_ALL
                                & ~BluetoothLeAudio.CONTEXT_TYPE_SOUND_EFFECTS,
                        BluetoothLeAudio.CONTEXTS_ALL);
                mIsAllowedContextOfActiveGroupModified = true;
            }
        }
    }

    private void checkAndResetGroupAllowedContextMask() {
    private void checkAndResetGroupAllowedContextMask() {
        LeAudioService leAudioService = mServiceFactory.getLeAudioService();
        LeAudioService leAudioService = mServiceFactory.getLeAudioService();
        if (leAudioService == null) {
        if (leAudioService == null) {
@@ -911,7 +945,8 @@ public class BassClientService extends ProfileService {
        if (leaudioAllowedContextMask()) {
        if (leaudioAllowedContextMask()) {
            /* Restore allowed context mask for Unicast */
            /* Restore allowed context mask for Unicast */
            if (mIsAllowedContextOfActiveGroupModified
            if (mIsAllowedContextOfActiveGroupModified
                    && !hasAnyConnectedDeviceExternalBroadcastSource()) {
                    && !hasAnyConnectedDeviceExternalBroadcastSource()
                    && !isAnyConnectedDeviceSwitchingSource()) {
                leAudioService.setActiveGroupAllowedContextMask(
                leAudioService.setActiveGroupAllowedContextMask(
                        BluetoothLeAudio.CONTEXTS_ALL, BluetoothLeAudio.CONTEXTS_ALL);
                        BluetoothLeAudio.CONTEXTS_ALL, BluetoothLeAudio.CONTEXTS_ALL);
                mIsAllowedContextOfActiveGroupModified = false;
                mIsAllowedContextOfActiveGroupModified = false;
@@ -935,17 +970,7 @@ public class BassClientService extends ProfileService {
                leAudioService.activeBroadcastAssistantNotification(true);
                leAudioService.activeBroadcastAssistantNotification(true);
            }
            }


            if (leaudioAllowedContextMask()) {
            checkAndSetGroupAllowedContextMask(sink);
                /* Don't bother active group (external broadcaster scenario) with SOUND EFFECTS */
                if (!mIsAllowedContextOfActiveGroupModified
                        && isDevicePartOfActiveUnicastGroup(sink)) {
                    leAudioService.setActiveGroupAllowedContextMask(
                            BluetoothLeAudio.CONTEXTS_ALL
                                    & ~BluetoothLeAudio.CONTEXT_TYPE_SOUND_EFFECTS,
                            BluetoothLeAudio.CONTEXTS_ALL);
                    mIsAllowedContextOfActiveGroupModified = true;
                }
            }
        } else {
        } else {
            /* Assistant become inactive */
            /* Assistant become inactive */
            if (mIsAssistantActive && mPausedBroadcastSinks.isEmpty()) {
            if (mIsAssistantActive && mPausedBroadcastSinks.isEmpty()) {
@@ -2608,6 +2633,10 @@ public class BassClientService extends ProfileService {
                        device, BassClientStateMachine.ADD_BCAST_SOURCE, sourceMetadata);
                        device, BassClientStateMachine.ADD_BCAST_SOURCE, sourceMetadata);
            }
            }


            if (!isLocalBroadcast(sourceMetadata)) {
                checkAndSetGroupAllowedContextMask(device);
            }

            sEventLogger.logd(
            sEventLogger.logd(
                    TAG,
                    TAG,
                    "Add Broadcast Source: "
                    "Add Broadcast Source: "
@@ -3552,6 +3581,8 @@ public class BassClientService extends ProfileService {


        void notifySourceAddFailed(
        void notifySourceAddFailed(
                BluetoothDevice sink, BluetoothLeBroadcastMetadata source, int reason) {
                BluetoothDevice sink, BluetoothLeBroadcastMetadata source, int reason) {
            sService.checkAndResetGroupAllowedContextMask();

            sEventLogger.loge(
            sEventLogger.loge(
                    TAG,
                    TAG,
                    "notifySourceAddFailed: sink: "
                    "notifySourceAddFailed: sink: "
+10 −1
Original line number Original line Diff line number Diff line
@@ -269,6 +269,10 @@ public class BassClientStateMachine extends StateMachine {
        }
        }
    }
    }


    Boolean hasPendingSwitchingSourceOperation() {
        return mPendingSourceToSwitch != null;
    }

    BluetoothLeBroadcastMetadata getCurrentBroadcastMetadata(Integer sourceId) {
    BluetoothLeBroadcastMetadata getCurrentBroadcastMetadata(Integer sourceId) {
        return mCurrentMetadata.getOrDefault(sourceId, null);
        return mCurrentMetadata.getOrDefault(sourceId, null);
    }
    }
@@ -995,7 +999,6 @@ public class BassClientStateMachine extends StateMachine {
                        Message message = obtainMessage(ADD_BCAST_SOURCE);
                        Message message = obtainMessage(ADD_BCAST_SOURCE);
                        message.obj = mPendingSourceToSwitch;
                        message.obj = mPendingSourceToSwitch;
                        sendMessage(message);
                        sendMessage(message);
                        mPendingSourceToSwitch = null;
                    } else {
                    } else {
                        mService.getCallbacks()
                        mService.getCallbacks()
                                .notifySourceRemoved(
                                .notifySourceRemoved(
@@ -2003,6 +2006,12 @@ public class BassClientStateMachine extends StateMachine {
                                .notifySourceAddFailed(
                                .notifySourceAddFailed(
                                        mDevice, metaData, BluetoothStatusCodes.ERROR_UNKNOWN);
                                        mDevice, metaData, BluetoothStatusCodes.ERROR_UNKNOWN);
                    }
                    }
                    if (mPendingSourceToSwitch != null
                            && mPendingSourceToSwitch.getBroadcastId()
                                    == metaData.getBroadcastId()) {
                        // Clear pending source to switch when starting to add this new source
                        mPendingSourceToSwitch = null;
                    }
                    break;
                    break;
                case UPDATE_BCAST_SOURCE:
                case UPDATE_BCAST_SOURCE:
                    metaData = (BluetoothLeBroadcastMetadata) message.obj;
                    metaData = (BluetoothLeBroadcastMetadata) message.obj;
+83 −0
Original line number Original line Diff line number Diff line
@@ -40,6 +40,7 @@ import static org.mockito.Mockito.when;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothLeAudio;
import android.bluetooth.BluetoothLeAudioCodecConfigMetadata;
import android.bluetooth.BluetoothLeAudioCodecConfigMetadata;
import android.bluetooth.BluetoothLeAudioContentMetadata;
import android.bluetooth.BluetoothLeAudioContentMetadata;
import android.bluetooth.BluetoothLeBroadcastAssistant;
import android.bluetooth.BluetoothLeBroadcastAssistant;
@@ -2624,6 +2625,88 @@ public class BassClientServiceTest {
        }
        }
    }
    }


    @Test
    public void testAddSourceForExternalBroadcast_triggerSetContextMask() {
        mSetFlagsRule.enableFlags(Flags.FLAG_LEAUDIO_ALLOWED_CONTEXT_MASK);
        mSetFlagsRule.enableFlags(
                Flags.FLAG_LEAUDIO_BROADCAST_EXTRACT_PERIODIC_SCANNER_FROM_STATE_MACHINE);
        mSetFlagsRule.enableFlags(Flags.FLAG_LEAUDIO_BROADCAST_ASSISTANT_PERIPHERAL_ENTRUSTMENT);

        final int testGroupId = 1;
        prepareConnectedDeviceGroup();
        startSearchingForSources();
        onScanResult(mSourceDevice, TEST_BROADCAST_ID);
        onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE);

        /* Fake external broadcast - no Broadcast Metadata from LE Audio service */
        doReturn(new ArrayList<BluetoothLeBroadcastMetadata>())
                .when(mLeAudioService)
                .getAllBroadcastMetadata();
        doReturn(testGroupId).when(mLeAudioService).getActiveGroupId();
        doReturn(new ArrayList<BluetoothDevice>(Arrays.asList(mCurrentDevice)))
                .when(mLeAudioService)
                .getActiveDevices();

        assertThat(mBassClientService.getActiveSyncedSources().size()).isEqualTo(1);
        assertThat(mBassClientService.getActiveSyncedSources().contains(TEST_SYNC_HANDLE)).isTrue();
        assertThat(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE))
                .isEqualTo(mSourceDevice);
        assertThat(mBassClientService.getBroadcastIdForSyncHandle(TEST_SYNC_HANDLE))
                .isEqualTo(TEST_BROADCAST_ID);

        BluetoothLeBroadcastMetadata.Builder builder =
                new BluetoothLeBroadcastMetadata.Builder()
                        .setEncrypted(false)
                        .setSourceDevice(mSourceDevice, BluetoothDevice.ADDRESS_TYPE_RANDOM)
                        .setSourceAdvertisingSid(TEST_ADVERTISER_SID)
                        .setBroadcastId(TEST_BROADCAST_ID)
                        .setBroadcastCode(null)
                        .setPaSyncInterval(TEST_PA_SYNC_INTERVAL)
                        .setPresentationDelayMicros(TEST_PRESENTATION_DELAY_MS);
        // builder expect at least one subgroup
        builder.addSubgroup(createBroadcastSubgroup());
        BluetoothLeBroadcastMetadata meta = builder.build();

        // Add source to unsynced broadcast, causes synchronization first
        mBassClientService.addSource(mCurrentDevice, meta, true);

        // Verify setting allowed context mask is triggered
        verify(mLeAudioService)
                .setActiveGroupAllowedContextMask(
                        eq(
                                BluetoothLeAudio.CONTEXTS_ALL
                                        & ~BluetoothLeAudio.CONTEXT_TYPE_SOUND_EFFECTS),
                        eq(BluetoothLeAudio.CONTEXTS_ALL));
        handleHandoverSupport();

        // Verify all group members getting ADD_BCAST_SOURCE message
        assertThat(mStateMachines.size()).isEqualTo(2);
        for (BassClientStateMachine sm : mStateMachines.values()) {
            ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
            verify(sm, atLeast(1)).sendMessage(messageCaptor.capture());

            Message msg =
                    messageCaptor.getAllValues().stream()
                            .filter(
                                    m ->
                                            (m.what == BassClientStateMachine.ADD_BCAST_SOURCE)
                                                    && (m.obj == meta))
                            .findFirst()
                            .orElse(null);
            assertThat(msg).isNotNull();
        }

        mBassClientService
                .getCallbacks()
                .notifySourceAddFailed(mCurrentDevice, meta, BluetoothStatusCodes.ERROR_UNKNOWN);
        TestUtils.waitForLooperToFinishScheduledTask(mBassClientService.getCallbacks().getLooper());

        // Verify resetting allowed context mask is triggered when switching source failed
        verify(mLeAudioService)
                .setActiveGroupAllowedContextMask(
                        eq(BluetoothLeAudio.CONTEXTS_ALL), eq(BluetoothLeAudio.CONTEXTS_ALL));
    }

    @Test
    @Test
    public void testSelectSource_orderOfSyncRegistering() {
    public void testSelectSource_orderOfSyncRegistering() {
        mSetFlagsRule.enableFlags(
        mSetFlagsRule.enableFlags(
+5 −1
Original line number Original line Diff line number Diff line
@@ -907,7 +907,6 @@ public class BassClientStateMachineTest {
        verify(callbacks)
        verify(callbacks)
                .notifyReceiveStateChanged(any(), eq(sourceId), receiveStateCaptor.capture());
                .notifyReceiveStateChanged(any(), eq(sourceId), receiveStateCaptor.capture());
        Assert.assertEquals(receiveStateCaptor.getValue().getSourceDevice(), mEmptyTestDevice);
        Assert.assertEquals(receiveStateCaptor.getValue().getSourceDevice(), mEmptyTestDevice);
        assertThat(mBassClientStateMachine.mPendingSourceToSwitch).isEqualTo(null);
    }
    }


    @Test
    @Test
@@ -1364,6 +1363,9 @@ public class BassClientStateMachineTest {


    @Test
    @Test
    public void sendAddBcastSourceMessage_inConnectedState() {
    public void sendAddBcastSourceMessage_inConnectedState() {
        mSetFlagsRule.enableFlags(
                Flags.FLAG_LEAUDIO_BROADCAST_EXTRACT_PERIODIC_SCANNER_FROM_STATE_MACHINE);

        initToConnectedState();
        initToConnectedState();


        BassClientService.Callbacks callbacks = Mockito.mock(BassClientService.Callbacks.class);
        BassClientService.Callbacks callbacks = Mockito.mock(BassClientService.Callbacks.class);
@@ -1386,11 +1388,13 @@ public class BassClientStateMachineTest {
                Mockito.mock(BluetoothGattCharacteristic.class);
                Mockito.mock(BluetoothGattCharacteristic.class);
        mBassClientStateMachine.mBroadcastScanControlPoint = scanControlPoint;
        mBassClientStateMachine.mBroadcastScanControlPoint = scanControlPoint;


        mBassClientStateMachine.mPendingSourceToSwitch = metadata;
        sendMessageAndVerifyTransition(
        sendMessageAndVerifyTransition(
                mBassClientStateMachine.obtainMessage(ADD_BCAST_SOURCE, metadata),
                mBassClientStateMachine.obtainMessage(ADD_BCAST_SOURCE, metadata),
                BassClientStateMachine.ConnectedProcessing.class);
                BassClientStateMachine.ConnectedProcessing.class);
        verify(scanControlPoint).setValue(any(byte[].class));
        verify(scanControlPoint).setValue(any(byte[].class));
        verify(btGatt).writeCharacteristic(any());
        verify(btGatt).writeCharacteristic(any());
        assertThat(mBassClientStateMachine.mPendingSourceToSwitch).isEqualTo(null);
    }
    }


    @Test
    @Test