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

Commit a3a70445 authored by Rongxuan Liu's avatar Rongxuan Liu Committed by Gerrit Code Review
Browse files

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

parents 4686219f 77bad2da
Loading
Loading
Loading
Loading
+58 −17
Original line number Original line Diff line number Diff line
@@ -64,6 +64,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Arrays;
import java.util.Collections;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.List;
import java.util.Map;
import java.util.Map;
import java.util.Objects;
import java.util.Objects;
@@ -77,6 +78,7 @@ public class BassClientService extends ProfileService {
    private static final boolean DBG = true;
    private static final boolean DBG = true;
    private static final String TAG = BassClientService.class.getSimpleName();
    private static final String TAG = BassClientService.class.getSimpleName();
    private static final int MAX_BASS_CLIENT_STATE_MACHINES = 10;
    private static final int MAX_BASS_CLIENT_STATE_MACHINES = 10;
    private static final int MAX_ACTIVE_SYNCED_SOURCES_NUM = 4;


    private static BassClientService sService;
    private static BassClientService sService;


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


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

    /* Caching the PeriodicAdvertisementResult from Broadcast source */
    /* Caching the PeriodicAdvertisementResult from Broadcast source */
    /* This is stored at service so that each device state machine can access
    /* 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
    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;
        return base;
    }
    }


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


        log("setActiveSyncedSource, scanDelegator: " + scanDelegator + ", sourceDevice: " +
        log("removeActiveSyncedSource, scanDelegator: " + scanDelegator + ", sourceDevice: "
            sourceDevice);
                + sourceDevice);
        if (sourceDevice == null) {
        if (sourceDevice == null) {
            // remove all sources for this scanDelegator
            mActiveSourceMap.remove(scanDelegator);
            mActiveSourceMap.remove(scanDelegator);
        } else {
        } 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) {
        if (mActiveSourceMap == null) {
            Log.e(TAG, "getActiveSyncedSource: mActiveSourceMap is null");
            Log.e(TAG, "getActiveSyncedSources: mActiveSourceMap is null");
            return null;
            return null;
        }
        }


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


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


@@ -1055,6 +1087,15 @@ public class BassClientService extends ProfileService {
            return;
            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) {
        synchronized (mStateMachines) {
            sEventLogger.logd(DBG, TAG, "Select Broadcast Source");
            sEventLogger.logd(DBG, TAG, "Select Broadcast Source");


+31 −23
Original line number Original line Diff line number Diff line
@@ -67,6 +67,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Iterator;
import java.util.List;
import java.util.List;
import java.util.Map;
import java.util.Map;
@@ -159,8 +160,6 @@ public class BassClientStateMachine extends StateMachine {
    private PeriodicAdvertisingManager mPeriodicAdvManager;
    private PeriodicAdvertisingManager mPeriodicAdvManager;
    @VisibleForTesting
    @VisibleForTesting
    boolean mAutoTriggered = false;
    boolean mAutoTriggered = false;
    @VisibleForTesting
    boolean mNoStopScanOffload = false;
    private boolean mDefNoPAS = false;
    private boolean mDefNoPAS = false;
    private boolean mForceSB = false;
    private boolean mForceSB = false;
    private int mBroadcastSourceIdLength = 3;
    private int mBroadcastSourceIdLength = 3;
@@ -383,11 +382,6 @@ public class BassClientStateMachine extends StateMachine {
        mPASyncRetryCounter = 1;
        mPASyncRetryCounter = 1;
        // Cache Scan res for Retrys
        // Cache Scan res for Retrys
        mScanRes = scanRes;
        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 {
        try {
            BluetoothMethodProxy.getInstance().periodicAdvertisingManagerRegisterSync(
            BluetoothMethodProxy.getInstance().periodicAdvertisingManagerRegisterSync(
                    mPeriodicAdvManager, scanRes, 0, BassConstants.PSYNC_TIMEOUT,
                    mPeriodicAdvManager, scanRes, 0, BassConstants.PSYNC_TIMEOUT,
@@ -435,10 +429,12 @@ public class BassClientStateMachine extends StateMachine {


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


        /* Stop sync if there is some running */
        /* 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);
                removeMessages(PSYNC_ACTIVE_TIMEOUT);
                try {
                try {
                    log("calling unregisterSync");
                    log("calling unregisterSync");
@@ -447,14 +443,12 @@ public class BassClientStateMachine extends StateMachine {
                    Log.w(TAG, "unregisterSync:IllegalArgumentException");
                    Log.w(TAG, "unregisterSync:IllegalArgumentException");
                }
                }
                mService.clearNotifiedFlags();
                mService.clearNotifiedFlags();
            mService.setActiveSyncedSource(mDevice, null);
            if (!mNoStopScanOffload) {
                // trigger scan stop here
                // trigger scan stop here
                Message message = obtainMessage(STOP_SCAN_OFFLOAD);
                Message message = obtainMessage(STOP_SCAN_OFFLOAD);
                sendMessage(message);
                sendMessage(message);
            }
            }
            mService.removeActiveSyncedSource(mDevice, sourceDev);
        }
        }
        mNoStopScanOffload = false;
    }
    }


    private void resetBluetoothGatt() {
    private void resetBluetoothGatt() {
@@ -589,9 +583,11 @@ public class BassClientStateMachine extends StateMachine {
                                BassConstants.INVALID_BROADCAST_ID,
                                BassConstants.INVALID_BROADCAST_ID,
                                null,
                                null,
                                null);
                                null);
                        sendMessageDelayed(PSYNC_ACTIVE_TIMEOUT,
                        removeMessages(PSYNC_ACTIVE_TIMEOUT);
                                BassConstants.PSYNC_ACTIVE_TIMEOUT_MS);
                        // Refresh sync timeout if another source synced
                        mService.setActiveSyncedSource(mDevice, device);
                        sendMessageDelayed(
                                PSYNC_ACTIVE_TIMEOUT, BassConstants.PSYNC_ACTIVE_TIMEOUT_MS);
                        mService.addActiveSyncedSource(mDevice, device);
                        mFirstTimeBisDiscoveryMap.put(syncHandle, true);
                        mFirstTimeBisDiscoveryMap.put(syncHandle, true);
                    } else {
                    } else {
                        log("failed to sync to PA: " + mPASyncRetryCounter);
                        log("failed to sync to PA: " + mPASyncRetryCounter);
@@ -1579,6 +1575,18 @@ public class BassClientStateMachine extends StateMachine {
                    break;
                    break;
                case ADD_BCAST_SOURCE:
                case ADD_BCAST_SOURCE:
                    metaData = (BluetoothLeBroadcastMetadata) message.obj;
                    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);
                    byte[] addSourceInfo = convertMetadataToAddSourceByteArray(metaData);
                    if (addSourceInfo == null) {
                    if (addSourceInfo == null) {
                        Log.e(TAG, "add source: source Info is NULL");
                        Log.e(TAG, "add source: source Info is NULL");
+104 −1
Original line number Original line Diff line number Diff line
@@ -39,7 +39,6 @@ import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothLeAudioCodecConfigMetadata;
import android.bluetooth.BluetoothLeAudioCodecConfigMetadata;
import android.bluetooth.BluetoothLeAudioContentMetadata;
import android.bluetooth.BluetoothLeAudioContentMetadata;
import android.bluetooth.BluetoothLeBroadcast;
import android.bluetooth.BluetoothLeBroadcastAssistant;
import android.bluetooth.BluetoothLeBroadcastAssistant;
import android.bluetooth.BluetoothLeBroadcastChannel;
import android.bluetooth.BluetoothLeBroadcastChannel;
import android.bluetooth.BluetoothLeBroadcastMetadata;
import android.bluetooth.BluetoothLeBroadcastMetadata;
@@ -50,6 +49,8 @@ import android.bluetooth.BluetoothStatusCodes;
import android.bluetooth.BluetoothUuid;
import android.bluetooth.BluetoothUuid;
import android.bluetooth.IBluetoothLeBroadcastAssistantCallback;
import android.bluetooth.IBluetoothLeBroadcastAssistantCallback;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanRecord;
import android.bluetooth.le.ScanResult;
import android.content.BroadcastReceiver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Context;
import android.content.Intent;
import android.content.Intent;
@@ -1040,4 +1041,106 @@ public class BassClientServiceTest {
        assertThat(devices.contains(mCurrentDevice)).isTrue();
        assertThat(devices.contains(mCurrentDevice)).isTrue();
        assertThat(devices.contains(mCurrentDevice1)).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 Original line Diff line number Diff line
@@ -275,6 +275,9 @@ public class BassClientStateMachineTest {
        allowConnection(true);
        allowConnection(true);
        allowConnectGatt(true);
        allowConnectGatt(true);


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

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


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


    @Test
    @Test
    public void parseScanRecord_withoutBaseData_makesNoStopScanOffloadFalse() {
    public void parseScanRecord_withoutBaseData_callCancelActiveSync() {
        byte[] scanRecord = new byte[]{
        byte[] scanRecord = new byte[]{
                0x02, 0x01, 0x1a, // advertising flags
                0x02, 0x01, 0x1a, // advertising flags
                0x05, 0x02, 0x0b, 0x11, 0x0a, 0x11, // 16 bit service uuids
                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
                0x05, (byte) 0xff, (byte) 0xe0, 0x00, 0x02, 0x15, // manufacturer specific data
                0x03, 0x50, 0x01, 0x02, // an unknown data type won't cause trouble
                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);
        ScanRecord data = ScanRecord.parseFromBytes(scanRecord);
        mBassClientStateMachine.mNoStopScanOffload = true;
        mBassClientStateMachine.parseScanRecord(0, data);
        mBassClientStateMachine.parseScanRecord(0, data);
        assertThat(mBassClientStateMachine.mNoStopScanOffload).isFalse();
        // verify getActiveSyncedSource got called in CancelActiveSync
        verify(mBassClientService).getActiveSyncedSources(any());
    }
    }


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


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

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


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

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


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

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

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


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


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


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


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

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


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

        mBassClientStateMachine.sendMessage(msgToConnectedState);
        mBassClientStateMachine.sendMessage(msgToConnectedState);
        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
        TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
        verify(mBassClientService, never()).sendBroadcast(any(Intent.class), anyString(), any());
        verify(mBassClientService, never()).sendBroadcast(any(Intent.class), anyString(), any());
@@ -1414,15 +1440,17 @@ public class BassClientStateMachineTest {
        initToConnectedProcessingState();
        initToConnectedProcessingState();
        BassClientService.Callbacks callbacks = Mockito.mock(BassClientService.Callbacks.class);
        BassClientService.Callbacks callbacks = Mockito.mock(BassClientService.Callbacks.class);
        when(mBassClientService.getCallbacks()).thenReturn(callbacks);
        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)
        // Test sendPendingCallbacks(START_SCAN_OFFLOAD, ERROR_UNKNOWN)
        mBassClientStateMachine.mPendingOperation = START_SCAN_OFFLOAD;
        mBassClientStateMachine.mPendingOperation = START_SCAN_OFFLOAD;
        mBassClientStateMachine.mNoStopScanOffload = true;
        mBassClientStateMachine.mAutoTriggered = false;
        mBassClientStateMachine.mAutoTriggered = false;
        sendMessageAndVerifyTransition(
        sendMessageAndVerifyTransition(
                mBassClientStateMachine.obtainMessage(GATT_TXN_PROCESSED, GATT_FAILURE),
                mBassClientStateMachine.obtainMessage(GATT_TXN_PROCESSED, GATT_FAILURE),
                BassClientStateMachine.Connected.class);
                BassClientStateMachine.Connected.class);
        assertThat(mBassClientStateMachine.mNoStopScanOffload).isFalse();
        // verify getActiveSyncedSource got called in CancelActiveSync
        verify(mBassClientService).getActiveSyncedSources(any());


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