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

Commit 77bad2da authored by Rongxuan Liu's avatar Rongxuan Liu
Browse files

[le audio] BassClient multiple sources handling improvement

Current code doesn't handle multiple sources well. It might have race
conditions and cause unsynced source got reported, and following PAST
operation might fail because of this.

This commit improved the handling of multiple sources for BassClient,
1. We need to make sure all notified sources are managable by
   maintaining the syncs.
2. Limit the max active synced sources.
3. Clean up the source properly when source or sink lost.

Bug: 294456735
Tag: #bug
Test: atest BassClientStateMachineTest BassClientServiceTest
Test: manually test assistant with two sources
Change-Id: Ib69563a342f3f093997902fbf6252bd9626550b3
parent b9b29806
Loading
Loading
Loading
Loading
+58 −17
Original line number Diff line number Diff line
@@ -64,6 +64,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -77,6 +78,7 @@ public class BassClientService extends ProfileService {
    private static final boolean DBG = true;
    private static final String TAG = BassClientService.class.getSimpleName();
    private static final int MAX_BASS_CLIENT_STATE_MACHINES = 10;
    private static final int MAX_ACTIVE_SYNCED_SOURCES_NUM = 4;

    private static BassClientService sService;

@@ -88,13 +90,15 @@ public class BassClientService extends ProfileService {
            new ConcurrentHashMap<>();
    private final Map<BluetoothDevice, List<Integer>> mGroupManagedSources =
            new ConcurrentHashMap<>();
    private final Map<BluetoothDevice, HashSet<BluetoothDevice>> mActiveSourceMap =
            new ConcurrentHashMap<>();

    private HandlerThread mStateMachinesThread;
    private HandlerThread mCallbackHandlerThread;
    private AdapterService mAdapterService;
    private DatabaseManager mDatabaseManager;
    private BluetoothAdapter mBluetoothAdapter = null;
    private Map<BluetoothDevice, BluetoothDevice> mActiveSourceMap;

    /* Caching the PeriodicAdvertisementResult from Broadcast source */
    /* This is stored at service so that each device state machine can access
    and use it as needed. Once the periodic sync in cancelled, this data will bre
@@ -219,34 +223,61 @@ public class BassClientService extends ProfileService {
        return base;
    }

    void setActiveSyncedSource(BluetoothDevice scanDelegator, BluetoothDevice sourceDevice) {
    void removeActiveSyncedSource(BluetoothDevice scanDelegator, BluetoothDevice sourceDevice) {
        if (mActiveSourceMap == null) {
            Log.e(TAG, "setActiveSyncedSource: mActiveSourceMap is null");
            Log.e(TAG, "removeActiveSyncedSource: mActiveSourceMap is null");
            return;
        }

        log("setActiveSyncedSource, scanDelegator: " + scanDelegator + ", sourceDevice: " +
            sourceDevice);
        log("removeActiveSyncedSource, scanDelegator: " + scanDelegator + ", sourceDevice: "
                + sourceDevice);
        if (sourceDevice == null) {
            // remove all sources for this scanDelegator
            mActiveSourceMap.remove(scanDelegator);
        } else {
            mActiveSourceMap.put(scanDelegator, sourceDevice);
            HashSet<BluetoothDevice> sources = mActiveSourceMap.get(scanDelegator);
            if (sources != null) {
                sources.remove(sourceDevice);
                if (sources.isEmpty()) {
                    mActiveSourceMap.remove(scanDelegator);
                }
            }
        }
        sEventLogger.logd(DBG, TAG, "Broadcast Source Unsynced: scanDelegator= " + scanDelegator
                + ", sourceDevice= " + sourceDevice);
    }

    void addActiveSyncedSource(BluetoothDevice scanDelegator, BluetoothDevice sourceDevice) {
        if (mActiveSourceMap == null) {
            Log.e(TAG, "addActiveSyncedSource: mActiveSourceMap is null");
            return;
        }

        log("addActiveSyncedSource, scanDelegator: " + scanDelegator + ", sourceDevice: "
                + sourceDevice);
        if (sourceDevice != null) {
            mActiveSourceMap.putIfAbsent(scanDelegator, new HashSet<>());
            mActiveSourceMap.get(scanDelegator).add(sourceDevice);
        }
        sEventLogger.logd(DBG, TAG, "Broadcast Source Synced: scanDelegator= " + scanDelegator
                + ", sourceDevice= " + sourceDevice);
    }

    BluetoothDevice getActiveSyncedSource(BluetoothDevice scanDelegator) {
    HashSet<BluetoothDevice> getActiveSyncedSources(BluetoothDevice scanDelegator) {
        if (mActiveSourceMap == null) {
            Log.e(TAG, "getActiveSyncedSource: mActiveSourceMap is null");
            Log.e(TAG, "getActiveSyncedSources: mActiveSourceMap is null");
            return null;
        }

        BluetoothDevice currentSource = mActiveSourceMap.get(scanDelegator);
        log(
                "getActiveSyncedSource: scanDelegator: "
                        + scanDelegator
                        + ", returning: "
                        + currentSource);
        return currentSource;
        HashSet<BluetoothDevice> currentSources = mActiveSourceMap.get(scanDelegator);
        if (currentSources != null) {
            log("getActiveSyncedSources: scanDelegator: " + scanDelegator
                    + ", sources num: " + currentSources.size());
        } else {
            log("getActiveSyncedSources: scanDelegator: " + scanDelegator
                    + ", currentSources is null");
        }
        return currentSources;
    }

    public Callbacks getCallbacks() {
@@ -314,7 +345,6 @@ public class BassClientService extends ProfileService {
        mPeriodicAdvertisementResultMap = new HashMap<BluetoothDevice,
                PeriodicAdvertisementResult>();
        mSyncHandleToBaseDataMap = new HashMap<Integer, BaseData>();
        mActiveSourceMap = new HashMap<BluetoothDevice, BluetoothDevice>();
        mSearchScanCallback = null;
        return true;
    }
@@ -355,7 +385,6 @@ public class BassClientService extends ProfileService {
        }
        if (mActiveSourceMap != null) {
            mActiveSourceMap.clear();
            mActiveSourceMap = null;
        }
        if (mPendingGroupOp != null) {
            mPendingGroupOp.clear();
@@ -381,6 +410,9 @@ public class BassClientService extends ProfileService {
                break;
            }
        }
        if (device == null) {
            Log.w(TAG, "No device found for sync handle: " + syncHandle);
        }
        return device;
    }

@@ -1050,6 +1082,15 @@ public class BassClientService extends ProfileService {
            return;
        }

        HashSet<BluetoothDevice> activeSyncedSrc = getActiveSyncedSources(sink);
        if (activeSyncedSrc != null
                && (activeSyncedSrc.size() >= MAX_ACTIVE_SYNCED_SOURCES_NUM
                        || activeSyncedSrc.contains(result.getDevice()))) {
            log("selectSource : found num of active sources: " + activeSyncedSrc.size()
                    + ", is source synced: " + activeSyncedSrc.contains(result.getDevice()));
            return;
        }

        synchronized (mStateMachines) {
            sEventLogger.logd(DBG, TAG, "Select Broadcast Source");

+31 −23
Original line number Diff line number Diff line
@@ -67,6 +67,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -159,8 +160,6 @@ public class BassClientStateMachine extends StateMachine {
    private PeriodicAdvertisingManager mPeriodicAdvManager;
    @VisibleForTesting
    boolean mAutoTriggered = false;
    @VisibleForTesting
    boolean mNoStopScanOffload = false;
    private boolean mDefNoPAS = false;
    private boolean mForceSB = false;
    private int mBroadcastSourceIdLength = 3;
@@ -383,11 +382,6 @@ public class BassClientStateMachine extends StateMachine {
        mPASyncRetryCounter = 1;
        // Cache Scan res for Retrys
        mScanRes = scanRes;
        /*This is an override case if Previous sync is still active, cancel It, but don't stop the
         * Scan offload as we still trying to assist remote
         */
        mNoStopScanOffload = true;
        cancelActiveSync(null);
        try {
            BluetoothMethodProxy.getInstance().periodicAdvertisingManagerRegisterSync(
                    mPeriodicAdvManager, scanRes, 0, BassConstants.PSYNC_TIMEOUT,
@@ -435,10 +429,12 @@ public class BassClientStateMachine extends StateMachine {

    private void cancelActiveSync(BluetoothDevice sourceDev) {
        log("cancelActiveSync: sourceDev = " + sourceDev);
        BluetoothDevice activeSyncedSrc = mService.getActiveSyncedSource(mDevice);
        HashSet<BluetoothDevice> activeSyncedSrc = mService.getActiveSyncedSources(mDevice);

        /* Stop sync if there is some running */
        if (activeSyncedSrc != null && (sourceDev == null || activeSyncedSrc.equals(sourceDev))) {
        if (activeSyncedSrc != null && (sourceDev == null || activeSyncedSrc.contains(sourceDev))) {
            // clean up if sourceDev is null or this is the only source
            if (sourceDev == null || (activeSyncedSrc.size() == 0x1)) {
                removeMessages(PSYNC_ACTIVE_TIMEOUT);
                try {
                    log("calling unregisterSync");
@@ -447,14 +443,12 @@ public class BassClientStateMachine extends StateMachine {
                    Log.w(TAG, "unregisterSync:IllegalArgumentException");
                }
                mService.clearNotifiedFlags();
            mService.setActiveSyncedSource(mDevice, null);
            if (!mNoStopScanOffload) {
                // trigger scan stop here
                Message message = obtainMessage(STOP_SCAN_OFFLOAD);
                sendMessage(message);
            }
            mService.removeActiveSyncedSource(mDevice, sourceDev);
        }
        mNoStopScanOffload = false;
    }

    private void resetBluetoothGatt() {
@@ -589,9 +583,11 @@ public class BassClientStateMachine extends StateMachine {
                                BassConstants.INVALID_BROADCAST_ID,
                                null,
                                null);
                        sendMessageDelayed(PSYNC_ACTIVE_TIMEOUT,
                                BassConstants.PSYNC_ACTIVE_TIMEOUT_MS);
                        mService.setActiveSyncedSource(mDevice, device);
                        removeMessages(PSYNC_ACTIVE_TIMEOUT);
                        // Refresh sync timeout if another source synced
                        sendMessageDelayed(
                                PSYNC_ACTIVE_TIMEOUT, BassConstants.PSYNC_ACTIVE_TIMEOUT_MS);
                        mService.addActiveSyncedSource(mDevice, device);
                        mFirstTimeBisDiscoveryMap.put(syncHandle, true);
                    } else {
                        log("failed to sync to PA: " + mPASyncRetryCounter);
@@ -1579,6 +1575,18 @@ public class BassClientStateMachine extends StateMachine {
                    break;
                case ADD_BCAST_SOURCE:
                    metaData = (BluetoothLeBroadcastMetadata) message.obj;

                    HashSet<BluetoothDevice> activeSyncedSrc =
                            mService.getActiveSyncedSources(mDevice);
                    if (!mService.isLocalBroadcast(metaData)
                            && (activeSyncedSrc == null
                                    || !activeSyncedSrc.contains(metaData.getSourceDevice()))) {
                        log("Adding non-active synced source: " + metaData.getSourceDevice());
                        mService.getCallbacks().notifySourceAddFailed(mDevice, metaData,
                                BluetoothStatusCodes.ERROR_UNKNOWN);
                        break;
                    }

                    byte[] addSourceInfo = convertMetadataToAddSourceByteArray(metaData);
                    if (addSourceInfo == null) {
                        Log.e(TAG, "add source: source Info is NULL");
+104 −1
Original line number Diff line number Diff line
@@ -39,7 +39,6 @@ import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothLeAudioCodecConfigMetadata;
import android.bluetooth.BluetoothLeAudioContentMetadata;
import android.bluetooth.BluetoothLeBroadcast;
import android.bluetooth.BluetoothLeBroadcastAssistant;
import android.bluetooth.BluetoothLeBroadcastChannel;
import android.bluetooth.BluetoothLeBroadcastMetadata;
@@ -50,6 +49,8 @@ import android.bluetooth.BluetoothStatusCodes;
import android.bluetooth.BluetoothUuid;
import android.bluetooth.IBluetoothLeBroadcastAssistantCallback;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanRecord;
import android.bluetooth.le.ScanResult;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -1040,4 +1041,106 @@ public class BassClientServiceTest {
        assertThat(devices.contains(mCurrentDevice)).isTrue();
        assertThat(devices.contains(mCurrentDevice1)).isTrue();
    }

    @Test
    public void testActiveSyncedSource_AddRemoveGet() {
        prepareConnectedDeviceGroup();
        assertThat(mStateMachines.size()).isEqualTo(2);

        assertThat(mBassClientService.getActiveSyncedSources(mCurrentDevice)).isEqualTo(null);
        assertThat(mBassClientService.getActiveSyncedSources(mCurrentDevice1)).isEqualTo(null);

        BluetoothDevice testDevice =
                mBluetoothAdapter.getRemoteLeDevice(
                        TEST_MAC_ADDRESS, BluetoothDevice.ADDRESS_TYPE_RANDOM);
        // Verify add active synced source
        mBassClientService.addActiveSyncedSource(mCurrentDevice, testDevice);
        mBassClientService.addActiveSyncedSource(mCurrentDevice1, testDevice);
        // Verify duplicated source won't be added
        mBassClientService.addActiveSyncedSource(mCurrentDevice, testDevice);
        mBassClientService.addActiveSyncedSource(mCurrentDevice1, testDevice);
        assertThat(mBassClientService.getActiveSyncedSources(mCurrentDevice)).isNotEqualTo(null);
        assertThat(mBassClientService.getActiveSyncedSources(mCurrentDevice1)).isNotEqualTo(null);
        assertThat(mBassClientService.getActiveSyncedSources(mCurrentDevice).size()).isEqualTo(1);
        assertThat(mBassClientService.getActiveSyncedSources(mCurrentDevice1).size()).isEqualTo(1);

        // Verify remove active synced source
        mBassClientService.removeActiveSyncedSource(mCurrentDevice, testDevice);
        mBassClientService.removeActiveSyncedSource(mCurrentDevice1, testDevice);
        assertThat(mBassClientService.getActiveSyncedSources(mCurrentDevice)).isEqualTo(null);
        assertThat(mBassClientService.getActiveSyncedSources(mCurrentDevice1)).isEqualTo(null);
    }

    @Test
    public void testSelectSource_invalidActiveSource() {
        byte[] scanRecord = new byte[]{
                0x02, 0x01, 0x1a, // advertising flags
                0x05, 0x02, 0x52, 0x18, 0x0a, 0x11, // 16 bit service uuids
                0x04, 0x09, 0x50, 0x65, 0x64, // name
                0x02, 0x0A, (byte) 0xec, // tx power level
                0x05, 0x30, 0x54, 0x65, 0x73, 0x74, // broadcast name: Test
                0x06, 0x16, 0x52, 0x18, 0x50, 0x64, 0x65, // service data
                0x08, 0x16, 0x56, 0x18, 0x07, 0x03, 0x06, 0x07, 0x08,
                // service data - public broadcast,
                // feature - 0x7, metadata len - 0x3, metadata - 0x6, 0x7, 0x8
                0x05, (byte) 0xff, (byte) 0xe0, 0x00, 0x02, 0x15, // manufacturer specific data
                0x03, 0x50, 0x01, 0x02, // an unknown data type won't cause trouble
        };
        ScanRecord record = ScanRecord.parseFromBytes(scanRecord);

        prepareConnectedDeviceGroup();
        assertThat(mStateMachines.size()).isEqualTo(2);

        BluetoothDevice testDevice = mBluetoothAdapter.getRemoteLeDevice(
                TEST_MAC_ADDRESS, BluetoothDevice.ADDRESS_TYPE_RANDOM);
        BluetoothDevice testDevice1 = mBluetoothAdapter.getRemoteLeDevice(
                "00:11:22:33:44:66", BluetoothDevice.ADDRESS_TYPE_RANDOM);
        BluetoothDevice testDevice2 = mBluetoothAdapter.getRemoteLeDevice(
                "00:11:22:33:44:77", BluetoothDevice.ADDRESS_TYPE_RANDOM);
        BluetoothDevice testDevice3 = mBluetoothAdapter.getRemoteLeDevice(
                "00:11:22:33:44:88", BluetoothDevice.ADDRESS_TYPE_RANDOM);
        // Verify add active synced source
        mBassClientService.addActiveSyncedSource(mCurrentDevice, testDevice);
        mBassClientService.addActiveSyncedSource(mCurrentDevice1, testDevice);
        assertThat(mBassClientService.getActiveSyncedSources(mCurrentDevice)).isNotEqualTo(null);
        assertThat(mBassClientService.getActiveSyncedSources(mCurrentDevice1)).isNotEqualTo(null);
        assertThat(mBassClientService.getActiveSyncedSources(mCurrentDevice).size()).isEqualTo(1);
        assertThat(mBassClientService.getActiveSyncedSources(mCurrentDevice1).size()).isEqualTo(1);

        // Verify selectSource with synced device should not proceed
        ScanResult scanResult = new ScanResult(testDevice, 0, 0, 0, 0, 0, 0, 0, record, 0);
        mBassClientService.selectSource(mCurrentDevice, scanResult, false);
        mBassClientService.selectSource(mCurrentDevice1, scanResult, false);
        for (BassClientStateMachine sm : mStateMachines.values()) {
            verify(sm, never()).sendMessage(any());
        }

        // Verify selectSource with max synced device should not proceed
        mBassClientService.addActiveSyncedSource(mCurrentDevice, testDevice1);
        mBassClientService.addActiveSyncedSource(mCurrentDevice1, testDevice1);
        mBassClientService.addActiveSyncedSource(mCurrentDevice, testDevice2);
        mBassClientService.addActiveSyncedSource(mCurrentDevice1, testDevice2);
        mBassClientService.addActiveSyncedSource(mCurrentDevice, testDevice3);
        mBassClientService.addActiveSyncedSource(mCurrentDevice1, testDevice3);

        assertThat(mBassClientService.getActiveSyncedSources(mCurrentDevice)).isNotEqualTo(null);
        assertThat(mBassClientService.getActiveSyncedSources(mCurrentDevice1)).isNotEqualTo(null);
        assertThat(mBassClientService.getActiveSyncedSources(mCurrentDevice).size()).isEqualTo(4);
        assertThat(mBassClientService.getActiveSyncedSources(mCurrentDevice1).size()).isEqualTo(4);

        BluetoothDevice testDevice4 = mBluetoothAdapter.getRemoteLeDevice(
                "00:01:02:03:04:05", BluetoothDevice.ADDRESS_TYPE_RANDOM);
        ScanResult scanResult1 = new ScanResult(testDevice4, 0, 0, 0, 0, 0, 0, 0, record, 0);
        mBassClientService.selectSource(mCurrentDevice, scanResult1, false);
        mBassClientService.selectSource(mCurrentDevice1, scanResult1, false);
        for (BassClientStateMachine sm : mStateMachines.values()) {
            verify(sm, never()).sendMessage(any());
        }

        // Verify remove all active synced source
        mBassClientService.removeActiveSyncedSource(mCurrentDevice, null);
        mBassClientService.removeActiveSyncedSource(mCurrentDevice1, null);
        assertThat(mBassClientService.getActiveSyncedSources(mCurrentDevice)).isEqualTo(null);
        assertThat(mBassClientService.getActiveSyncedSources(mCurrentDevice1)).isEqualTo(null);
    }
}
+35 −7
Original line number Diff line number Diff line
@@ -275,6 +275,9 @@ public class BassClientStateMachineTest {
        allowConnection(true);
        allowConnectGatt(true);

        // need this to ensure expected mock behavior for getActiveSyncedSource
        when(mBassClientService.getActiveSyncedSources(any())).thenReturn(null);

        assertThat(mBassClientStateMachine.getCurrentState())
                .isInstanceOf(BassClientStateMachine.Disconnected.class);

@@ -438,7 +441,7 @@ public class BassClientStateMachineTest {
    }

    @Test
    public void parseScanRecord_withoutBaseData_makesNoStopScanOffloadFalse() {
    public void parseScanRecord_withoutBaseData_callCancelActiveSync() {
        byte[] scanRecord = new byte[]{
                0x02, 0x01, 0x1a, // advertising flags
                0x05, 0x02, 0x0b, 0x11, 0x0a, 0x11, // 16 bit service uuids
@@ -448,10 +451,13 @@ public class BassClientStateMachineTest {
                0x05, (byte) 0xff, (byte) 0xe0, 0x00, 0x02, 0x15, // manufacturer specific data
                0x03, 0x50, 0x01, 0x02, // an unknown data type won't cause trouble
        };
        // need this to ensure expected mock behavior for getActiveSyncedSource
        when(mBassClientService.getActiveSyncedSources(any())).thenReturn(null);

        ScanRecord data = ScanRecord.parseFromBytes(scanRecord);
        mBassClientStateMachine.mNoStopScanOffload = true;
        mBassClientStateMachine.parseScanRecord(0, data);
        assertThat(mBassClientStateMachine.mNoStopScanOffload).isFalse();
        // verify getActiveSyncedSource got called in CancelActiveSync
        verify(mBassClientService).getActiveSyncedSources(any());
    }

    @Test
@@ -867,6 +873,9 @@ public class BassClientStateMachineTest {
    public void sendOtherMessages_inDisconnectedState_doesNotChangeState() {
        initToDisconnectedState();

        // need this to ensure expected mock behavior for getActiveSyncedSource
        when(mBassClientService.getActiveSyncedSources(any())).thenReturn(null);

        mBassClientStateMachine.sendMessage(PSYNC_ACTIVE_TIMEOUT);
        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
        verify(mBassClientService, never()).sendBroadcast(any(Intent.class), anyString(), any());
@@ -977,6 +986,9 @@ public class BassClientStateMachineTest {
        initToConnectedState();

        mBassClientStateMachine.mBluetoothGatt = null;
        // need this to ensure expected mock behavior for getActiveSyncedSource
        when(mBassClientService.getActiveSyncedSources(any())).thenReturn(null);

        mBassClientStateMachine.sendMessage(DISCONNECT);
        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
        verify(mBassClientService, never()).sendBroadcast(any(Intent.class), anyString(), any());
@@ -997,6 +1009,10 @@ public class BassClientStateMachineTest {

        Message connectedMsg = mBassClientStateMachine.obtainMessage(CONNECTION_STATE_CHANGED);
        connectedMsg.obj = BluetoothProfile.STATE_CONNECTED;

        // need this to ensure expected mock behavior for getActiveSyncedSource
        when(mBassClientService.getActiveSyncedSources(any())).thenReturn(null);

        mBassClientStateMachine.sendMessage(connectedMsg);
        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
        verify(mBassClientService, never()).sendBroadcast(any(Intent.class), anyString(), any());
@@ -1076,11 +1092,13 @@ public class BassClientStateMachineTest {
    @Test
    public void sendPsyncActiveMessage_inConnectedState() {
        initToConnectedState();
        // need this to ensure expected mock behavior for getActiveSyncedSource
        when(mBassClientService.getActiveSyncedSources(any())).thenReturn(null);

        mBassClientStateMachine.mNoStopScanOffload = true;
        mBassClientStateMachine.sendMessage(PSYNC_ACTIVE_TIMEOUT);
        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
        assertThat(mBassClientStateMachine.mNoStopScanOffload).isFalse();
        // verify getActiveSyncedSource got called in CancelActiveSync
        verify(mBassClientService).getActiveSyncedSources(any());
    }

    @Test
@@ -1130,6 +1148,8 @@ public class BassClientStateMachineTest {
        when(mBassClientService.getCallbacks()).thenReturn(callbacks);

        BluetoothLeBroadcastMetadata metadata = createBroadcastMetadata();
        // verify local broadcast doesn't require active synced source
        when(mBassClientService.isLocalBroadcast(any())).thenReturn(true);
        mBassClientStateMachine.sendMessage(ADD_BCAST_SOURCE, metadata);
        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());

@@ -1369,6 +1389,9 @@ public class BassClientStateMachineTest {
        // Mock instance of btGatt was created in initToConnectedProcessingState().
        BassClientStateMachine.BluetoothGattTestableWrapper btGatt =
                mBassClientStateMachine.mBluetoothGatt;
        // need this to ensure expected mock behavior for getActiveSyncedSource
        when(mBassClientService.getActiveSyncedSources(any())).thenReturn(null);

        mBassClientStateMachine.mBluetoothGatt = null;
        mBassClientStateMachine.sendMessage(DISCONNECT);
        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
@@ -1390,6 +1413,9 @@ public class BassClientStateMachineTest {
                mBassClientStateMachine.obtainMessage(CONNECTION_STATE_CHANGED);
        msgToConnectedState.obj = BluetoothProfile.STATE_CONNECTED;

        // need this to ensure expected mock behavior for getActiveSyncedSource
        when(mBassClientService.getActiveSyncedSources(any())).thenReturn(null);

        mBassClientStateMachine.sendMessage(msgToConnectedState);
        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
        verify(mBassClientService, never()).sendBroadcast(any(Intent.class), anyString(), any());
@@ -1414,15 +1440,17 @@ public class BassClientStateMachineTest {
        initToConnectedProcessingState();
        BassClientService.Callbacks callbacks = Mockito.mock(BassClientService.Callbacks.class);
        when(mBassClientService.getCallbacks()).thenReturn(callbacks);
        // need this to ensure expected mock behavior for getActiveSyncedSource
        when(mBassClientService.getActiveSyncedSources(any())).thenReturn(null);

        // Test sendPendingCallbacks(START_SCAN_OFFLOAD, ERROR_UNKNOWN)
        mBassClientStateMachine.mPendingOperation = START_SCAN_OFFLOAD;
        mBassClientStateMachine.mNoStopScanOffload = true;
        mBassClientStateMachine.mAutoTriggered = false;
        sendMessageAndVerifyTransition(
                mBassClientStateMachine.obtainMessage(GATT_TXN_PROCESSED, GATT_FAILURE),
                BassClientStateMachine.Connected.class);
        assertThat(mBassClientStateMachine.mNoStopScanOffload).isFalse();
        // verify getActiveSyncedSource got called in CancelActiveSync
        verify(mBassClientService).getActiveSyncedSources(any());

        // Test sendPendingCallbacks(START_SCAN_OFFLOAD, ERROR_UNKNOWN)
        moveConnectedStateToConnectedProcessingState();