diff --git a/android/app/src/com/android/bluetooth/bass_client/BassClientService.java b/android/app/src/com/android/bluetooth/bass_client/BassClientService.java index 38cb01c442b91c6ba8cfe727c9876a0958e6897c..a02f64e4277d5c3ce21547d51520f1f7b4dd74c3 100644 --- a/android/app/src/com/android/bluetooth/bass_client/BassClientService.java +++ b/android/app/src/com/android/bluetooth/bass_client/BassClientService.java @@ -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); } - BluetoothDevice getActiveSyncedSource(BluetoothDevice scanDelegator) { + void addActiveSyncedSource(BluetoothDevice scanDelegator, BluetoothDevice sourceDevice) { if (mActiveSourceMap == null) { - Log.e(TAG, "getActiveSyncedSource: mActiveSourceMap is 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); + } + + HashSet<BluetoothDevice> getActiveSyncedSources(BluetoothDevice scanDelegator) { + if (mActiveSourceMap == null) { + Log.e(TAG, "getActiveSyncedSources: mActiveSourceMap is null"); return null; } - BluetoothDevice currentSource = mActiveSourceMap.get(scanDelegator); - log( - "getActiveSyncedSource: scanDelegator: " - + scanDelegator - + ", returning: " - + currentSource); - return currentSource; + HashSet<BluetoothDevice> currentSources = mActiveSourceMap.get(scanDelegator); + if (currentSources != null) { + log("getActiveSyncedSources: scanDelegator: " + scanDelegator + + ", sources num: " + currentSources.size()); + } else { + log("getActiveSyncedSources: scanDelegator: " + scanDelegator + + ", currentSources is null"); + } + return currentSources; } public Callbacks getCallbacks() { @@ -314,7 +345,6 @@ public class BassClientService extends ProfileService { mPeriodicAdvertisementResultMap = new HashMap<BluetoothDevice, PeriodicAdvertisementResult>(); mSyncHandleToBaseDataMap = new HashMap<Integer, BaseData>(); - mActiveSourceMap = new HashMap<BluetoothDevice, BluetoothDevice>(); mSearchScanCallback = null; return true; } @@ -355,7 +385,6 @@ public class BassClientService extends ProfileService { } if (mActiveSourceMap != null) { mActiveSourceMap.clear(); - mActiveSourceMap = null; } if (mPendingGroupOp != null) { mPendingGroupOp.clear(); @@ -381,6 +410,9 @@ public class BassClientService extends ProfileService { break; } } + if (device == null) { + Log.w(TAG, "No device found for sync handle: " + syncHandle); + } return device; } @@ -1050,6 +1082,15 @@ public class BassClientService extends ProfileService { return; } + HashSet<BluetoothDevice> activeSyncedSrc = getActiveSyncedSources(sink); + if (activeSyncedSrc != null + && (activeSyncedSrc.size() >= MAX_ACTIVE_SYNCED_SOURCES_NUM + || activeSyncedSrc.contains(result.getDevice()))) { + log("selectSource : found num of active sources: " + activeSyncedSrc.size() + + ", is source synced: " + activeSyncedSrc.contains(result.getDevice())); + return; + } + synchronized (mStateMachines) { sEventLogger.logd(DBG, TAG, "Select Broadcast Source"); diff --git a/android/app/src/com/android/bluetooth/bass_client/BassClientStateMachine.java b/android/app/src/com/android/bluetooth/bass_client/BassClientStateMachine.java index d625d92f8ca7a60ee8d6cdd755313df381303c23..68eda174b62063ef0b2cc8e6d0bc8796ec598a0b 100644 --- a/android/app/src/com/android/bluetooth/bass_client/BassClientStateMachine.java +++ b/android/app/src/com/android/bluetooth/bass_client/BassClientStateMachine.java @@ -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,26 +429,26 @@ 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))) { - removeMessages(PSYNC_ACTIVE_TIMEOUT); - try { - log("calling unregisterSync"); - mPeriodicAdvManager.unregisterSync(mPeriodicAdvCallback); - } catch (IllegalArgumentException ex) { - Log.w(TAG, "unregisterSync:IllegalArgumentException"); - } - mService.clearNotifiedFlags(); - mService.setActiveSyncedSource(mDevice, null); - if (!mNoStopScanOffload) { + 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"); + mPeriodicAdvManager.unregisterSync(mPeriodicAdvCallback); + } catch (IllegalArgumentException ex) { + Log.w(TAG, "unregisterSync:IllegalArgumentException"); + } + mService.clearNotifiedFlags(); // 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"); diff --git a/android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientServiceTest.java index 3164fecc0b4159fb8973df92e8c12f38fadd921e..71455a0f3cdfecf3410d37f0a812e84bbb2f1c21 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientServiceTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientServiceTest.java @@ -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); + } } diff --git a/android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientStateMachineTest.java b/android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientStateMachineTest.java index 7e0aca04fbe255e44cef93b9687fbeac6910f660..fe4ad4eda396d076e6f911927a89af5d90db6df8 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientStateMachineTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientStateMachineTest.java @@ -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();