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 Original line Diff line number Diff line
@@ -169,6 +169,8 @@ public class BassClientService extends ProfileService {
    private final Map<BluetoothDevice, BluetoothLeBroadcastMetadata> mBroadcastMetadataMap =
    private final Map<BluetoothDevice, BluetoothLeBroadcastMetadata> mBroadcastMetadataMap =
            new ConcurrentHashMap<>();
            new ConcurrentHashMap<>();
    private final HashSet<BluetoothDevice> mPausedBroadcastSinks = new HashSet<>();
    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 Map<Integer, PauseType> mPausedBroadcastIds = new HashMap<>();
    private final Deque<AddSourceData> mPendingAddSources = new ArrayDeque<>();
    private final Deque<AddSourceData> mPendingAddSources = new ArrayDeque<>();
    private final Map<Integer, HashSet<BluetoothDevice>> mLocalBroadcastReceivers =
    private final Map<Integer, HashSet<BluetoothDevice>> mLocalBroadcastReceivers =
@@ -276,13 +278,6 @@ public class BassClientService extends ProfileService {
                                            {
                                            {
                                                log("MESSAGE_BIG_MONITOR_TIMEOUT");
                                                log("MESSAGE_BIG_MONITOR_TIMEOUT");
                                                stopSourceReceivers(broadcastId);
                                                stopSourceReceivers(broadcastId);
                                                synchronized (mSearchScanCallbackLock) {
                                                    // when searching is stopped then clear all sync
                                                    // data
                                                    if (mSearchScanCallback == null) {
                                                        clearAllSyncData();
                                                    }
                                                }
                                                break;
                                                break;
                                            }
                                            }
                                        default:
                                        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(
    private void localNotifyReceiveStateChanged(
            BluetoothDevice sink, BluetoothLeBroadcastReceiveState receiveState) {
            BluetoothDevice sink, BluetoothLeBroadcastReceiveState receiveState) {
        int broadcastId = receiveState.getBroadcastId();
        int broadcastId = receiveState.getBroadcastId();
@@ -1114,24 +1127,14 @@ public class BassClientService extends ProfileService {
                && !isLocalBroadcast(receiveState)
                && !isLocalBroadcast(receiveState)
                && !isEmptyBluetoothDevice(receiveState.getSourceDevice())
                && !isEmptyBluetoothDevice(receiveState.getSourceDevice())
                && !isHostPauseType(broadcastId)) {
                && !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);
                    stopBigMonitoring(broadcastId, false);
                }
            } else if (!mPausedBroadcastIds.containsKey(broadcastId)) {
            } else if (!mPausedBroadcastIds.containsKey(broadcastId)) {
                if (mCachedBroadcasts.containsKey(broadcastId)) {
                if (mCachedBroadcasts.containsKey(broadcastId)) {
                    addSelectSourceRequest(broadcastId, true);
                    addSelectSourceRequest(broadcastId, true);
@@ -1144,6 +1147,9 @@ public class BassClientService extends ProfileService {
                }
                }
            }
            }
        } else if (isEmptyBluetoothDevice(receiveState.getSourceDevice())) {
        } else if (isEmptyBluetoothDevice(receiveState.getSourceDevice())) {
            synchronized (mSinksWaitingForPast) {
                mSinksWaitingForPast.remove(sink);
            }
            checkAndStopBigMonitoring();
            checkAndStopBigMonitoring();
        }
        }


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

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


            int bondState = mAdapterService.getBondState(device);
            int bondState = mAdapterService.getBondState(device);
            if (bondState == BluetoothDevice.BOND_NONE) {
            if (bondState == BluetoothDevice.BOND_NONE) {
@@ -1593,6 +1600,20 @@ public class BassClientService extends ProfileService {
                removeStateMachine(device);
                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
            /* Restore allowed context mask for unicast in case if last connected broadcast
             * delegator device which has external source disconnectes.
             * delegator device which has external source disconnectes.
             */
             */
@@ -2167,6 +2188,41 @@ public class BassClientService extends ProfileService {
                }
                }
                mBisDiscoveryCounterMap.put(syncHandle, MAX_BIS_DISCOVERY_TRIES_NUM);
                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) {
                synchronized (mPendingSourcesToAdd) {
                    Iterator<AddSourceData> iterator = mPendingSourcesToAdd.iterator();
                    Iterator<AddSourceData> iterator = mPendingSourcesToAdd.iterator();
                    while (iterator.hasNext()) {
                    while (iterator.hasNext()) {
@@ -3698,14 +3754,23 @@ public class BassClientService extends ProfileService {
                                .filter(e -> e.getBroadcastId() == metadata.getBroadcastId())
                                .filter(e -> e.getBroadcastId() == metadata.getBroadcastId())
                                .findAny();
                                .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();
                List<Integer> activeSyncedSrc = getActiveSyncedSources();


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


    private Set<Integer> getExternalBroadcastsActiveOnSinks() {
    private boolean isReceiverActive(BluetoothLeBroadcastReceiveState receiveState) {
        HashSet<Integer> syncedBroadcasts = new HashSet<>();
        if (receiveState.getPaSyncState()
        for (BluetoothDevice device : getConnectedDevices()) {
                == BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCHRONIZED) {
            for (BluetoothLeBroadcastReceiveState receiveState : getAllSources(device)) {
            return true;
                if (!isLocalBroadcast(receiveState)) {
        } else {
            for (int i = 0; i < receiveState.getNumSubgroups(); i++) {
            for (int i = 0; i < receiveState.getNumSubgroups(); i++) {
                Long syncState = receiveState.getBisSyncState().get(i);
                Long syncState = receiveState.getBisSyncState().get(i);
                /* Synced to BIS */
                /* Synced to BIS */
                if (syncState != BassConstants.BIS_SYNC_NOT_SYNC_TO_BIS
                if (syncState != BassConstants.BIS_SYNC_NOT_SYNC_TO_BIS
                        && syncState != BassConstants.BIS_SYNC_FAILED_SYNC_TO_BIG) {
                        && 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());
                    syncedBroadcasts.add(receiveState.getBroadcastId());
                    log("getExternalBroadcastsActiveOnSinks: " + receiveState);
                    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 */
    /** Get sink devices synced to the broadcasts */
+78 −59
Original line number Original line Diff line number Diff line
@@ -84,8 +84,7 @@ import java.util.Scanner;
import java.util.UUID;
import java.util.UUID;
import java.util.stream.IntStream;
import java.util.stream.IntStream;


@VisibleForTesting
class BassClientStateMachine extends StateMachine {
public class BassClientStateMachine extends StateMachine {
    private static final String TAG = "BassClientStateMachine";
    private static final String TAG = "BassClientStateMachine";
    @VisibleForTesting static final byte[] REMOTE_SCAN_STOP = {00};
    @VisibleForTesting static final byte[] REMOTE_SCAN_STOP = {00};
    @VisibleForTesting static final byte[] REMOTE_SCAN_START = {01};
    @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 REACHED_MAX_SOURCE_LIMIT = 16;
    static final int SWITCH_BCAST_SOURCE = 17;
    static final int SWITCH_BCAST_SOURCE = 17;
    static final int CANCEL_PENDING_SOURCE_OPERATION = 18;
    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
    // NOTE: the value is not "final" - it is modified in the unit tests
    @VisibleForTesting private int mConnectTimeoutMs;
    @VisibleForTesting private int mConnectTimeoutMs;
@@ -816,43 +816,11 @@ public class BassClientStateMachine extends StateMachine {
        int state = recvState.getPaSyncState();
        int state = recvState.getPaSyncState();
        if (state == BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCINFO_REQUEST) {
        if (state == BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCINFO_REQUEST) {
            log("Initiate PAST procedure");
            log("Initiate PAST procedure");
            PeriodicAdvertisementResult result =
            int sourceId = recvState.getSourceId();
                    mService.getPeriodicAdvertisementResult(
            BluetoothLeBroadcastMetadata currentMetadata = getCurrentBroadcastMetadata(sourceId);
                            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());
            if (mService.isLocalBroadcast(currentMetadata)) {
            if (mService.isLocalBroadcast(currentMetadata)) {
                int advHandle = currentMetadata.getSourceAdvertisingSid();
                int advHandle = currentMetadata.getSourceAdvertisingSid();
                    serviceData = 0x000000FF & recvState.getSourceId();
                serviceData = 0x000000FF & sourceId;
                serviceData = serviceData << 8;
                serviceData = serviceData << 8;
                // Address we set in the Source Address can differ from the address in the air
                // Address we set in the Source Address can differ from the address in the air
                serviceData =
                serviceData =
@@ -873,9 +841,52 @@ public class BassClientStateMachine extends StateMachine {
                                advHandle,
                                advHandle,
                                mLocalPeriodicAdvCallback);
                                mLocalPeriodicAdvCallback);
            } else {
            } 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;
                    int broadcastId = message.arg1;
                    cancelPendingSourceOperation(broadcastId);
                    cancelPendingSourceOperation(broadcastId);
                    break;
                    break;
                case INITIATE_PA_SYNC_TRANSFER:
                    int syncHandle = message.arg1;
                    int sourceIdForPast = message.arg2;
                    initiatePaSyncTransfer(syncHandle, sourceIdForPast);
                    break;
                default:
                default:
                    log("CONNECTED: not handled message:" + message.what);
                    log("CONNECTED: not handled message:" + message.what);
                    return NOT_HANDLED;
                    return NOT_HANDLED;
@@ -2539,6 +2555,7 @@ public class BassClientStateMachine extends StateMachine {
                case REACHED_MAX_SOURCE_LIMIT:
                case REACHED_MAX_SOURCE_LIMIT:
                case SWITCH_BCAST_SOURCE:
                case SWITCH_BCAST_SOURCE:
                case PSYNC_ACTIVE_TIMEOUT:
                case PSYNC_ACTIVE_TIMEOUT:
                case INITIATE_PA_SYNC_TRANSFER:
                    log(
                    log(
                            "defer the message: "
                            "defer the message: "
                                    + messageWhatToString(message.what)
                                    + messageWhatToString(message.what)
@@ -2646,6 +2663,8 @@ public class BassClientStateMachine extends StateMachine {
                return "CONNECT_TIMEOUT";
                return "CONNECT_TIMEOUT";
            case CANCEL_PENDING_SOURCE_OPERATION:
            case CANCEL_PENDING_SOURCE_OPERATION:
                return "CANCEL_PENDING_SOURCE_OPERATION";
                return "CANCEL_PENDING_SOURCE_OPERATION";
            case INITIATE_PA_SYNC_TRANSFER:
                return "INITIATE_PA_SYNC_TRANSFER";
            default:
            default:
                break;
                break;
        }
        }
+177 −4

File changed.

Preview size limit exceeded, changes collapsed.

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


        Mockito.clearInvocations(callbacks);
        Mockito.clearInvocations(callbacks);
        cb.onCharacteristicRead(null, characteristic, GATT_SUCCESS);
        cb.onCharacteristicRead(null, characteristic, GATT_SUCCESS);
        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
        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)
        verify(callbacks)
                .notifyReceiveStateChanged(any(), eq(sourceId), receiveStateCaptor.capture());
                .notifyReceiveStateChanged(any(), eq(sourceId), receiveStateCaptor.capture());
        Assert.assertEquals(receiveStateCaptor.getValue().getSourceDevice(), mSourceTestDevice);
        Assert.assertEquals(receiveStateCaptor.getValue().getSourceDevice(), mSourceTestDevice);
@@ -1706,6 +1718,26 @@ public class BassClientStateMachineTest {
        assertThat(mBassClientStateMachine.mPendingSourceId).isEqualTo(sid);
        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
    @Test
    public void sendConnectMessage_inConnectedProcessingState_doesNotChangeState() {
    public void sendConnectMessage_inConnectedProcessingState_doesNotChangeState() {
        initToConnectedProcessingState();
        initToConnectedProcessingState();
@@ -1906,6 +1938,11 @@ public class BassClientStateMachineTest {
        mBassClientStateMachine.sendMessage(SWITCH_BCAST_SOURCE);
        mBassClientStateMachine.sendMessage(SWITCH_BCAST_SOURCE);
        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
        assertThat(mBassClientStateMachine.hasDeferredMessagesSuper(SWITCH_BCAST_SOURCE)).isTrue();
        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
    @Test