Loading android/app/src/com/android/bluetooth/bass_client/BassClientService.java +41 −23 Original line number Diff line number Diff line Loading @@ -76,6 +76,7 @@ import com.android.bluetooth.le_audio.LeAudioService; import com.android.internal.annotations.VisibleForTesting; import java.nio.charset.StandardCharsets; import java.time.Duration; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; Loading Loading @@ -112,9 +113,14 @@ public class BassClientService extends ProfileService { private static final int BROADCAST_STATE_STOPPING = 3; private static final int BROADCAST_STATE_STREAMING = 4; private static final int MESSAGE_SYNC_TIMEOUT = 1; /* 1 minute timeout for primary device reconnection in Private Broadcast case */ private static final int DIALING_OUT_TIMEOUT_MS = 60000; // 30 secs timeout for keeping PSYNC active when searching is stopped @VisibleForTesting static Duration sSyncActiveTimeout = Duration.ofSeconds(30); private static BassClientService sService; private final Map<BluetoothDevice, BassClientStateMachine> mStateMachines = new HashMap<>(); Loading Loading @@ -142,7 +148,6 @@ public class BassClientService extends ProfileService { private HandlerThread mStateMachinesThread; private HandlerThread mCallbackHandlerThread; private Handler mHandler = null; private AdapterService mAdapterService; private DatabaseManager mDatabaseManager; private BluetoothAdapter mBluetoothAdapter = null; Loading Loading @@ -178,6 +183,19 @@ public class BassClientService extends ProfileService { @VisibleForTesting ServiceFactory mServiceFactory = new ServiceFactory(); private final Handler mHandler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { switch (msg.what) { case MESSAGE_SYNC_TIMEOUT: log("MESSAGE_SYNC_TIMEOUT: clear all sync data"); clearAllSyncData(); break; } } }; public BassClientService(Context ctx) { super(ctx); } Loading Loading @@ -462,9 +480,6 @@ public class BassClientService extends ProfileService { "DatabaseManager cannot be null when BassClientService starts"); mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); // Setup Handler to handle local broadcast use cases. mHandler = new Handler(Looper.getMainLooper()); mStateMachines.clear(); mStateMachinesThread = new HandlerThread("BassClientService.StateMachines"); mStateMachinesThread.start(); Loading Loading @@ -530,11 +545,7 @@ public class BassClientService extends ProfileService { mStateMachinesThread = null; } // Unregister Handler and stop all queued messages. if (mHandler != null) { mHandler.removeCallbacksAndMessages(null); mHandler = null; } setBassClientService(null); if (!leaudioBroadcastExtractPeriodicScannerFromStateMachine()) { Loading Loading @@ -571,12 +582,12 @@ public class BassClientService extends ProfileService { } } else { synchronized (mSearchScanCallbackLock) { if (mBluetoothLeScannerWrapper != null) { if (mBluetoothLeScannerWrapper != null && mSearchScanCallback != null) { mBluetoothLeScannerWrapper.stopScan(mSearchScanCallback); mBluetoothLeScannerWrapper = null; } mBluetoothLeScannerWrapper = null; mSearchScanCallback = null; cleanAllSyncData(); clearAllSyncData(); } mLocalBroadcastReceivers.clear(); Loading Loading @@ -1551,6 +1562,7 @@ public class BassClientService extends ProfileService { informConnectedDeviceAboutScanOffloadStop(); } }; mHandler.removeMessages(MESSAGE_SYNC_TIMEOUT); // when starting scan, clear the previously cached broadcast scan results mCachedBroadcasts.clear(); if (!leaudioBroadcastExtractPeriodicScannerFromStateMachine()) { Loading Loading @@ -1626,11 +1638,7 @@ public class BassClientService extends ProfileService { } } else { synchronized (mSearchScanCallbackLock) { if (mBluetoothLeScannerWrapper == null) { Log.e(TAG, "startLeScan: cannot get BluetoothLeScanner"); return; } if (mSearchScanCallback == null) { if (mBluetoothLeScannerWrapper == null || mSearchScanCallback == null) { Log.e(TAG, "Scan not started yet"); mCallbacks.notifySearchStopFailed(BluetoothStatusCodes.ERROR_UNKNOWN); return; Loading @@ -1638,7 +1646,7 @@ public class BassClientService extends ProfileService { mBluetoothLeScannerWrapper.stopScan(mSearchScanCallback); mBluetoothLeScannerWrapper = null; mSearchScanCallback = null; cleanAllSyncData(); clearAllSyncData(); informConnectedDeviceAboutScanOffloadStop(); sEventLogger.logd(TAG, "stopSearchingForSources"); mCallbacks.notifySearchStopped(BluetoothStatusCodes.REASON_LOCAL_APP_REQUEST); Loading @@ -1646,7 +1654,7 @@ public class BassClientService extends ProfileService { } } private void cleanAllSyncData() { private void clearAllSyncData() { mSourceSyncRequestsQueue.clear(); mPendingSourcesToAdd.clear(); Loading Loading @@ -1708,6 +1716,16 @@ public class BassClientService extends ProfileService { null); addActiveSyncedSource(syncHandle); synchronized (mSearchScanCallbackLock) { // when searching is stopped then start timer to stop active syncs if (mSearchScanCallback == null) { mHandler.removeMessages(MESSAGE_SYNC_TIMEOUT); log("onSyncEstablished started timeout for canceling syncs"); mHandler.sendEmptyMessageDelayed( MESSAGE_SYNC_TIMEOUT, sSyncActiveTimeout.toMillis()); } } // update valid sync handle in mPeriodicAdvCallbacksMap synchronized (mPeriodicAdvCallbacksMap) { if (mPeriodicAdvCallbacksMap.containsKey(BassConstants.INVALID_SYNC_HANDLE)) { Loading Loading @@ -1766,8 +1784,8 @@ public class BassClientService extends ProfileService { } } int broadcastId = getBroadcastIdForSyncHandle(syncHandle); cleanAllDataForSyncHandle(syncHandle); // Clean from cache to make possible sync again clearAllDataForSyncHandle(syncHandle); // Clear from cache to make possible sync again mCachedBroadcasts.remove(broadcastId); } Loading Loading @@ -1811,7 +1829,7 @@ public class BassClientService extends ProfileService { } } private void cleanAllDataForSyncHandle(Integer syncHandle) { private void clearAllDataForSyncHandle(Integer syncHandle) { removeActiveSyncedSource(syncHandle); mPeriodicAdvCallbacksMap.remove(syncHandle); mSyncHandleToBaseDataMap.remove(syncHandle); Loading Loading @@ -1965,7 +1983,7 @@ public class BassClientService extends ProfileService { } else { log("calling unregisterSync, not found syncHandle: " + syncHandle); } cleanAllDataForSyncHandle(syncHandle); clearAllDataForSyncHandle(syncHandle); return true; } Loading android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientServiceTest.java +302 −0 Original line number Diff line number Diff line Loading @@ -92,6 +92,7 @@ import org.mockito.Spy; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; import java.util.concurrent.LinkedBlockingQueue; Loading Loading @@ -538,6 +539,307 @@ public class BassClientServiceTest { } } @Test public void testStopSearchingForSources() { mSetFlagsRule.enableFlags( Flags.FLAG_LEAUDIO_BROADCAST_EXTRACT_PERIODIC_SCANNER_FROM_STATE_MACHINE); prepareConnectedDeviceGroup(); startSearchingForSources(); // Scan and sync 1 clearInvocations(mMethodProxy); onScanResult(mSourceDevice, TEST_BROADCAST_ID); verify(mMethodProxy, timeout(TIMEOUT_MS).times(1)) .periodicAdvertisingManagerRegisterSync( any(), any(), anyInt(), anyInt(), any(), any()); onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE); assertThat(mBassClientService.getActiveSyncedSources().size()).isEqualTo(1); assertThat(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(mSourceDevice); assertThat(mBassClientService.getBroadcastIdForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(TEST_BROADCAST_ID); // Stop searching clearInvocations(mMethodProxy); clearInvocations(mBluetoothLeScannerWrapper); mBassClientService.stopSearchingForSources(); verify(mBluetoothLeScannerWrapper).stopScan(mCallbackCaptor.getValue()); for (BassClientStateMachine sm : mStateMachines.values()) { verify(sm).sendMessage(BassClientStateMachine.STOP_SCAN_OFFLOAD); } // Check if unsyced verify(mMethodProxy, timeout(TIMEOUT_MS).times(1)) .periodicAdvertisingManagerUnregisterSync(any(), any()); assertThat(mBassClientService.getActiveSyncedSources()).isEmpty(); assertThat(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE)).isEqualTo(null); assertThat(mBassClientService.getBroadcastIdForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(BassConstants.INVALID_BROADCAST_ID); } @Test public void testStop() { mSetFlagsRule.enableFlags( Flags.FLAG_LEAUDIO_BROADCAST_EXTRACT_PERIODIC_SCANNER_FROM_STATE_MACHINE); prepareConnectedDeviceGroup(); startSearchingForSources(); // Scan and sync 1 clearInvocations(mMethodProxy); onScanResult(mSourceDevice, TEST_BROADCAST_ID); verify(mMethodProxy, timeout(TIMEOUT_MS).times(1)) .periodicAdvertisingManagerRegisterSync( any(), any(), anyInt(), anyInt(), any(), any()); onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE); assertThat(mBassClientService.getActiveSyncedSources().size()).isEqualTo(1); assertThat(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(mSourceDevice); assertThat(mBassClientService.getBroadcastIdForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(TEST_BROADCAST_ID); // Stop clearInvocations(mMethodProxy); clearInvocations(mBluetoothLeScannerWrapper); mBassClientService.stop(); // Check if unsyced verify(mMethodProxy, timeout(TIMEOUT_MS).times(1)) .periodicAdvertisingManagerUnregisterSync(any(), any()); assertThat(mBassClientService.getActiveSyncedSources()).isEmpty(); assertThat(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE)).isEqualTo(null); assertThat(mBassClientService.getBroadcastIdForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(BassConstants.INVALID_BROADCAST_ID); } @Test public void testStopSearchingForSources_startAndSyncAgain() { mSetFlagsRule.enableFlags( Flags.FLAG_LEAUDIO_BROADCAST_EXTRACT_PERIODIC_SCANNER_FROM_STATE_MACHINE); prepareConnectedDeviceGroup(); startSearchingForSources(); // Scan and sync 1 clearInvocations(mMethodProxy); onScanResult(mSourceDevice, TEST_BROADCAST_ID); onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE); // Stop searching mBassClientService.stopSearchingForSources(); // Start searching again clearInvocations(mBluetoothLeScannerWrapper); startSearchingForSources(); // Sync the same device again clearInvocations(mMethodProxy); onScanResult(mSourceDevice, TEST_BROADCAST_ID); verify(mMethodProxy, timeout(TIMEOUT_MS).times(1)) .periodicAdvertisingManagerRegisterSync( any(), any(), anyInt(), anyInt(), any(), any()); onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE); assertThat(mBassClientService.getActiveSyncedSources().size()).isEqualTo(1); assertThat(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(mSourceDevice); assertThat(mBassClientService.getBroadcastIdForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(TEST_BROADCAST_ID); } @Test public void testStop_startAndSyncAgain() { mSetFlagsRule.enableFlags( Flags.FLAG_LEAUDIO_BROADCAST_EXTRACT_PERIODIC_SCANNER_FROM_STATE_MACHINE); prepareConnectedDeviceGroup(); startSearchingForSources(); // Scan and sync 1 clearInvocations(mMethodProxy); onScanResult(mSourceDevice, TEST_BROADCAST_ID); onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE); // Stop mBassClientService.stop(); // Start again mBassClientService.start(); // Start searching again clearInvocations(mBluetoothLeScannerWrapper); prepareConnectedDeviceGroup(); startSearchingForSources(); // Sync the same device again clearInvocations(mMethodProxy); onScanResult(mSourceDevice, TEST_BROADCAST_ID); verify(mMethodProxy, timeout(TIMEOUT_MS).times(1)) .periodicAdvertisingManagerRegisterSync( any(), any(), anyInt(), anyInt(), any(), any()); onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE); assertThat(mBassClientService.getActiveSyncedSources().size()).isEqualTo(1); assertThat(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(mSourceDevice); assertThat(mBassClientService.getBroadcastIdForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(TEST_BROADCAST_ID); } @Test public void testStopSearchingForSources_addSourceCauseSyncEvenWithoutScanning() { mSetFlagsRule.enableFlags( Flags.FLAG_LEAUDIO_BROADCAST_EXTRACT_PERIODIC_SCANNER_FROM_STATE_MACHINE); prepareConnectedDeviceGroup(); startSearchingForSources(); // Scan and sync 1 onScanResult(mSourceDevice, TEST_BROADCAST_ID); onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE); // Stop searching mBassClientService.stopSearchingForSources(); // Add source to unsynced broadcast, causes synchronization first BluetoothLeBroadcastMetadata meta = createBroadcastMetadata(TEST_BROADCAST_ID); clearInvocations(mMethodProxy); mBassClientService.addSource(mCurrentDevice, meta, true); handleHandoverSupport(); verify(mMethodProxy, timeout(TIMEOUT_MS).times(1)) .periodicAdvertisingManagerRegisterSync( any(), any(), anyInt(), anyInt(), any(), any()); // Verify not getting ADD_BCAST_SOURCE message before source sync assertThat(mStateMachines.size()).isEqualTo(2); for (BassClientStateMachine sm : mStateMachines.values()) { ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class); verify(sm, never()).sendMessage(messageCaptor.capture()); Message msg = messageCaptor.getAllValues().stream() .filter( m -> (m.what == BassClientStateMachine.ADD_BCAST_SOURCE) && (m.obj == meta)) .findFirst() .orElse(null); assertThat(msg).isNull(); clearInvocations(sm); } // Source synced which cause execute pending add source onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE); assertThat(mBassClientService.getActiveSyncedSources().size()).isEqualTo(1); assertThat(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(mSourceDevice); assertThat(mBassClientService.getBroadcastIdForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(TEST_BROADCAST_ID); // Verify all group members getting ADD_BCAST_SOURCE message assertThat(mStateMachines.size()).isEqualTo(2); for (BassClientStateMachine sm : mStateMachines.values()) { ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class); verify(sm, atLeast(1)).sendMessage(messageCaptor.capture()); Message msg = messageCaptor.getAllValues().stream() .filter( m -> (m.what == BassClientStateMachine.ADD_BCAST_SOURCE) && (m.obj == meta)) .findFirst() .orElse(null); assertThat(msg).isNotNull(); } } @Test public void testStopSearchingForSources_timeoutForActiveSync() { mSetFlagsRule.enableFlags( Flags.FLAG_LEAUDIO_BROADCAST_EXTRACT_PERIODIC_SCANNER_FROM_STATE_MACHINE); prepareConnectedDeviceGroup(); startSearchingForSources(); // Scan and sync 1 onScanResult(mSourceDevice, TEST_BROADCAST_ID); onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE); // Stop searching mBassClientService.stopSearchingForSources(); // Add source to unsynced broadcast, causes synchronization first BluetoothLeBroadcastMetadata meta = createBroadcastMetadata(TEST_BROADCAST_ID); mBassClientService.addSource(mCurrentDevice, meta, true); handleHandoverSupport(); // Source synced which cause start timeout event mBassClientService.sSyncActiveTimeout = Duration.ofSeconds(1); clearInvocations(mMethodProxy); onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE); assertThat(mBassClientService.getActiveSyncedSources().size()).isEqualTo(1); assertThat(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(mSourceDevice); assertThat(mBassClientService.getBroadcastIdForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(TEST_BROADCAST_ID); // Check if unsyced verify(mMethodProxy, timeout(2000).times(1)) .periodicAdvertisingManagerUnregisterSync(any(), any()); assertThat(mBassClientService.getActiveSyncedSources()).isEmpty(); assertThat(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE)).isEqualTo(null); assertThat(mBassClientService.getBroadcastIdForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(BassConstants.INVALID_BROADCAST_ID); } @Test public void testStopSearchingForSources_clearTimeoutForActiveSync() { mSetFlagsRule.enableFlags( Flags.FLAG_LEAUDIO_BROADCAST_EXTRACT_PERIODIC_SCANNER_FROM_STATE_MACHINE); prepareConnectedDeviceGroup(); startSearchingForSources(); // Scan and sync 1 onScanResult(mSourceDevice, TEST_BROADCAST_ID); onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE); // Stop searching mBassClientService.stopSearchingForSources(); // Add source to unsynced broadcast, causes synchronization first BluetoothLeBroadcastMetadata meta = createBroadcastMetadata(TEST_BROADCAST_ID); mBassClientService.addSource(mCurrentDevice, meta, true); handleHandoverSupport(); // Source synced which cause start timeout event mBassClientService.sSyncActiveTimeout = Duration.ofSeconds(1); clearInvocations(mMethodProxy); onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE); assertThat(mBassClientService.getActiveSyncedSources().size()).isEqualTo(1); assertThat(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(mSourceDevice); assertThat(mBassClientService.getBroadcastIdForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(TEST_BROADCAST_ID); // Start searching again should clear timeout clearInvocations(mBluetoothLeScannerWrapper); clearInvocations(mMethodProxy); startSearchingForSources(); verify(mMethodProxy, timeout(2000).times(0)) .periodicAdvertisingManagerUnregisterSync(any(), any()); assertThat(mBassClientService.getActiveSyncedSources().size()).isEqualTo(1); assertThat(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(mSourceDevice); assertThat(mBassClientService.getBroadcastIdForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(TEST_BROADCAST_ID); } private byte[] getScanRecord(int broadcastId) { return new byte[] { 0x02, Loading Loading
android/app/src/com/android/bluetooth/bass_client/BassClientService.java +41 −23 Original line number Diff line number Diff line Loading @@ -76,6 +76,7 @@ import com.android.bluetooth.le_audio.LeAudioService; import com.android.internal.annotations.VisibleForTesting; import java.nio.charset.StandardCharsets; import java.time.Duration; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; Loading Loading @@ -112,9 +113,14 @@ public class BassClientService extends ProfileService { private static final int BROADCAST_STATE_STOPPING = 3; private static final int BROADCAST_STATE_STREAMING = 4; private static final int MESSAGE_SYNC_TIMEOUT = 1; /* 1 minute timeout for primary device reconnection in Private Broadcast case */ private static final int DIALING_OUT_TIMEOUT_MS = 60000; // 30 secs timeout for keeping PSYNC active when searching is stopped @VisibleForTesting static Duration sSyncActiveTimeout = Duration.ofSeconds(30); private static BassClientService sService; private final Map<BluetoothDevice, BassClientStateMachine> mStateMachines = new HashMap<>(); Loading Loading @@ -142,7 +148,6 @@ public class BassClientService extends ProfileService { private HandlerThread mStateMachinesThread; private HandlerThread mCallbackHandlerThread; private Handler mHandler = null; private AdapterService mAdapterService; private DatabaseManager mDatabaseManager; private BluetoothAdapter mBluetoothAdapter = null; Loading Loading @@ -178,6 +183,19 @@ public class BassClientService extends ProfileService { @VisibleForTesting ServiceFactory mServiceFactory = new ServiceFactory(); private final Handler mHandler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { switch (msg.what) { case MESSAGE_SYNC_TIMEOUT: log("MESSAGE_SYNC_TIMEOUT: clear all sync data"); clearAllSyncData(); break; } } }; public BassClientService(Context ctx) { super(ctx); } Loading Loading @@ -462,9 +480,6 @@ public class BassClientService extends ProfileService { "DatabaseManager cannot be null when BassClientService starts"); mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); // Setup Handler to handle local broadcast use cases. mHandler = new Handler(Looper.getMainLooper()); mStateMachines.clear(); mStateMachinesThread = new HandlerThread("BassClientService.StateMachines"); mStateMachinesThread.start(); Loading Loading @@ -530,11 +545,7 @@ public class BassClientService extends ProfileService { mStateMachinesThread = null; } // Unregister Handler and stop all queued messages. if (mHandler != null) { mHandler.removeCallbacksAndMessages(null); mHandler = null; } setBassClientService(null); if (!leaudioBroadcastExtractPeriodicScannerFromStateMachine()) { Loading Loading @@ -571,12 +582,12 @@ public class BassClientService extends ProfileService { } } else { synchronized (mSearchScanCallbackLock) { if (mBluetoothLeScannerWrapper != null) { if (mBluetoothLeScannerWrapper != null && mSearchScanCallback != null) { mBluetoothLeScannerWrapper.stopScan(mSearchScanCallback); mBluetoothLeScannerWrapper = null; } mBluetoothLeScannerWrapper = null; mSearchScanCallback = null; cleanAllSyncData(); clearAllSyncData(); } mLocalBroadcastReceivers.clear(); Loading Loading @@ -1551,6 +1562,7 @@ public class BassClientService extends ProfileService { informConnectedDeviceAboutScanOffloadStop(); } }; mHandler.removeMessages(MESSAGE_SYNC_TIMEOUT); // when starting scan, clear the previously cached broadcast scan results mCachedBroadcasts.clear(); if (!leaudioBroadcastExtractPeriodicScannerFromStateMachine()) { Loading Loading @@ -1626,11 +1638,7 @@ public class BassClientService extends ProfileService { } } else { synchronized (mSearchScanCallbackLock) { if (mBluetoothLeScannerWrapper == null) { Log.e(TAG, "startLeScan: cannot get BluetoothLeScanner"); return; } if (mSearchScanCallback == null) { if (mBluetoothLeScannerWrapper == null || mSearchScanCallback == null) { Log.e(TAG, "Scan not started yet"); mCallbacks.notifySearchStopFailed(BluetoothStatusCodes.ERROR_UNKNOWN); return; Loading @@ -1638,7 +1646,7 @@ public class BassClientService extends ProfileService { mBluetoothLeScannerWrapper.stopScan(mSearchScanCallback); mBluetoothLeScannerWrapper = null; mSearchScanCallback = null; cleanAllSyncData(); clearAllSyncData(); informConnectedDeviceAboutScanOffloadStop(); sEventLogger.logd(TAG, "stopSearchingForSources"); mCallbacks.notifySearchStopped(BluetoothStatusCodes.REASON_LOCAL_APP_REQUEST); Loading @@ -1646,7 +1654,7 @@ public class BassClientService extends ProfileService { } } private void cleanAllSyncData() { private void clearAllSyncData() { mSourceSyncRequestsQueue.clear(); mPendingSourcesToAdd.clear(); Loading Loading @@ -1708,6 +1716,16 @@ public class BassClientService extends ProfileService { null); addActiveSyncedSource(syncHandle); synchronized (mSearchScanCallbackLock) { // when searching is stopped then start timer to stop active syncs if (mSearchScanCallback == null) { mHandler.removeMessages(MESSAGE_SYNC_TIMEOUT); log("onSyncEstablished started timeout for canceling syncs"); mHandler.sendEmptyMessageDelayed( MESSAGE_SYNC_TIMEOUT, sSyncActiveTimeout.toMillis()); } } // update valid sync handle in mPeriodicAdvCallbacksMap synchronized (mPeriodicAdvCallbacksMap) { if (mPeriodicAdvCallbacksMap.containsKey(BassConstants.INVALID_SYNC_HANDLE)) { Loading Loading @@ -1766,8 +1784,8 @@ public class BassClientService extends ProfileService { } } int broadcastId = getBroadcastIdForSyncHandle(syncHandle); cleanAllDataForSyncHandle(syncHandle); // Clean from cache to make possible sync again clearAllDataForSyncHandle(syncHandle); // Clear from cache to make possible sync again mCachedBroadcasts.remove(broadcastId); } Loading Loading @@ -1811,7 +1829,7 @@ public class BassClientService extends ProfileService { } } private void cleanAllDataForSyncHandle(Integer syncHandle) { private void clearAllDataForSyncHandle(Integer syncHandle) { removeActiveSyncedSource(syncHandle); mPeriodicAdvCallbacksMap.remove(syncHandle); mSyncHandleToBaseDataMap.remove(syncHandle); Loading Loading @@ -1965,7 +1983,7 @@ public class BassClientService extends ProfileService { } else { log("calling unregisterSync, not found syncHandle: " + syncHandle); } cleanAllDataForSyncHandle(syncHandle); clearAllDataForSyncHandle(syncHandle); return true; } Loading
android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientServiceTest.java +302 −0 Original line number Diff line number Diff line Loading @@ -92,6 +92,7 @@ import org.mockito.Spy; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; import java.util.concurrent.LinkedBlockingQueue; Loading Loading @@ -538,6 +539,307 @@ public class BassClientServiceTest { } } @Test public void testStopSearchingForSources() { mSetFlagsRule.enableFlags( Flags.FLAG_LEAUDIO_BROADCAST_EXTRACT_PERIODIC_SCANNER_FROM_STATE_MACHINE); prepareConnectedDeviceGroup(); startSearchingForSources(); // Scan and sync 1 clearInvocations(mMethodProxy); onScanResult(mSourceDevice, TEST_BROADCAST_ID); verify(mMethodProxy, timeout(TIMEOUT_MS).times(1)) .periodicAdvertisingManagerRegisterSync( any(), any(), anyInt(), anyInt(), any(), any()); onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE); assertThat(mBassClientService.getActiveSyncedSources().size()).isEqualTo(1); assertThat(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(mSourceDevice); assertThat(mBassClientService.getBroadcastIdForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(TEST_BROADCAST_ID); // Stop searching clearInvocations(mMethodProxy); clearInvocations(mBluetoothLeScannerWrapper); mBassClientService.stopSearchingForSources(); verify(mBluetoothLeScannerWrapper).stopScan(mCallbackCaptor.getValue()); for (BassClientStateMachine sm : mStateMachines.values()) { verify(sm).sendMessage(BassClientStateMachine.STOP_SCAN_OFFLOAD); } // Check if unsyced verify(mMethodProxy, timeout(TIMEOUT_MS).times(1)) .periodicAdvertisingManagerUnregisterSync(any(), any()); assertThat(mBassClientService.getActiveSyncedSources()).isEmpty(); assertThat(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE)).isEqualTo(null); assertThat(mBassClientService.getBroadcastIdForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(BassConstants.INVALID_BROADCAST_ID); } @Test public void testStop() { mSetFlagsRule.enableFlags( Flags.FLAG_LEAUDIO_BROADCAST_EXTRACT_PERIODIC_SCANNER_FROM_STATE_MACHINE); prepareConnectedDeviceGroup(); startSearchingForSources(); // Scan and sync 1 clearInvocations(mMethodProxy); onScanResult(mSourceDevice, TEST_BROADCAST_ID); verify(mMethodProxy, timeout(TIMEOUT_MS).times(1)) .periodicAdvertisingManagerRegisterSync( any(), any(), anyInt(), anyInt(), any(), any()); onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE); assertThat(mBassClientService.getActiveSyncedSources().size()).isEqualTo(1); assertThat(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(mSourceDevice); assertThat(mBassClientService.getBroadcastIdForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(TEST_BROADCAST_ID); // Stop clearInvocations(mMethodProxy); clearInvocations(mBluetoothLeScannerWrapper); mBassClientService.stop(); // Check if unsyced verify(mMethodProxy, timeout(TIMEOUT_MS).times(1)) .periodicAdvertisingManagerUnregisterSync(any(), any()); assertThat(mBassClientService.getActiveSyncedSources()).isEmpty(); assertThat(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE)).isEqualTo(null); assertThat(mBassClientService.getBroadcastIdForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(BassConstants.INVALID_BROADCAST_ID); } @Test public void testStopSearchingForSources_startAndSyncAgain() { mSetFlagsRule.enableFlags( Flags.FLAG_LEAUDIO_BROADCAST_EXTRACT_PERIODIC_SCANNER_FROM_STATE_MACHINE); prepareConnectedDeviceGroup(); startSearchingForSources(); // Scan and sync 1 clearInvocations(mMethodProxy); onScanResult(mSourceDevice, TEST_BROADCAST_ID); onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE); // Stop searching mBassClientService.stopSearchingForSources(); // Start searching again clearInvocations(mBluetoothLeScannerWrapper); startSearchingForSources(); // Sync the same device again clearInvocations(mMethodProxy); onScanResult(mSourceDevice, TEST_BROADCAST_ID); verify(mMethodProxy, timeout(TIMEOUT_MS).times(1)) .periodicAdvertisingManagerRegisterSync( any(), any(), anyInt(), anyInt(), any(), any()); onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE); assertThat(mBassClientService.getActiveSyncedSources().size()).isEqualTo(1); assertThat(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(mSourceDevice); assertThat(mBassClientService.getBroadcastIdForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(TEST_BROADCAST_ID); } @Test public void testStop_startAndSyncAgain() { mSetFlagsRule.enableFlags( Flags.FLAG_LEAUDIO_BROADCAST_EXTRACT_PERIODIC_SCANNER_FROM_STATE_MACHINE); prepareConnectedDeviceGroup(); startSearchingForSources(); // Scan and sync 1 clearInvocations(mMethodProxy); onScanResult(mSourceDevice, TEST_BROADCAST_ID); onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE); // Stop mBassClientService.stop(); // Start again mBassClientService.start(); // Start searching again clearInvocations(mBluetoothLeScannerWrapper); prepareConnectedDeviceGroup(); startSearchingForSources(); // Sync the same device again clearInvocations(mMethodProxy); onScanResult(mSourceDevice, TEST_BROADCAST_ID); verify(mMethodProxy, timeout(TIMEOUT_MS).times(1)) .periodicAdvertisingManagerRegisterSync( any(), any(), anyInt(), anyInt(), any(), any()); onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE); assertThat(mBassClientService.getActiveSyncedSources().size()).isEqualTo(1); assertThat(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(mSourceDevice); assertThat(mBassClientService.getBroadcastIdForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(TEST_BROADCAST_ID); } @Test public void testStopSearchingForSources_addSourceCauseSyncEvenWithoutScanning() { mSetFlagsRule.enableFlags( Flags.FLAG_LEAUDIO_BROADCAST_EXTRACT_PERIODIC_SCANNER_FROM_STATE_MACHINE); prepareConnectedDeviceGroup(); startSearchingForSources(); // Scan and sync 1 onScanResult(mSourceDevice, TEST_BROADCAST_ID); onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE); // Stop searching mBassClientService.stopSearchingForSources(); // Add source to unsynced broadcast, causes synchronization first BluetoothLeBroadcastMetadata meta = createBroadcastMetadata(TEST_BROADCAST_ID); clearInvocations(mMethodProxy); mBassClientService.addSource(mCurrentDevice, meta, true); handleHandoverSupport(); verify(mMethodProxy, timeout(TIMEOUT_MS).times(1)) .periodicAdvertisingManagerRegisterSync( any(), any(), anyInt(), anyInt(), any(), any()); // Verify not getting ADD_BCAST_SOURCE message before source sync assertThat(mStateMachines.size()).isEqualTo(2); for (BassClientStateMachine sm : mStateMachines.values()) { ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class); verify(sm, never()).sendMessage(messageCaptor.capture()); Message msg = messageCaptor.getAllValues().stream() .filter( m -> (m.what == BassClientStateMachine.ADD_BCAST_SOURCE) && (m.obj == meta)) .findFirst() .orElse(null); assertThat(msg).isNull(); clearInvocations(sm); } // Source synced which cause execute pending add source onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE); assertThat(mBassClientService.getActiveSyncedSources().size()).isEqualTo(1); assertThat(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(mSourceDevice); assertThat(mBassClientService.getBroadcastIdForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(TEST_BROADCAST_ID); // Verify all group members getting ADD_BCAST_SOURCE message assertThat(mStateMachines.size()).isEqualTo(2); for (BassClientStateMachine sm : mStateMachines.values()) { ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class); verify(sm, atLeast(1)).sendMessage(messageCaptor.capture()); Message msg = messageCaptor.getAllValues().stream() .filter( m -> (m.what == BassClientStateMachine.ADD_BCAST_SOURCE) && (m.obj == meta)) .findFirst() .orElse(null); assertThat(msg).isNotNull(); } } @Test public void testStopSearchingForSources_timeoutForActiveSync() { mSetFlagsRule.enableFlags( Flags.FLAG_LEAUDIO_BROADCAST_EXTRACT_PERIODIC_SCANNER_FROM_STATE_MACHINE); prepareConnectedDeviceGroup(); startSearchingForSources(); // Scan and sync 1 onScanResult(mSourceDevice, TEST_BROADCAST_ID); onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE); // Stop searching mBassClientService.stopSearchingForSources(); // Add source to unsynced broadcast, causes synchronization first BluetoothLeBroadcastMetadata meta = createBroadcastMetadata(TEST_BROADCAST_ID); mBassClientService.addSource(mCurrentDevice, meta, true); handleHandoverSupport(); // Source synced which cause start timeout event mBassClientService.sSyncActiveTimeout = Duration.ofSeconds(1); clearInvocations(mMethodProxy); onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE); assertThat(mBassClientService.getActiveSyncedSources().size()).isEqualTo(1); assertThat(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(mSourceDevice); assertThat(mBassClientService.getBroadcastIdForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(TEST_BROADCAST_ID); // Check if unsyced verify(mMethodProxy, timeout(2000).times(1)) .periodicAdvertisingManagerUnregisterSync(any(), any()); assertThat(mBassClientService.getActiveSyncedSources()).isEmpty(); assertThat(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE)).isEqualTo(null); assertThat(mBassClientService.getBroadcastIdForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(BassConstants.INVALID_BROADCAST_ID); } @Test public void testStopSearchingForSources_clearTimeoutForActiveSync() { mSetFlagsRule.enableFlags( Flags.FLAG_LEAUDIO_BROADCAST_EXTRACT_PERIODIC_SCANNER_FROM_STATE_MACHINE); prepareConnectedDeviceGroup(); startSearchingForSources(); // Scan and sync 1 onScanResult(mSourceDevice, TEST_BROADCAST_ID); onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE); // Stop searching mBassClientService.stopSearchingForSources(); // Add source to unsynced broadcast, causes synchronization first BluetoothLeBroadcastMetadata meta = createBroadcastMetadata(TEST_BROADCAST_ID); mBassClientService.addSource(mCurrentDevice, meta, true); handleHandoverSupport(); // Source synced which cause start timeout event mBassClientService.sSyncActiveTimeout = Duration.ofSeconds(1); clearInvocations(mMethodProxy); onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE); assertThat(mBassClientService.getActiveSyncedSources().size()).isEqualTo(1); assertThat(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(mSourceDevice); assertThat(mBassClientService.getBroadcastIdForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(TEST_BROADCAST_ID); // Start searching again should clear timeout clearInvocations(mBluetoothLeScannerWrapper); clearInvocations(mMethodProxy); startSearchingForSources(); verify(mMethodProxy, timeout(2000).times(0)) .periodicAdvertisingManagerUnregisterSync(any(), any()); assertThat(mBassClientService.getActiveSyncedSources().size()).isEqualTo(1); assertThat(mBassClientService.getDeviceForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(mSourceDevice); assertThat(mBassClientService.getBroadcastIdForSyncHandle(TEST_SYNC_HANDLE)) .isEqualTo(TEST_BROADCAST_ID); } private byte[] getScanRecord(int broadcastId) { return new byte[] { 0x02, Loading