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

Commit ad06c633 authored by Michal Belusiak's avatar Michal Belusiak
Browse files

Bass: Handle empty broadcast receive state using previous source ID

That is necessary to properly handle empty receive state in remove
source cases independently of remove request source.

Add sending notifySourceRemoved with REASON_REMOTE_REQUEST.

Removed setting of source IDs. According to the documentation,
this is the responsibility of the remote. Setting artificial source
IDs on host side could cause an issue where remote had more than one
receive state characteristic.

Add sending notifySourceAdded with REASON_REMOTE_REQUEST when it is
read from remote or added later by remote without host request.

Refactored processBroadcastReceiverState to make it more readable.

Bug: 372322278
Bug: 380231464
Test: atest BassClientServiceTest BassClientStateMachineTest
Change-Id: I77dc65b7bb0124e463ec3e82794ca7683af192ed
parent 2894aab9
Loading
Loading
Loading
Loading
+9 −4
Original line number Diff line number Diff line
@@ -1325,12 +1325,17 @@ public class BassClientService extends ProfileService {
            return false;
        }
        boolean isRoomAvailable = false;
        for (BluetoothLeBroadcastReceiveState recvState : stateMachine.getAllSources()) {
        List<BluetoothLeBroadcastReceiveState> sources = stateMachine.getAllSources();
        if (sources.size() < stateMachine.getMaximumSourceCapacity()) {
            isRoomAvailable = true;
        } else {
            for (BluetoothLeBroadcastReceiveState recvState : sources) {
                if (isEmptyBluetoothDevice(recvState.getSourceDevice())) {
                    isRoomAvailable = true;
                    break;
                }
            }
        }
        log("isRoomAvailable: " + isRoomAvailable);
        return isRoomAvailable;
    }
+251 −6
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import static android.Manifest.permission.BLUETOOTH_CONNECT;
import static android.Manifest.permission.BLUETOOTH_PRIVILEGED;

import static com.android.bluetooth.flags.Flags.leaudioBigDependsOnAudioState;
import static com.android.bluetooth.flags.Flags.leaudioBroadcastReceiveStateProcessingRefactor;
import static com.android.bluetooth.flags.Flags.leaudioBroadcastResyncHelper;

