Loading android/app/src/com/android/bluetooth/bass_client/BassClientService.java +58 −17 Original line number Original line Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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 Loading Loading @@ -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() { Loading Loading @@ -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; } } Loading Loading @@ -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(); Loading @@ -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; } } Loading Loading @@ -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"); Loading android/app/src/com/android/bluetooth/bass_client/BassClientStateMachine.java +31 −23 Original line number Original line Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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, Loading Loading @@ -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"); Loading @@ -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() { Loading Loading @@ -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); Loading Loading @@ -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"); Loading android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientServiceTest.java +104 −1 Original line number Original line Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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); } } } android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientStateMachineTest.java +35 −7 Original line number Original line Diff line number Diff line Loading @@ -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); Loading Loading @@ -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 Loading @@ -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 Loading Loading @@ -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()); Loading Loading @@ -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()); Loading @@ -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()); Loading Loading @@ -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 Loading Loading @@ -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()); Loading Loading @@ -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()); Loading @@ -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()); Loading @@ -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(); Loading Loading
android/app/src/com/android/bluetooth/bass_client/BassClientService.java +58 −17 Original line number Original line Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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 Loading Loading @@ -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() { Loading Loading @@ -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; } } Loading Loading @@ -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(); Loading @@ -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; } } Loading Loading @@ -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"); Loading
android/app/src/com/android/bluetooth/bass_client/BassClientStateMachine.java +31 −23 Original line number Original line Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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, Loading Loading @@ -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"); Loading @@ -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() { Loading Loading @@ -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); Loading Loading @@ -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"); Loading
android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientServiceTest.java +104 −1 Original line number Original line Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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); } } }
android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientStateMachineTest.java +35 −7 Original line number Original line Diff line number Diff line Loading @@ -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); Loading Loading @@ -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 Loading @@ -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 Loading Loading @@ -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()); Loading Loading @@ -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()); Loading @@ -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()); Loading Loading @@ -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 Loading Loading @@ -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()); Loading Loading @@ -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()); Loading @@ -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()); Loading @@ -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(); Loading