Loading android/app/src/com/android/bluetooth/bass_client/BassClientService.java +58 −17 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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 Loading Loading @@ -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() { Loading Loading @@ -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; } Loading Loading @@ -355,7 +385,6 @@ public class BassClientService extends ProfileService { } if (mActiveSourceMap != null) { mActiveSourceMap.clear(); mActiveSourceMap = null; } if (mPendingGroupOp != null) { mPendingGroupOp.clear(); Loading @@ -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; } Loading Loading @@ -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"); Loading android/app/src/com/android/bluetooth/bass_client/BassClientStateMachine.java +31 −23 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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, Loading Loading @@ -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"); Loading @@ -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() { Loading Loading @@ -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); Loading Loading @@ -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"); Loading android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientServiceTest.java +104 −1 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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); } } android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientStateMachineTest.java +35 −7 Original line number Diff line number Diff line Loading @@ -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); Loading Loading @@ -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 Loading @@ -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 Loading Loading @@ -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()); Loading Loading @@ -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()); Loading @@ -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()); Loading Loading @@ -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 Loading Loading @@ -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()); Loading Loading @@ -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()); Loading @@ -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()); Loading @@ -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(); Loading Loading
android/app/src/com/android/bluetooth/bass_client/BassClientService.java +58 −17 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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 Loading Loading @@ -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() { Loading Loading @@ -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; } Loading Loading @@ -355,7 +385,6 @@ public class BassClientService extends ProfileService { } if (mActiveSourceMap != null) { mActiveSourceMap.clear(); mActiveSourceMap = null; } if (mPendingGroupOp != null) { mPendingGroupOp.clear(); Loading @@ -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; } Loading Loading @@ -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"); Loading
android/app/src/com/android/bluetooth/bass_client/BassClientStateMachine.java +31 −23 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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, Loading Loading @@ -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"); Loading @@ -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() { Loading Loading @@ -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); Loading Loading @@ -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"); Loading
android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientServiceTest.java +104 −1 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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); } }
android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientStateMachineTest.java +35 −7 Original line number Diff line number Diff line Loading @@ -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); Loading Loading @@ -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 Loading @@ -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 Loading Loading @@ -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()); Loading Loading @@ -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()); Loading @@ -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()); Loading Loading @@ -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 Loading Loading @@ -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()); Loading Loading @@ -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()); Loading @@ -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()); Loading @@ -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(); Loading