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

Commit 028c2458 authored by Michal Belusiak's avatar Michal Belusiak
Browse files

Bass: Handle sync info request in all cases

While sink ask for sync info request when BASS is not synchronized
to the broadcaster, first synchronized it and then send PAST.

Remove paused sink when receiver is active or requesting for sync info.
StopBigMonitoring when all receivers are active and no pending paused
sinks.

Do not monitor BIG if sink is active or requesting for sync info.

While resume receivers source synchronization, do not modify source,
if PA is synced or requested.

Bug: 372522289
Bug: 366294688
Bug: 363168099
Test: atest BassClientServiceTest BassClientStateMachineTest
Change-Id: If728689e8b7d1ce3e571a14df7c0893bc00950e2
parent 6976c730
Loading
Loading
Loading
Loading
+129 −39
Original line number Diff line number Diff line
@@ -169,6 +169,8 @@ public class BassClientService extends ProfileService {
    private final Map<BluetoothDevice, BluetoothLeBroadcastMetadata> mBroadcastMetadataMap =
            new ConcurrentHashMap<>();
    private final HashSet<BluetoothDevice> mPausedBroadcastSinks = new HashSet<>();
    private final Map<BluetoothDevice, Pair<Integer, Integer>> mSinksWaitingForPast =
            new HashMap<>();
    private final Map<Integer, PauseType> mPausedBroadcastIds = new HashMap<>();
    private final Deque<AddSourceData> mPendingAddSources = new ArrayDeque<>();
    private final Map<Integer, HashSet<BluetoothDevice>> mLocalBroadcastReceivers =
@@ -276,13 +278,6 @@ public class BassClientService extends ProfileService {
                                            {
                                                log("MESSAGE_BIG_MONITOR_TIMEOUT");
                                                stopSourceReceivers(broadcastId);
                                                synchronized (mSearchScanCallbackLock) {
                                                    // when searching is stopped then clear all sync
                                                    // data
                                                    if (mSearchScanCallback == null) {
                                                        clearAllSyncData();
                                                    }
                                                }
                                                break;
                                            }
                                        default:
@@ -1107,6 +1102,24 @@ public class BassClientService extends ProfileService {
        }
    }

    void syncRequestForPast(BluetoothDevice sink, int broadcastId, int sourceId) {
        log(
                "syncRequestForPast sink: "
                        + sink
                        + ", broadcastId: "
                        + broadcastId
                        + ", sourceId: "
                        + sourceId);

        if (!leaudioBroadcastResyncHelper()) {
            return;
        }
        synchronized (mSinksWaitingForPast) {
            mSinksWaitingForPast.put(sink, new Pair<Integer, Integer>(broadcastId, sourceId));
        }
        addSelectSourceRequest(broadcastId, true);
    }

    private void localNotifyReceiveStateChanged(
            BluetoothDevice sink, BluetoothLeBroadcastReceiveState receiveState) {
        int broadcastId = receiveState.getBroadcastId();
@@ -1114,24 +1127,14 @@ public class BassClientService extends ProfileService {
                && !isLocalBroadcast(receiveState)
                && !isEmptyBluetoothDevice(receiveState.getSourceDevice())
                && !isHostPauseType(broadcastId)) {
            boolean isReadyToAutoResync = false;
            if (receiveState.getPaSyncState()
                    == BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCHRONIZED) {
                isReadyToAutoResync = true;
            } else {
                for (int i = 0; i < receiveState.getNumSubgroups(); i++) {
                    Long syncState = receiveState.getBisSyncState().get(i);
                    /* Synced to BIS */
                    if (syncState != BassConstants.BIS_SYNC_NOT_SYNC_TO_BIS
                            && syncState != BassConstants.BIS_SYNC_FAILED_SYNC_TO_BIG) {
                        isReadyToAutoResync = true;
                        break;
                    }
                }
            }

            if (isReadyToAutoResync) {
            if (isReceiverActive(receiveState)
                    || receiveState.getPaSyncState()
                            == BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCINFO_REQUEST) {
                mPausedBroadcastSinks.remove(sink);
                if (isAllReceiversActive(broadcastId) && mPausedBroadcastSinks.isEmpty()) {
                    stopBigMonitoring(broadcastId, false);
                }
            } else if (!mPausedBroadcastIds.containsKey(broadcastId)) {
                if (mCachedBroadcasts.containsKey(broadcastId)) {
                    addSelectSourceRequest(broadcastId, true);
@@ -1144,6 +1147,9 @@ public class BassClientService extends ProfileService {
                }
            }
        } else if (isEmptyBluetoothDevice(receiveState.getSourceDevice())) {
            synchronized (mSinksWaitingForPast) {
                mSinksWaitingForPast.remove(sink);
            }
            checkAndStopBigMonitoring();
        }

@@ -1584,8 +1590,9 @@ public class BassClientService extends ProfileService {
        if (toState == BluetoothProfile.STATE_DISCONNECTED) {
            mPendingGroupOp.remove(device);
            mPausedBroadcastSinks.remove(device);

            checkAndStopBigMonitoring();
            synchronized (mSinksWaitingForPast) {
                mSinksWaitingForPast.remove(device);
            }

            int bondState = mAdapterService.getBondState(device);
            if (bondState == BluetoothDevice.BOND_NONE) {
@@ -1593,6 +1600,20 @@ public class BassClientService extends ProfileService {
                removeStateMachine(device);
            }

            checkAndStopBigMonitoring();

            if (getConnectedDevices().isEmpty()
                    || (mPausedBroadcastSinks.isEmpty()
                            && mSinksWaitingForPast.isEmpty()
                            && !isAnyConnectedDeviceSwitchingSource())) {
                synchronized (mSearchScanCallbackLock) {
                    // when searching is stopped then clear all sync data
                    if (mSearchScanCallback == null) {
                        clearAllSyncData();
                    }
                }
            }

            /* Restore allowed context mask for unicast in case if last connected broadcast
             * delegator device which has external source disconnectes.
             */
@@ -2167,6 +2188,41 @@ public class BassClientService extends ProfileService {
                }
                mBisDiscoveryCounterMap.put(syncHandle, MAX_BIS_DISCOVERY_TRIES_NUM);

                synchronized (mSinksWaitingForPast) {
                    Iterator<Map.Entry<BluetoothDevice, Pair<Integer, Integer>>> iterator =
                            mSinksWaitingForPast.entrySet().iterator();
                    while (iterator.hasNext()) {
                        Map.Entry<BluetoothDevice, Pair<Integer, Integer>> entry = iterator.next();
                        BluetoothDevice sinkDevice = entry.getKey();
                        int broadcastIdForPast = entry.getValue().first;
                        if (broadcastId == broadcastIdForPast) {
                            int sourceId = entry.getValue().second;
                            synchronized (mStateMachines) {
                                BassClientStateMachine sm = getOrCreateStateMachine(sinkDevice);
                                Message message =
                                        sm.obtainMessage(
                                                BassClientStateMachine.INITIATE_PA_SYNC_TRANSFER);
                                message.arg1 = syncHandle;
                                message.arg2 = sourceId;
                                sm.sendMessage(message);
                            }
                            synchronized (mPendingSourcesToAdd) {
                                Iterator<AddSourceData> addIterator =
                                        mPendingSourcesToAdd.iterator();
                                while (addIterator.hasNext()) {
                                    AddSourceData pendingSourcesToAdd = addIterator.next();
                                    if (pendingSourcesToAdd.mSourceMetadata.getBroadcastId()
                                                    == broadcastId
                                            && pendingSourcesToAdd.mSink.equals(sinkDevice)) {
                                        addIterator.remove();
                                    }
                                }
                            }
                            iterator.remove();
                        }
                    }
                }

                synchronized (mPendingSourcesToAdd) {
                    Iterator<AddSourceData> iterator = mPendingSourcesToAdd.iterator();
                    while (iterator.hasNext()) {
@@ -3698,14 +3754,23 @@ public class BassClientService extends ProfileService {
                                .filter(e -> e.getBroadcastId() == metadata.getBroadcastId())
                                .findAny();

                if (leaudioBroadcastResyncHelper()
                        && receiveState.isPresent()
                        && (receiveState.get().getPaSyncState()
                                        == BluetoothLeBroadcastReceiveState
                                                .PA_SYNC_STATE_SYNCINFO_REQUEST
                                || receiveState.get().getPaSyncState()
                                        == BluetoothLeBroadcastReceiveState
                                                .PA_SYNC_STATE_SYNCHRONIZED)) {
                    iterator.remove();
                    continue;
                }

                List<Integer> activeSyncedSrc = getActiveSyncedSources();

                if (receiveState.isPresent()
                        && (!leaudioBroadcastResyncHelper()
                                || isLocalBroadcast(metadata)
                                || receiveState.get().getPaSyncState()
                                        == BluetoothLeBroadcastReceiveState
                                                .PA_SYNC_STATE_SYNCHRONIZED
                                || activeSyncedSrc.contains(
                                        getSyncHandleForBroadcastId(metadata.getBroadcastId())))) {
                    int sourceId = receiveState.get().getSourceId();
@@ -3862,24 +3927,49 @@ public class BassClientService extends ProfileService {
        return false;
    }

    private Set<Integer> getExternalBroadcastsActiveOnSinks() {
        HashSet<Integer> syncedBroadcasts = new HashSet<>();
        for (BluetoothDevice device : getConnectedDevices()) {
            for (BluetoothLeBroadcastReceiveState receiveState : getAllSources(device)) {
                if (!isLocalBroadcast(receiveState)) {
    private boolean isReceiverActive(BluetoothLeBroadcastReceiveState receiveState) {
        if (receiveState.getPaSyncState()
                == BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCHRONIZED) {
            return true;
        } else {
            for (int i = 0; i < receiveState.getNumSubgroups(); i++) {
                Long syncState = receiveState.getBisSyncState().get(i);
                /* Synced to BIS */
                if (syncState != BassConstants.BIS_SYNC_NOT_SYNC_TO_BIS
                        && syncState != BassConstants.BIS_SYNC_FAILED_SYNC_TO_BIG) {
                    return true;
                }
            }
        }
        return false;
    }

    private Set<Integer> getExternalBroadcastsActiveOnSinks() {
        HashSet<Integer> syncedBroadcasts = new HashSet<>();
        for (BluetoothDevice device : getConnectedDevices()) {
            for (BluetoothLeBroadcastReceiveState receiveState : getAllSources(device)) {
                if (isLocalBroadcast(receiveState)) {
                    continue;
                }
                if (isReceiverActive(receiveState)) {
                    syncedBroadcasts.add(receiveState.getBroadcastId());
                    log("getExternalBroadcastsActiveOnSinks: " + receiveState);
                }
            }
        }
        return syncedBroadcasts;
    }

    private boolean isAllReceiversActive(int broadcastId) {
        for (BluetoothDevice device : getConnectedDevices()) {
            for (BluetoothLeBroadcastReceiveState receiveState : getAllSources(device)) {
                if (receiveState.getBroadcastId() == broadcastId
                        && !isReceiverActive(receiveState)) {
                    return false;
                }
        return syncedBroadcasts;
            }
        }
        return true;
    }

    /** Get sink devices synced to the broadcasts */
+78 −59
Original line number Diff line number Diff line
@@ -84,8 +84,7 @@ import java.util.Scanner;
import java.util.UUID;
import java.util.stream.IntStream;

@VisibleForTesting
public class BassClientStateMachine extends StateMachine {
class BassClientStateMachine extends StateMachine {
    private static final String TAG = "BassClientStateMachine";
    @VisibleForTesting static final byte[] REMOTE_SCAN_STOP = {00};
    @VisibleForTesting static final byte[] REMOTE_SCAN_START = {01};
@@ -113,6 +112,7 @@ public class BassClientStateMachine extends StateMachine {
    static final int REACHED_MAX_SOURCE_LIMIT = 16;
    static final int SWITCH_BCAST_SOURCE = 17;
    static final int CANCEL_PENDING_SOURCE_OPERATION = 18;
    static final int INITIATE_PA_SYNC_TRANSFER = 19;

    // NOTE: the value is not "final" - it is modified in the unit tests
    @VisibleForTesting private int mConnectTimeoutMs;
@@ -816,43 +816,11 @@ public class BassClientStateMachine extends StateMachine {
        int state = recvState.getPaSyncState();
        if (state == BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCINFO_REQUEST) {
            log("Initiate PAST procedure");
            PeriodicAdvertisementResult result =
                    mService.getPeriodicAdvertisementResult(
                            recvState.getSourceDevice(), recvState.getBroadcastId());
            if (result != null) {
                int syncHandle = result.getSyncHandle();
                log("processPASyncState: syncHandle " + result.getSyncHandle());
                if (syncHandle != BassConstants.INVALID_SYNC_HANDLE) {
                    serviceData = 0x000000FF & recvState.getSourceId();
                    serviceData = serviceData << 8;
                    // advA matches EXT_ADV_ADDRESS
                    // also matches source address (as we would have written)
                    serviceData =
                            serviceData & (~BassConstants.ADV_ADDRESS_DONT_MATCHES_EXT_ADV_ADDRESS);
                    serviceData =
                            serviceData
                                    & (~BassConstants.ADV_ADDRESS_DONT_MATCHES_SOURCE_ADV_ADDRESS);
                    log(
                            "Initiate PAST for: "
                                    + mDevice
                                    + ", syncHandle: "
                                    + syncHandle
                                    + "serviceData"
                                    + serviceData);
                    BluetoothMethodProxy.getInstance()
                            .periodicAdvertisingManagerTransferSync(
                                    BassClientPeriodicAdvertisingManager
                                            .getPeriodicAdvertisingManager(),
                                    mDevice,
                                    serviceData,
                                    syncHandle);
                }
            } else {
                BluetoothLeBroadcastMetadata currentMetadata =
                        getCurrentBroadcastMetadata(recvState.getSourceId());
            int sourceId = recvState.getSourceId();
            BluetoothLeBroadcastMetadata currentMetadata = getCurrentBroadcastMetadata(sourceId);
            if (mService.isLocalBroadcast(currentMetadata)) {
                int advHandle = currentMetadata.getSourceAdvertisingSid();
                    serviceData = 0x000000FF & recvState.getSourceId();
                serviceData = 0x000000FF & sourceId;
                serviceData = serviceData << 8;
                // Address we set in the Source Address can differ from the address in the air
                serviceData =
@@ -873,9 +841,52 @@ public class BassClientStateMachine extends StateMachine {
                                advHandle,
                                mLocalPeriodicAdvCallback);
            } else {
                    Log.e(TAG, "There is no valid sync handle for this Source");
                int broadcastId = recvState.getBroadcastId();
                PeriodicAdvertisementResult result =
                        mService.getPeriodicAdvertisementResult(
                                recvState.getSourceDevice(), broadcastId);
                if (result != null) {
                    int syncHandle = result.getSyncHandle();
                    if (syncHandle != BassConstants.INVALID_SYNC_HANDLE) {
                        initiatePaSyncTransfer(syncHandle, sourceId);
                        return;
                    }
                }
                mService.syncRequestForPast(mDevice, broadcastId, sourceId);
            }
        }
    }

    private void initiatePaSyncTransfer(int syncHandle, int sourceId) {
        if (syncHandle != BassConstants.INVALID_SYNC_HANDLE
                && sourceId != BassConstants.INVALID_SOURCE_ID) {
            int serviceData = 0x000000FF & sourceId;
            serviceData = serviceData << 8;
            // advA matches EXT_ADV_ADDRESS
            // also matches source address (as we would have written)
            serviceData = serviceData & (~BassConstants.ADV_ADDRESS_DONT_MATCHES_EXT_ADV_ADDRESS);
            serviceData =
                    serviceData & (~BassConstants.ADV_ADDRESS_DONT_MATCHES_SOURCE_ADV_ADDRESS);
            log(
                    "Initiate PAST for: "
                            + mDevice
                            + ", syncHandle: "
                            + syncHandle
                            + ", serviceData: "
                            + serviceData);
            BluetoothMethodProxy.getInstance()
                    .periodicAdvertisingManagerTransferSync(
                            BassClientPeriodicAdvertisingManager.getPeriodicAdvertisingManager(),
                            mDevice,
                            serviceData,
                            syncHandle);
        } else {
            Log.e(
                    TAG,
                    "Invalid syncHandle or sourceId for PAST, syncHandle: "
                            + syncHandle
                            + ", sourceId: "
                            + sourceId);
        }
    }

@@ -2350,6 +2361,11 @@ public class BassClientStateMachine extends StateMachine {
                    int broadcastId = message.arg1;
                    cancelPendingSourceOperation(broadcastId);
                    break;
                case INITIATE_PA_SYNC_TRANSFER:
                    int syncHandle = message.arg1;
                    int sourceIdForPast = message.arg2;
                    initiatePaSyncTransfer(syncHandle, sourceIdForPast);
                    break;
                default:
                    log("CONNECTED: not handled message:" + message.what);
                    return NOT_HANDLED;
@@ -2539,6 +2555,7 @@ public class BassClientStateMachine extends StateMachine {
                case REACHED_MAX_SOURCE_LIMIT:
                case SWITCH_BCAST_SOURCE:
                case PSYNC_ACTIVE_TIMEOUT:
                case INITIATE_PA_SYNC_TRANSFER:
                    log(
                            "defer the message: "
                                    + messageWhatToString(message.what)
@@ -2646,6 +2663,8 @@ public class BassClientStateMachine extends StateMachine {
                return "CONNECT_TIMEOUT";
            case CANCEL_PENDING_SOURCE_OPERATION:
                return "CANCEL_PENDING_SOURCE_OPERATION";
            case INITIATE_PA_SYNC_TRANSFER:
                return "INITIATE_PA_SYNC_TRANSFER";
            default:
                break;
        }
+177 −4

File changed.

Preview size limit exceeded, changes collapsed.

+38 −1
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@ import static com.android.bluetooth.bass_client.BassClientStateMachine.CONNECT_T
import static com.android.bluetooth.bass_client.BassClientStateMachine.DISCONNECT;
import static com.android.bluetooth.bass_client.BassClientStateMachine.GATT_TXN_PROCESSED;
import static com.android.bluetooth.bass_client.BassClientStateMachine.GATT_TXN_TIMEOUT;
import static com.android.bluetooth.bass_client.BassClientStateMachine.INITIATE_PA_SYNC_TRANSFER;
import static com.android.bluetooth.bass_client.BassClientStateMachine.PSYNC_ACTIVE_TIMEOUT;
import static com.android.bluetooth.bass_client.BassClientStateMachine.REACHED_MAX_SOURCE_LIMIT;
import static com.android.bluetooth.bass_client.BassClientStateMachine.READ_BASS_CHARACTERISTICS;
@@ -894,12 +895,23 @@ public class BassClientStateMachineTest {
        when(characteristic.getValue()).thenReturn(value);
        when(mBassClientService.getPeriodicAdvertisementResult(any(), anyInt()))
                .thenReturn(paResult);
        when(paResult.getSyncHandle()).thenReturn(100);
        int syncHandle = 100;
        when(paResult.getSyncHandle()).thenReturn(syncHandle);

        Mockito.clearInvocations(callbacks);
        cb.onCharacteristicRead(null, characteristic, GATT_SUCCESS);
        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());

        int serviceData = 0x000000FF & sourceId;
        serviceData = serviceData << 8;
        // advA matches EXT_ADV_ADDRESS
        // also matches source address (as we would have written)
        serviceData = serviceData & (~BassConstants.ADV_ADDRESS_DONT_MATCHES_EXT_ADV_ADDRESS);
        serviceData = serviceData & (~BassConstants.ADV_ADDRESS_DONT_MATCHES_SOURCE_ADV_ADDRESS);
        verify(mMethodProxy)
                .periodicAdvertisingManagerTransferSync(
                        any(), any(), eq(serviceData), eq(syncHandle));

        verify(callbacks)
                .notifyReceiveStateChanged(any(), eq(sourceId), receiveStateCaptor.capture());
        Assert.assertEquals(receiveStateCaptor.getValue().getSourceDevice(), mSourceTestDevice);
@@ -1706,6 +1718,26 @@ public class BassClientStateMachineTest {
        assertThat(mBassClientStateMachine.mPendingSourceId).isEqualTo(sid);
    }

    @Test
    public void sendInitiatePaSyncTransferMessage_inConnectedState() {
        initToConnectedState();
        int syncHandle = 1234;
        int sourceId = 4321;

        mBassClientStateMachine.sendMessage(INITIATE_PA_SYNC_TRANSFER, syncHandle, sourceId);
        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());

        int serviceData = 0x000000FF & sourceId;
        serviceData = serviceData << 8;
        // advA matches EXT_ADV_ADDRESS
        // also matches source address (as we would have written)
        serviceData = serviceData & (~BassConstants.ADV_ADDRESS_DONT_MATCHES_EXT_ADV_ADDRESS);
        serviceData = serviceData & (~BassConstants.ADV_ADDRESS_DONT_MATCHES_SOURCE_ADV_ADDRESS);
        verify(mMethodProxy)
                .periodicAdvertisingManagerTransferSync(
                        any(), any(), eq(serviceData), eq(syncHandle));
    }

    @Test
    public void sendConnectMessage_inConnectedProcessingState_doesNotChangeState() {
        initToConnectedProcessingState();
@@ -1906,6 +1938,11 @@ public class BassClientStateMachineTest {
        mBassClientStateMachine.sendMessage(SWITCH_BCAST_SOURCE);
        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
        assertThat(mBassClientStateMachine.hasDeferredMessagesSuper(SWITCH_BCAST_SOURCE)).isTrue();

        mBassClientStateMachine.sendMessage(INITIATE_PA_SYNC_TRANSFER);
        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
        assertThat(mBassClientStateMachine.hasDeferredMessagesSuper(INITIATE_PA_SYNC_TRANSFER))
                .isTrue();
    }

    @Test