import android.annotation.Nullable;
@@ -155,6 +156,7 @@ class BassClientStateMachine extends StateMachine {
    private final Map<Integer, Boolean> mFirstTimeBisDiscoveryMap;
    private int mPASyncRetryCounter = 0;
    @VisibleForTesting int mNumOfBroadcastReceiverStates = 0;
    int mNumOfReadyBroadcastReceiverStates = 0;
    @VisibleForTesting int mPendingOperation = -1;
    @VisibleForTesting byte mPendingSourceId = -1;
    @VisibleForTesting BluetoothLeBroadcastMetadata mPendingMetadata = null;
@@ -975,10 +977,15 @@ class BassClientStateMachine extends StateMachine {
    }

    private boolean isSourceAbsent(BluetoothLeBroadcastReceiveState recvState) {
        return recvState.getSourceDevice() == null
        return recvState == null
                || recvState.getSourceDevice() == null
                || recvState.getSourceDevice().getAddress().equals("00:00:00:00:00:00");
    }

    private boolean isSourcePresent(BluetoothLeBroadcastReceiveState recvState) {
        return !isSourceAbsent(recvState);
    }

    private void checkAndUpdateBroadcastCode(BluetoothLeBroadcastReceiveState recvState) {
        log("checkAndUpdateBroadcastCode");
        // Whenever receive state indicated code requested, assistant should set the broadcast code
@@ -999,7 +1006,8 @@ class BassClientStateMachine extends StateMachine {
        }
    }

    private BluetoothLeBroadcastReceiveState parseBroadcastReceiverState(byte[] receiverState) {
    private BluetoothLeBroadcastReceiveState parseBroadcastReceiverStateObsolete(
            byte[] receiverState) {
        byte sourceId = 0;
        if (receiverState.length > 0) {
            sourceId = receiverState[BassConstants.BCAST_RCVR_STATE_SRC_ID_IDX];
@@ -1140,10 +1148,11 @@ class BassClientStateMachine extends StateMachine {
        return recvState;
    }

    private void processBroadcastReceiverState(
    private void processBroadcastReceiverStateObsolete(
            byte[] receiverState, BluetoothGattCharacteristic characteristic) {
        log("processBroadcastReceiverState: characteristic:" + characteristic);
        BluetoothLeBroadcastReceiveState recvState = parseBroadcastReceiverState(receiverState);
        BluetoothLeBroadcastReceiveState recvState =
                parseBroadcastReceiverStateObsolete(receiverState);
        if (recvState == null) {
            log("processBroadcastReceiverState: Null recvState");
            return;
@@ -1249,6 +1258,226 @@ class BassClientStateMachine extends StateMachine {
        broadcastReceiverState(recvState, sourceId);
    }

    private BluetoothLeBroadcastReceiveState parseBroadcastReceiverState(
            byte[] receiverState, int previousSourceId) {
        log("parseBroadcastReceiverState: receiverState length: " + receiverState.length);

        BluetoothLeBroadcastReceiveState recvState = null;
        if (receiverState.length == 0) {
            byte[] emptyBluetoothDeviceAddress = Utils.getBytesFromAddress("00:00:00:00:00:00");
            if (previousSourceId != BassConstants.INVALID_SOURCE_ID) {
                recvState =
                        new BluetoothLeBroadcastReceiveState(
                                previousSourceId,
                                BluetoothDevice.ADDRESS_TYPE_PUBLIC, // sourceAddressType
                                mAdapterService.getDeviceFromByte(
                                        emptyBluetoothDeviceAddress), // sourceDev
                                0, // sourceAdvertisingSid
                                0, // broadcastId
                                BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_IDLE, // paSyncState
                                // bigEncryptionState
                                BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_NOT_ENCRYPTED,
                                null, // badCode
                                0, // numSubgroups
                                Arrays.asList(new Long[0]), // bisSyncState
                                Arrays.asList(
                                        new BluetoothLeAudioContentMetadata[0]) // subgroupMetadata
                                );
            } else {
                log("parseBroadcastReceiverState: unknown sourceId");
            }
        } else {
            byte sourceId = receiverState[BassConstants.BCAST_RCVR_STATE_SRC_ID_IDX];
            byte paSyncState = receiverState[BassConstants.BCAST_RCVR_STATE_PA_SYNC_IDX];
            byte bigEncryptionStatus = receiverState[BassConstants.BCAST_RCVR_STATE_ENC_STATUS_IDX];
            byte[] badBroadcastCode = null;
            int badBroadcastCodeLen = 0;
            if (bigEncryptionStatus
                    == BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_BAD_CODE) {
                badBroadcastCode = new byte[BassConstants.BCAST_RCVR_STATE_BADCODE_SIZE];
                System.arraycopy(
                        receiverState,
                        BassConstants.BCAST_RCVR_STATE_BADCODE_START_IDX,
                        badBroadcastCode,
                        0,
                        BassConstants.BCAST_RCVR_STATE_BADCODE_SIZE);
                badBroadcastCodeLen = BassConstants.BCAST_RCVR_STATE_BADCODE_SIZE;
            }
            byte numSubGroups =
                    receiverState[
                            BassConstants.BCAST_RCVR_STATE_BADCODE_START_IDX + badBroadcastCodeLen];
            int offset = BassConstants.BCAST_RCVR_STATE_BADCODE_START_IDX + badBroadcastCodeLen + 1;
            ArrayList<BluetoothLeAudioContentMetadata> metadataList =
                    new ArrayList<BluetoothLeAudioContentMetadata>();
            ArrayList<Long> bisSyncState = new ArrayList<Long>();
            for (int i = 0; i < numSubGroups; i++) {
                byte[] bisSyncIndex = new byte[Long.BYTES];
                System.arraycopy(
                        receiverState,
                        offset,
                        bisSyncIndex,
                        0,
                        BassConstants.BCAST_RCVR_STATE_BIS_SYNC_SIZE);
                offset += BassConstants.BCAST_RCVR_STATE_BIS_SYNC_SIZE;
                bisSyncState.add((long) Utils.byteArrayToLong(bisSyncIndex));

                int metaDataLength = receiverState[offset++] & 0xFF;
                if (metaDataLength > 0) {
                    log("metadata of length: " + metaDataLength + "is available");
                    byte[] metaData = new byte[metaDataLength];
                    System.arraycopy(receiverState, offset, metaData, 0, metaDataLength);
                    offset += metaDataLength;
                    metadataList.add(BluetoothLeAudioContentMetadata.fromRawBytes(metaData));
                } else {
                    metadataList.add(BluetoothLeAudioContentMetadata.fromRawBytes(new byte[0]));
                }
            }
            byte[] broadcastIdBytes = new byte[mBroadcastSourceIdLength];
            System.arraycopy(
                    receiverState,
                    BassConstants.BCAST_RCVR_STATE_SRC_BCAST_ID_START_IDX,
                    broadcastIdBytes,
                    0,
                    mBroadcastSourceIdLength);
            int broadcastId = BassUtils.parseBroadcastId(broadcastIdBytes);
            byte[] sourceAddress = new byte[BassConstants.BCAST_RCVR_STATE_SRC_ADDR_SIZE];
            System.arraycopy(
                    receiverState,
                    BassConstants.BCAST_RCVR_STATE_SRC_ADDR_START_IDX,
                    sourceAddress,
                    0,
                    BassConstants.BCAST_RCVR_STATE_SRC_ADDR_SIZE);
            byte sourceAddressType =
                    receiverState[BassConstants.BCAST_RCVR_STATE_SRC_ADDR_TYPE_IDX];
            BassUtils.reverse(sourceAddress);
            String address = Utils.getAddressStringFromByte(sourceAddress);
            BluetoothDevice device =
                    BluetoothAdapter.getDefaultAdapter()
                            .getRemoteLeDevice(address, sourceAddressType);
            byte sourceAdvSid = receiverState[BassConstants.BCAST_RCVR_STATE_SRC_ADV_SID_IDX];
            recvState =
                    new BluetoothLeBroadcastReceiveState(
                            sourceId,
                            (int) sourceAddressType,
                            device,
                            sourceAdvSid,
                            broadcastId,
                            (int) paSyncState,
                            (int) bigEncryptionStatus,
                            badBroadcastCode,
                            numSubGroups,
                            bisSyncState,
                            metadataList);
        }
        return recvState;
    }

    private void processBroadcastReceiverState(
            byte[] receiverState, BluetoothGattCharacteristic characteristic) {
        log(
                "processBroadcastReceiverState: characteristic:"
                        + characteristic
                        + ", instanceId:"
                        + characteristic.getInstanceId());

        BluetoothLeBroadcastReceiveState prevRecvState =
                mBluetoothLeBroadcastReceiveStates.get(characteristic.getInstanceId());
        if (prevRecvState == null
                && (mBluetoothLeBroadcastReceiveStates.size() == mNumOfBroadcastReceiverStates)) {
            Log.e(TAG, "processBroadcastReceiverState: reached the Max SourceInfos");
            return;
        }

        int prevSourceId = BassConstants.INVALID_SOURCE_ID;
        if (prevRecvState != null) {
            prevSourceId = prevRecvState.getSourceId();
        }

        BluetoothLeBroadcastReceiveState recvState =
                parseBroadcastReceiverState(receiverState, prevSourceId);
        if (recvState == null) {
            log("processBroadcastReceiverState: Null recvState");
            return;
        }

        log("processBroadcastReceiverState: Updated receiver state: " + recvState);
        mBluetoothLeBroadcastReceiveStates.put(characteristic.getInstanceId(), recvState);
        int sourceId = recvState.getSourceId();

        if (isSourceAbsent(prevRecvState) && isSourcePresent(recvState)) {
            log("processBroadcastReceiverState: Source Addition");
            removeMessages(CANCEL_PENDING_SOURCE_OPERATION);
            if (mPendingMetadata != null) {
                setCurrentBroadcastMetadata(sourceId, mPendingMetadata);
                mPendingMetadata = null;
            }
            if (mPendingOperation == ADD_BCAST_SOURCE) {
                mService.getCallbacks()
                        .notifySourceAdded(
                                mDevice, recvState, BluetoothStatusCodes.REASON_LOCAL_APP_REQUEST);
            } else {
                mService.getCallbacks()
                        .notifySourceAdded(
                                mDevice, recvState, BluetoothStatusCodes.REASON_REMOTE_REQUEST);
            }
            checkAndUpdateBroadcastCode(recvState);
            processPASyncState(recvState);
            processSyncStateChangeStats(recvState);
        } else if (isSourcePresent(prevRecvState) && isSourcePresent(recvState)) {
            log("processBroadcastReceiverState: Source Update");
            removeMessages(CANCEL_PENDING_SOURCE_OPERATION);
            if (mPendingMetadata != null) {
                setCurrentBroadcastMetadata(sourceId, mPendingMetadata);
                mPendingMetadata = null;
            }
            mService.getCallbacks()
                    .notifySourceModified(
                            mDevice, sourceId, BluetoothStatusCodes.REASON_LOCAL_APP_REQUEST);
            checkAndUpdateBroadcastCode(recvState);
            processPASyncState(recvState);
            processSyncStateChangeStats(recvState);

            if (isPendingRemove(sourceId) && !isSyncedToTheSource(sourceId)) {
                Message message = obtainMessage(REMOVE_BCAST_SOURCE);
                message.arg1 = sourceId;
                sendMessage(message);
            }
        } else if (isSourcePresent(prevRecvState) && isSourceAbsent(recvState)) {
            BluetoothDevice removedDevice = prevRecvState.getSourceDevice();
            log("processBroadcastReceiverState: Source Removal " + removedDevice);
            if (!Flags.leaudioBroadcastExtractPeriodicScannerFromStateMachine()) {
                cancelActiveSync(mService.getSyncHandleForBroadcastId(recvState.getBroadcastId()));
            }
            BluetoothLeBroadcastMetadata metaData = getCurrentBroadcastMetadata(sourceId);
            if (metaData != null) {
                logBroadcastSyncStatsWithStatus(
                        metaData.getBroadcastId(),
                        BluetoothStatsLog
                                .BROADCAST_AUDIO_SYNC_REPORTED__SYNC_STATUS__SYNC_STATUS_UNKNOWN);
            }
            setCurrentBroadcastMetadata(sourceId, null);
            if (mPendingSourceToSwitch != null) {
                // Source remove is triggered by switch source request
                mService.getCallbacks()
                        .notifySourceRemoved(
                                mDevice, sourceId, BluetoothStatusCodes.REASON_LOCAL_STACK_REQUEST);
                log("processBroadcastReceiverState: Source Switching");
                Message message = obtainMessage(ADD_BCAST_SOURCE);
                message.obj = mPendingSourceToSwitch;
                sendMessage(message);
            } else if (mPendingOperation == REMOVE_BCAST_SOURCE) {
                mService.getCallbacks()
                        .notifySourceRemoved(
                                mDevice, sourceId, BluetoothStatusCodes.REASON_LOCAL_APP_REQUEST);
            } else {
                mService.getCallbacks()
                        .notifySourceRemoved(
                                mDevice, sourceId, BluetoothStatusCodes.REASON_REMOTE_REQUEST);
            }
        }
        broadcastReceiverState(recvState, sourceId);
    }

    // Implements callback methods for GATT events that the app cares about.
    // For example, connection change and services discovered.
    final class GattCallback extends BluetoothGattCallback {
@@ -1325,7 +1554,17 @@ class BassClientStateMachine extends StateMachine {
                        characteristic.getValue(),
                        0,
                        characteristic.getValue().length);
                if (leaudioBroadcastReceiveStateProcessingRefactor()) {
                    processBroadcastReceiverState(characteristic.getValue(), characteristic);
                    mNumOfReadyBroadcastReceiverStates++;
                    if (mNumOfReadyBroadcastReceiverStates == mNumOfBroadcastReceiverStates) {
                        // Notify service BASS state ready for operations
                        mService.getCallbacks().notifyBassStateReady(mDevice);
                    }
                } else {
                    processBroadcastReceiverStateObsolete(
                            characteristic.getValue(), characteristic);
                }
            }
            // switch to receiving notifications after initial characteristic read
            BluetoothGattDescriptor desc =
@@ -1376,7 +1615,12 @@ class BassClientStateMachine extends StateMachine {
                    Log.e(TAG, "Remote receiver state is NULL");
                    return;
                }
                if (leaudioBroadcastReceiveStateProcessingRefactor()) {
                    processBroadcastReceiverState(characteristic.getValue(), characteristic);
                } else {
                    processBroadcastReceiverStateObsolete(
                            characteristic.getValue(), characteristic);
                }
            }
        }

@@ -1625,6 +1869,7 @@ class BassClientStateMachine extends StateMachine {
            mBroadcastScanControlPoint = null;
        }
        mNumOfBroadcastReceiverStates = 0;
        mNumOfReadyBroadcastReceiverStates = 0;
        if (mBluetoothLeBroadcastReceiveStates != null) {
            mBluetoothLeBroadcastReceiveStates.clear();
        }
+24 −1
Original line number Diff line number Diff line
@@ -140,7 +140,7 @@ public class BassClientServiceTest {
    // German language code in ISO 639-3
    private static final String TEST_LANGUAGE = "deu";
    private static final int TEST_SOURCE_ID = 10;
    private static final int TEST_NUM_SOURCES = 2;
    private static final int TEST_NUM_SOURCES = 1;

    private final HashMap<BluetoothDevice, BassClientStateMachine> mStateMachines = new HashMap<>();
    private HashMap<BluetoothDevice, LinkedBlockingQueue<Intent>> mIntentQueue;
@@ -2176,6 +2176,29 @@ public class BassClientServiceTest {
        }
    }

    @Test
    public void testSecondAddSourceWithCapacityGreaterThanOne() {
        prepareConnectedDeviceGroup();

        // Set maximum source capacity to 2
        for (BassClientStateMachine sm : mStateMachines.values()) {
            doReturn(2).when(sm).getMaximumSourceCapacity();
        }

        startSearchingForSources();
        onScanResult(mSourceDevice, TEST_BROADCAST_ID);
        onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE);
        BluetoothLeBroadcastMetadata meta = createBroadcastMetadata(TEST_BROADCAST_ID);
        verifyAddSourceForGroup(meta);
        prepareRemoteSourceState(meta, true, true);

        // Add another new broadcast source
        onScanResult(mSourceDevice2, TEST_BROADCAST_ID + 1);
        onSyncEstablished(mSourceDevice2, TEST_SYNC_HANDLE + 1);
        BluetoothLeBroadcastMetadata newMeta = createBroadcastMetadata(TEST_BROADCAST_ID + 1);
        verifyAddSourceForGroup(newMeta);
    }

    /**
     * Test that after multiple calls to service.addSource() with a group operation flag set, there
     * are two call to service.removeSource() needed to clear the flag
+447 −23

File changed.

Preview size limit exceeded, changes collapsed.