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

Commit 819eb2ab authored by Rongxuan Liu's avatar Rongxuan Liu
Browse files

[le audio] Remove external source without local metadata

When a newly connected device has an existing source, the assistant doesn't have current metadata and it can't remove this source from remotes.
Assistant should also be able to remove the source without metadata.

Also this CL fixed one test error with bad code.

Flag: EXEMPT, trivial change covered by unit tests
Bug: 330690784
Test: atest BassClientServiceTest BassClientStateMachineTest
Test: manually test with newly connected device
Change-Id: I99bcffbf3868c21e52365862ceaf9de98538f956
parent 2df13ef7
Loading
Loading
Loading
Loading
+8 −4
Original line number Diff line number Diff line
@@ -3026,14 +3026,18 @@ public class BassClientService extends ProfileService {
                stopBigMonitoring(metaData.getBroadcastId(), true);
            }

            if (metaData != null && stateMachine.isSyncedToTheSource(sourceId)) {
            if (stateMachine.isSyncedToTheSource(sourceId)) {
                sEventLogger.logd(
                        TAG,
                        "Remove Broadcast Source(Force lost PA sync): "
                                + ("device: " + device)
                                + (", sourceId: " + sourceId)
                                + (", broadcastId: " + metaData.getBroadcastId())
                                + (", broadcastName: " + metaData.getBroadcastName()));
                                + (", broadcastId: "
                                        + ((metaData == null)
                                                ? BassConstants.INVALID_BROADCAST_ID
                                                : metaData.getBroadcastId()))
                                + (", broadcastName: "
                                        + ((metaData == null) ? "" : metaData.getBroadcastName())));

                log("Force source to lost PA sync");
                Message message =
@@ -3041,9 +3045,9 @@ public class BassClientService extends ProfileService {
                message.arg1 = sourceId;
                message.arg2 = BassConstants.PA_SYNC_DO_NOT_SYNC;
                /* Pending remove set. Remove source once not synchronized to PA */
                /* MetaData can be null if source is from remote's receive state */
                message.obj = metaData;
                stateMachine.sendMessage(message);

                continue;
            }

+21 −17
Original line number Diff line number Diff line
@@ -1879,7 +1879,7 @@ public class BassClientStateMachine extends StateMachine {
        return res;
    }

    private byte[] convertBroadcastMetadataToUpdateSourceByteArray(
    private byte[] convertToUpdateSourceByteArray(
            int sourceId, BluetoothLeBroadcastMetadata metaData, int paSync) {
        BluetoothLeBroadcastReceiveState existingState =
                getBroadcastReceiveStateForSourceId(sourceId);
@@ -1887,8 +1887,10 @@ public class BassClientStateMachine extends StateMachine {
            log("no existing SI for update source op");
            return null;
        }
        List<BluetoothLeBroadcastSubgroup> subGroups = metaData.getSubgroups();
        byte numSubGroups = (byte) subGroups.size();
        int numSubGroups =
                (metaData != null)
                        ? metaData.getSubgroups().size()
                        : existingState.getNumSubgroups();
        byte[] res = new byte[UPDATE_SOURCE_FIXED_LENGTH + numSubGroups * 5];
        int offset = 0;
        // Opcode
@@ -1908,23 +1910,22 @@ public class BassClientStateMachine extends StateMachine {
        res[offset++] = (byte) 0xFF;
        res[offset++] = (byte) 0xFF;
        // Num_Subgroups
        res[offset++] = numSubGroups;
        res[offset++] = (byte) numSubGroups;

        for (BluetoothLeBroadcastSubgroup subGroup : subGroups) {
            int bisIndexValue;
        for (int i = 0; i < numSubGroups; i++) {
            int bisIndexValue = existingState.getBisSyncState().get(i).intValue();
            if (paSync == BassConstants.PA_SYNC_DO_NOT_SYNC) {
                bisIndexValue = 0;
            } else if (paSync == BassConstants.PA_SYNC_PAST_AVAILABLE
                    || paSync == BassConstants.PA_SYNC_PAST_NOT_AVAILABLE) {
                bisIndexValue = getBisSyncFromChannelPreference(subGroup.getChannels());

            } else if (metaData != null
                    && (paSync == BassConstants.PA_SYNC_PAST_AVAILABLE
                            || paSync == BassConstants.PA_SYNC_PAST_NOT_AVAILABLE)) {
                bisIndexValue =
                        getBisSyncFromChannelPreference(
                                metaData.getSubgroups().get(i).getChannels());
                // Let sink decide to which BIS sync if there is no channel preference
                if (bisIndexValue == 0) {
                    bisIndexValue = 0xFFFFFFFF;
                }
            } else {
                bisIndexValue =
                        existingState.getBisSyncState().get(subGroups.indexOf(subGroup)).intValue();
            }
            log("UPDATE_BCAST_SOURCE: bisIndexValue : " + bisIndexValue);
            // BIS_Sync
@@ -2239,9 +2240,9 @@ public class BassClientStateMachine extends StateMachine {
                    int sourceId = message.arg1;
                    int paSync = message.arg2;
                    log("Updating Broadcast source: " + metaData);
                    // Convert the source from either metadata or remote receive state
                    byte[] updateSourceInfo =
                            convertBroadcastMetadataToUpdateSourceByteArray(
                                    sourceId, metaData, paSync);
                            convertToUpdateSourceByteArray(sourceId, metaData, paSync);
                    if (updateSourceInfo == null) {
                        Log.e(TAG, "update source: source Info is NULL");
                        break;
@@ -2253,7 +2254,9 @@ public class BassClientStateMachine extends StateMachine {
                        if (paSync == BassConstants.PA_SYNC_DO_NOT_SYNC) {
                            setPendingRemove(sourceId, true);
                        }
                        if (metaData.isEncrypted() && (metaData.getBroadcastCode() != null)) {
                        if (metaData != null
                                && metaData.isEncrypted()
                                && metaData.getBroadcastCode() != null) {
                            mSetBroadcastCodePending = true;
                        }
                        mPendingMetadata = metaData;
@@ -2262,9 +2265,10 @@ public class BassClientStateMachine extends StateMachine {
                                GATT_TXN_TIMEOUT,
                                UPDATE_BCAST_SOURCE,
                                BassConstants.GATT_TXN_TIMEOUT_MS);
                        // convertToUpdateSourceByteArray ensures receive state valid for sourceId
                        sendMessageDelayed(
                                CANCEL_PENDING_SOURCE_OPERATION,
                                metaData.getBroadcastId(),
                                getBroadcastReceiveStateForSourceId(sourceId).getBroadcastId(),
                                BassConstants.SOURCE_OPERATION_TIMEOUT_MS);
                    } else {
                        Log.e(TAG, "UPDATE_BCAST_SOURCE: no Bluetooth Gatt handle, Fatal");
+51 −0
Original line number Diff line number Diff line
@@ -1868,6 +1868,57 @@ public class BassClientServiceTest {
        }
    }

    /**
     * Test whether service.removeSource() does send modify source if source is from remote receive
     * state. In this case, assistant should be able to remove source which was not managed by BASS
     * service (external manager/no source metadata)
     */
    @Test
    public void testRemoveSourceForGroupAndTriggerModifySourceWithoutMetadata() {
        prepareConnectedDeviceGroup();
        startSearchingForSources();
        onScanResult(mSourceDevice, TEST_BROADCAST_ID);
        onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE);
        BluetoothLeBroadcastMetadata meta = createBroadcastMetadata(TEST_BROADCAST_ID);

        for (BassClientStateMachine sm : mStateMachines.values()) {
            injectRemoteSourceStateSourceAdded(
                    sm,
                    meta,
                    TEST_SOURCE_ID,
                    BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCHRONIZED,
                    meta.isEncrypted()
                            ? BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING
                            : BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_NOT_ENCRYPTED,
                    null);
            // no current broadcast metadata for external broadcast source
            doReturn(null).when(sm).getCurrentBroadcastMetadata(eq(TEST_SOURCE_ID));
            doReturn(true).when(sm).isSyncedToTheSource(eq(TEST_SOURCE_ID));
        }

        for (BassClientStateMachine sm : mStateMachines.values()) {
            ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
            mBassClientService.removeSource(sm.getDevice(), TEST_SOURCE_ID);
            // Verify device get update source
            verify(sm, atLeast(1)).sendMessage(messageCaptor.capture());

            Optional<Message> msg =
                    messageCaptor.getAllValues().stream()
                            .filter(m -> m.what == BassClientStateMachine.UPDATE_BCAST_SOURCE)
                            .findFirst();
            assertThat(msg.isPresent()).isEqualTo(true);

            assertThat(msg.get().arg1).isEqualTo(TEST_SOURCE_ID);
            assertThat(msg.get().arg2).isEqualTo(BassConstants.PA_SYNC_DO_NOT_SYNC);
            // Verify metadata is null
            assertThat(msg.get().obj).isEqualTo(null);
        }

        for (BassClientStateMachine sm : mStateMachines.values()) {
            injectRemoteSourceStateRemoval(sm, TEST_SOURCE_ID);
        }
    }

    /** Test whether the group operation flag is set on addSource() and removed on removeSource */
    @Test
    public void testGroupStickyFlagSetUnset() {
+34 −18
Original line number Diff line number Diff line
@@ -1585,23 +1585,6 @@ public class BassClientStateMachineTest {
                    0x00, // broadcastIdBytes
                    (byte) BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_IDLE,
                    (byte) BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_CODE_REQUIRED,
                    // 16 bytes badBroadcastCode
                    0x00,
                    0x00,
                    0x00,
                    0x00,
                    0x00,
                    0x00,
                    0x00,
                    0x00,
                    0x00,
                    0x00,
                    0x00,
                    0x00,
                    0x00,
                    0x00,
                    0x00,
                    0x00,
                    0x01, // numSubGroups
                    // SubGroup #1
                    0x00,
@@ -2409,7 +2392,7 @@ public class BassClientStateMachineTest {
                    Utils.getByteAddress(mSourceTestDevice)[1],
                    Utils.getByteAddress(mSourceTestDevice)[0], // sourceAddress
                    0x00, // sourceAdvSid
                    0x00,
                    (byte) (TEST_BROADCAST_ID & 0xFF),
                    0x00,
                    0x00, // broadcastIdBytes
                    (byte) paSync,
@@ -2652,6 +2635,39 @@ public class BassClientStateMachineTest {
        verify(callbacks).notifyBassStateReady(eq(mTestDevice));
    }

    @Test
    public void updateBroadcastSource_withoutMetadata() {
        int sourceId = 1;
        int paSync = BassConstants.PA_SYNC_DO_NOT_SYNC;

        prepareInitialReceiveStateForGatt();

        generateBroadcastReceiveStatesAndVerify(
                mSourceTestDevice,
                TEST_SOURCE_ID,
                BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCHRONIZED,
                BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING,
                0x1L);

        BassClientStateMachine.BluetoothGattTestableWrapper btGatt =
                Mockito.mock(BassClientStateMachine.BluetoothGattTestableWrapper.class);
        mBassClientStateMachine.mBluetoothGatt = btGatt;
        BluetoothGattCharacteristic scanControlPoint =
                Mockito.mock(BluetoothGattCharacteristic.class);
        mBassClientStateMachine.mBroadcastScanControlPoint = scanControlPoint;

        mBassClientStateMachine.mPendingOperation = 0;
        mBassClientStateMachine.mPendingSourceId = 0;
        mBassClientStateMachine.mPendingMetadata = null;

        // update source without metadata
        sendMessageAndVerifyTransition(
                mBassClientStateMachine.obtainMessage(UPDATE_BCAST_SOURCE, sourceId, paSync, null),
                BassClientStateMachine.ConnectedProcessing.class);
        assertThat(mBassClientStateMachine.mPendingOperation).isEqualTo(UPDATE_BCAST_SOURCE);
        assertThat(mBassClientStateMachine.mPendingSourceId).isEqualTo(sourceId);
    }

    private void initToConnectingState() {
        allowConnection(true);
        allowConnectGatt(true);