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

Commit 4c1664bc authored by Rongxuan Liu's avatar Rongxuan Liu Committed by Automerger Merge Worker
Browse files

Merge "[le audio] BassClient multiple sources handling improvement" into main am: a3a70445

parents f5b5336c a3a70445
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;
    }

@@ -1055,6 +1087,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();