Loading android/app/src/com/android/bluetooth/bass_client/BassClientService.java +23 −22 Original line number Diff line number Diff line Loading @@ -92,7 +92,6 @@ import java.util.Deque; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Objects; Loading Loading @@ -148,7 +147,7 @@ public class BassClientService extends ProfileService { private final Map<BluetoothDevice, List<Integer>> mActiveSourceMap = new ConcurrentHashMap<>(); private final Map<BluetoothDevice, BluetoothLeBroadcastMetadata> mBroadcastMetadataMap = new ConcurrentHashMap<>(); private final LinkedList<BluetoothDevice> mPausedBroadcastSinks = new LinkedList<>(); private final HashSet<BluetoothDevice> mPausedBroadcastSinks = new HashSet<>(); private final Deque<AddSourceData> mPendingAddSources = new ArrayDeque<>(); private final Map<Integer, HashSet<BluetoothDevice>> mLocalBroadcastReceivers = new ConcurrentHashMap<>(); Loading Loading @@ -704,6 +703,7 @@ public class BassClientService extends ProfileService { mLocalBroadcastReceivers.clear(); mPendingGroupOp.clear(); mBroadcastMetadataMap.clear(); mPausedBroadcastSinks.clear(); } } Loading Loading @@ -1394,6 +1394,7 @@ public class BassClientService extends ProfileService { // Check if the device is disconnected - if unbond, remove the state machine if (toState == BluetoothProfile.STATE_DISCONNECTED) { mPendingGroupOp.remove(device); mPausedBroadcastSinks.remove(device); int bondState = mAdapterService.getBondState(device); if (bondState == BluetoothDevice.BOND_NONE) { Loading Loading @@ -2995,15 +2996,14 @@ public class BassClientService extends ProfileService { private void stopSourceReceivers(int broadcastId, boolean store) { Log.d(TAG, "stopSourceReceivers(), broadcastId: " + broadcastId + ", store: " + store); if (store && !mPausedBroadcastSinks.isEmpty()) { Log.w(TAG, "stopSourceReceivers(), paused broadcast sinks are replaced"); sEventLogger.logd(TAG, "Clear broadcast sinks paused cache"); mPausedBroadcastSinks.clear(); } Map<BluetoothDevice, Integer> sourcesToRemove = new HashMap<>(); for (BluetoothDevice device : getConnectedDevices()) { if (mPausedBroadcastSinks.contains(device)) { // Skip this device if it has been paused continue; } for (BluetoothLeBroadcastReceiveState receiveState : getAllSources(device)) { /* Check if local/last broadcast is the synced one. Invalid broadcast ID means * that all receivers should be considered. Loading @@ -3013,7 +3013,7 @@ public class BassClientService extends ProfileService { continue; } if (store && !mPausedBroadcastSinks.contains(device)) { if (store) { sEventLogger.logd(TAG, "Add broadcast sink to paused cache: " + device); mPausedBroadcastSinks.add(device); } Loading Loading @@ -3167,18 +3167,12 @@ public class BassClientService extends ProfileService { } } /** Cache suspending sources */ /** Cache suspending sources when broadcast paused */ public void cacheSuspendingSources(int broadcastId) { sEventLogger.logd(TAG, "Cache suspending sources: " + broadcastId); List<Pair<BluetoothLeBroadcastReceiveState, BluetoothDevice>> sourcesToCache = getReceiveStateDevicePairs(broadcastId); if (!mPausedBroadcastSinks.isEmpty()) { Log.w(TAG, "cacheSuspendingSources(), paused broadcast sinks are replaced"); sEventLogger.logd(TAG, "Clear broadcast sinks paused cache"); mPausedBroadcastSinks.clear(); } for (Pair<BluetoothLeBroadcastReceiveState, BluetoothDevice> pair : sourcesToCache) { mPausedBroadcastSinks.add(pair.second); } Loading Loading @@ -3210,8 +3204,9 @@ public class BassClientService extends ProfileService { public void resumeReceiversSourceSynchronization() { sEventLogger.logd(TAG, "Resume receivers source synchronization"); while (!mPausedBroadcastSinks.isEmpty()) { BluetoothDevice sink = mPausedBroadcastSinks.remove(); Iterator<BluetoothDevice> iterator = mPausedBroadcastSinks.iterator(); while (iterator.hasNext()) { BluetoothDevice sink = iterator.next(); sEventLogger.logd(TAG, "Remove broadcast sink from paused cache: " + sink); BluetoothLeBroadcastMetadata metadata = mBroadcastMetadataMap.get(sink); Loading @@ -3219,9 +3214,11 @@ public class BassClientService extends ProfileService { if (metadata == null) { Log.w( TAG, "resumeReceiversSourceSynchronization: failed to get metadata to resume" + " sink: " "resumeReceiversSourceSynchronization: failed to get metadata to" + " resume sink: " + sink); // remove the device from mPausedBroadcastSinks iterator.remove(); continue; } Loading @@ -3244,6 +3241,8 @@ public class BassClientService extends ProfileService { if (statusCode != BluetoothStatusCodes.SUCCESS) { mCallbacks.notifySourceModifyFailed(sink, sourceId, statusCode); // remove the device from mPausedBroadcastSinks iterator.remove(); continue; } Loading Loading @@ -3275,11 +3274,13 @@ public class BassClientService extends ProfileService { } else { Log.w( TAG, "resumeReceiversSourceSynchronization: failed to get metadata to resume" + " sink: " "resumeReceiversSourceSynchronization: failed to get metadata to" + " resume sink: " + sink); } } // remove the device from mPausedBroadcastSinks iterator.remove(); } } Loading android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientServiceTest.java +163 −177 Original line number Diff line number Diff line Loading @@ -1438,6 +1438,66 @@ public class BassClientServiceTest { TestUtils.waitForLooperToFinishScheduledTask(mBassClientService.getCallbacks().getLooper()); } private void prepareRemoteSourceState(BluetoothLeBroadcastMetadata meta, boolean isBisSynced) { for (BassClientStateMachine sm : mStateMachines.values()) { if (sm.getDevice().equals(mCurrentDevice)) { injectRemoteSourceStateSourceAdded( sm, meta, TEST_SOURCE_ID, BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_IDLE, meta.isEncrypted() ? BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING : BluetoothLeBroadcastReceiveState .BIG_ENCRYPTION_STATE_NOT_ENCRYPTED, null); if (isBisSynced) { // Update receiver state injectRemoteSourceStateChanged( sm, meta, TEST_SOURCE_ID, BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_IDLE, meta.isEncrypted() ? BluetoothLeBroadcastReceiveState .BIG_ENCRYPTION_STATE_DECRYPTING : BluetoothLeBroadcastReceiveState .BIG_ENCRYPTION_STATE_NOT_ENCRYPTED, null, (long) 0x00000001); } } else if (sm.getDevice().equals(mCurrentDevice1)) { injectRemoteSourceStateSourceAdded( sm, meta, TEST_SOURCE_ID + 1, BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_IDLE, meta.isEncrypted() ? BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING : BluetoothLeBroadcastReceiveState .BIG_ENCRYPTION_STATE_NOT_ENCRYPTED, null); if (isBisSynced) { // Update receiver state injectRemoteSourceStateChanged( sm, meta, TEST_SOURCE_ID + 1, BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_IDLE, meta.isEncrypted() ? BluetoothLeBroadcastReceiveState .BIG_ENCRYPTION_STATE_DECRYPTING : BluetoothLeBroadcastReceiveState .BIG_ENCRYPTION_STATE_NOT_ENCRYPTED, null, (long) 0x00000002); } } } } /** * Test whether service.addSource() does send proper messages to all the state machines within * the Csip coordinated group Loading Loading @@ -1520,31 +1580,7 @@ public class BassClientServiceTest { onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE); BluetoothLeBroadcastMetadata meta = createBroadcastMetadata(TEST_BROADCAST_ID); verifyAddSourceForGroup(meta); for (BassClientStateMachine sm : mStateMachines.values()) { if (sm.getDevice().equals(mCurrentDevice)) { injectRemoteSourceStateSourceAdded( sm, meta, TEST_SOURCE_ID, BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_IDLE, meta.isEncrypted() ? BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING : BluetoothLeBroadcastReceiveState .BIG_ENCRYPTION_STATE_NOT_ENCRYPTED, null); } else if (sm.getDevice().equals(mCurrentDevice1)) { injectRemoteSourceStateSourceAdded( sm, meta, TEST_SOURCE_ID + 1, BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_IDLE, meta.isEncrypted() ? BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING : BluetoothLeBroadcastReceiveState .BIG_ENCRYPTION_STATE_NOT_ENCRYPTED, null); } } prepareRemoteSourceState(meta, false); // Update broadcast source using other member of the same group BluetoothLeBroadcastMetadata metaUpdate = Loading Loading @@ -1587,31 +1623,7 @@ public class BassClientServiceTest { onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE); BluetoothLeBroadcastMetadata meta = createBroadcastMetadata(TEST_BROADCAST_ID); verifyAddSourceForGroup(meta); for (BassClientStateMachine sm : mStateMachines.values()) { if (sm.getDevice().equals(mCurrentDevice)) { injectRemoteSourceStateSourceAdded( sm, meta, TEST_SOURCE_ID, BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_IDLE, meta.isEncrypted() ? BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING : BluetoothLeBroadcastReceiveState .BIG_ENCRYPTION_STATE_NOT_ENCRYPTED, null); } else if (sm.getDevice().equals(mCurrentDevice1)) { injectRemoteSourceStateSourceAdded( sm, meta, TEST_SOURCE_ID + 1, BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_IDLE, meta.isEncrypted() ? BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING : BluetoothLeBroadcastReceiveState .BIG_ENCRYPTION_STATE_NOT_ENCRYPTED, null); } } prepareRemoteSourceState(meta, false); // Remove broadcast source using other member of the same group mBassClientService.removeSource(mCurrentDevice1, TEST_SOURCE_ID + 1); Loading Loading @@ -1732,32 +1744,7 @@ public class BassClientServiceTest { onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE); BluetoothLeBroadcastMetadata meta = createBroadcastMetadata(TEST_BROADCAST_ID); verifyAddSourceForGroup(meta); // Inject source added for (BassClientStateMachine sm : mStateMachines.values()) { if (sm.getDevice().equals(mCurrentDevice)) { injectRemoteSourceStateSourceAdded( sm, meta, TEST_SOURCE_ID, BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_IDLE, meta.isEncrypted() ? BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING : BluetoothLeBroadcastReceiveState .BIG_ENCRYPTION_STATE_NOT_ENCRYPTED, null); } else if (sm.getDevice().equals(mCurrentDevice1)) { injectRemoteSourceStateSourceAdded( sm, meta, TEST_SOURCE_ID + 1, BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_IDLE, meta.isEncrypted() ? BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING : BluetoothLeBroadcastReceiveState .BIG_ENCRYPTION_STATE_NOT_ENCRYPTED, null); } } prepareRemoteSourceState(meta, false); // Remove broadcast source mBassClientService.removeSource(mCurrentDevice, TEST_SOURCE_ID); Loading Loading @@ -2089,31 +2076,7 @@ public class BassClientServiceTest { // Prepare valid source for group BluetoothLeBroadcastMetadata meta = createBroadcastMetadata(TEST_BROADCAST_ID); verifyAddSourceForGroup(meta); for (BassClientStateMachine sm : mStateMachines.values()) { if (sm.getDevice().equals(mCurrentDevice)) { injectRemoteSourceStateSourceAdded( sm, meta, TEST_SOURCE_ID, BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_IDLE, meta.isEncrypted() ? BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING : BluetoothLeBroadcastReceiveState .BIG_ENCRYPTION_STATE_NOT_ENCRYPTED, null); } else if (sm.getDevice().equals(mCurrentDevice1)) { injectRemoteSourceStateSourceAdded( sm, meta, TEST_SOURCE_ID + 1, BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_IDLE, meta.isEncrypted() ? BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING : BluetoothLeBroadcastReceiveState .BIG_ENCRYPTION_STATE_NOT_ENCRYPTED, null); } } prepareRemoteSourceState(meta, false); // Verify errors are reported for the entire group mBassClientService.modifySource(mCurrentDevice, TEST_SOURCE_ID, null); Loading Loading @@ -3619,31 +3582,7 @@ public class BassClientServiceTest { onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE); BluetoothLeBroadcastMetadata meta = createBroadcastMetadata(TEST_BROADCAST_ID); verifyAddSourceForGroup(meta); for (BassClientStateMachine sm : mStateMachines.values()) { if (sm.getDevice().equals(mCurrentDevice)) { injectRemoteSourceStateSourceAdded( sm, meta, TEST_SOURCE_ID, BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_IDLE, meta.isEncrypted() ? BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING : BluetoothLeBroadcastReceiveState .BIG_ENCRYPTION_STATE_NOT_ENCRYPTED, null); } else if (sm.getDevice().equals(mCurrentDevice1)) { injectRemoteSourceStateSourceAdded( sm, meta, TEST_SOURCE_ID + 1, BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_IDLE, meta.isEncrypted() ? BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING : BluetoothLeBroadcastReceiveState .BIG_ENCRYPTION_STATE_NOT_ENCRYPTED, null); } } prepareRemoteSourceState(meta, false); if (Flags.leaudioBroadcastAssistantPeripheralEntrustment()) { for (BassClientStateMachine sm : mStateMachines.values()) { Loading Loading @@ -3805,58 +3744,9 @@ public class BassClientServiceTest { .getAllBroadcastMetadata(); verifyAddSourceForGroup(meta); for (BassClientStateMachine sm : mStateMachines.values()) { if (sm.getDevice().equals(mCurrentDevice)) { injectRemoteSourceStateSourceAdded( sm, meta, TEST_SOURCE_ID, BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_IDLE, meta.isEncrypted() ? BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING : BluetoothLeBroadcastReceiveState .BIG_ENCRYPTION_STATE_NOT_ENCRYPTED, null); prepareRemoteSourceState(meta, true); // Update receiver state injectRemoteSourceStateChanged( sm, meta, TEST_SOURCE_ID, BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_IDLE, meta.isEncrypted() ? BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING : BluetoothLeBroadcastReceiveState .BIG_ENCRYPTION_STATE_NOT_ENCRYPTED, null, (long) 0x00000001); verify(mLeAudioService).activeBroadcastAssistantNotification(eq(true)); } else if (sm.getDevice().equals(mCurrentDevice1)) { injectRemoteSourceStateSourceAdded( sm, meta, TEST_SOURCE_ID + 1, BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_IDLE, meta.isEncrypted() ? BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING : BluetoothLeBroadcastReceiveState .BIG_ENCRYPTION_STATE_NOT_ENCRYPTED, null); // Update receiver state injectRemoteSourceStateChanged( sm, meta, TEST_SOURCE_ID + 1, BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_IDLE, meta.isEncrypted() ? BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING : BluetoothLeBroadcastReceiveState .BIG_ENCRYPTION_STATE_NOT_ENCRYPTED, null, (long) 0x00000002); } } /* Unicast would like to stream */ mBassClientService.handleUnicastSourceStreamStatusChange( Loading Loading @@ -4002,6 +3892,102 @@ public class BassClientServiceTest { } } @Test public void testHandleUnicastSourceStreamStatusChange_MultipleRequests() { mSetFlagsRule.enableFlags(Flags.FLAG_LEAUDIO_BROADCAST_ASSISTANT_PERIPHERAL_ENTRUSTMENT); prepareConnectedDeviceGroup(); startSearchingForSources(); onScanResult(mSourceDevice, TEST_BROADCAST_ID); onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE); BluetoothLeBroadcastMetadata meta = createBroadcastMetadata(TEST_BROADCAST_ID); /* Fake external broadcast - no Broadcast Metadata from LE Audio service */ doReturn(new ArrayList<BluetoothLeBroadcastMetadata>()) .when(mLeAudioService) .getAllBroadcastMetadata(); verifyAddSourceForGroup(meta); prepareRemoteSourceState(meta, true); verify(mLeAudioService).activeBroadcastAssistantNotification(eq(true)); /* Unicast would like to stream */ mBassClientService.handleUnicastSourceStreamStatusChange( 3 /* STATUS_LOCAL_STREAM_REQUESTED_NO_CONTEXT_VALIDATE */); /* Imitate broadcast source stop, sink notify about loosing BIS sync */ for (BassClientStateMachine sm : mStateMachines.values()) { // Inject source removed ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class); verify(sm, atLeast(1)).sendMessage(messageCaptor.capture()); Optional<Message> msg = messageCaptor.getAllValues().stream() .filter(m -> m.what == BassClientStateMachine.REMOVE_BCAST_SOURCE) .findFirst(); assertThat(msg.isPresent()).isEqualTo(true); if (sm.getDevice().equals(mCurrentDevice)) { assertThat(msg.get().arg1).isEqualTo(TEST_SOURCE_ID); injectRemoteSourceStateRemoval(sm, TEST_SOURCE_ID); } else if (sm.getDevice().equals(mCurrentDevice1)) { assertThat(msg.get().arg1).isEqualTo(TEST_SOURCE_ID + 1); injectRemoteSourceStateRemoval(sm, TEST_SOURCE_ID + 1); } } assertThat(mStateMachines.size()).isEqualTo(2); for (BassClientStateMachine sm : mStateMachines.values()) { Mockito.clearInvocations(sm); } // Make another stream request with no context validate // and verify sm didn't get REMOVE_BCAST_SOURCE mBassClientService.handleUnicastSourceStreamStatusChange( 3 /* STATUS_LOCAL_STREAM_REQUESTED_NO_CONTEXT_VALIDATE */); // Make another stream request // and verify sinks to resume remain unchanged later mBassClientService.handleUnicastSourceStreamStatusChange( 0 /* STATUS_LOCAL_STREAM_REQUESTED */); 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.REMOVE_BCAST_SOURCE) && (m.arg1 == TEST_SOURCE_ID)) .findFirst() .orElse(null); assertThat(msg).isNull(); } /* Unicast finished streaming */ mBassClientService.handleUnicastSourceStreamStatusChange( 2 /* STATUS_LOCAL_STREAM_SUSPENDED */); // Verify all group members resume with the previous cached source 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 testIsAnyReceiverReceivingBroadcast() { prepareConnectedDeviceGroup(); Loading Loading
android/app/src/com/android/bluetooth/bass_client/BassClientService.java +23 −22 Original line number Diff line number Diff line Loading @@ -92,7 +92,6 @@ import java.util.Deque; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Objects; Loading Loading @@ -148,7 +147,7 @@ public class BassClientService extends ProfileService { private final Map<BluetoothDevice, List<Integer>> mActiveSourceMap = new ConcurrentHashMap<>(); private final Map<BluetoothDevice, BluetoothLeBroadcastMetadata> mBroadcastMetadataMap = new ConcurrentHashMap<>(); private final LinkedList<BluetoothDevice> mPausedBroadcastSinks = new LinkedList<>(); private final HashSet<BluetoothDevice> mPausedBroadcastSinks = new HashSet<>(); private final Deque<AddSourceData> mPendingAddSources = new ArrayDeque<>(); private final Map<Integer, HashSet<BluetoothDevice>> mLocalBroadcastReceivers = new ConcurrentHashMap<>(); Loading Loading @@ -704,6 +703,7 @@ public class BassClientService extends ProfileService { mLocalBroadcastReceivers.clear(); mPendingGroupOp.clear(); mBroadcastMetadataMap.clear(); mPausedBroadcastSinks.clear(); } } Loading Loading @@ -1394,6 +1394,7 @@ public class BassClientService extends ProfileService { // Check if the device is disconnected - if unbond, remove the state machine if (toState == BluetoothProfile.STATE_DISCONNECTED) { mPendingGroupOp.remove(device); mPausedBroadcastSinks.remove(device); int bondState = mAdapterService.getBondState(device); if (bondState == BluetoothDevice.BOND_NONE) { Loading Loading @@ -2995,15 +2996,14 @@ public class BassClientService extends ProfileService { private void stopSourceReceivers(int broadcastId, boolean store) { Log.d(TAG, "stopSourceReceivers(), broadcastId: " + broadcastId + ", store: " + store); if (store && !mPausedBroadcastSinks.isEmpty()) { Log.w(TAG, "stopSourceReceivers(), paused broadcast sinks are replaced"); sEventLogger.logd(TAG, "Clear broadcast sinks paused cache"); mPausedBroadcastSinks.clear(); } Map<BluetoothDevice, Integer> sourcesToRemove = new HashMap<>(); for (BluetoothDevice device : getConnectedDevices()) { if (mPausedBroadcastSinks.contains(device)) { // Skip this device if it has been paused continue; } for (BluetoothLeBroadcastReceiveState receiveState : getAllSources(device)) { /* Check if local/last broadcast is the synced one. Invalid broadcast ID means * that all receivers should be considered. Loading @@ -3013,7 +3013,7 @@ public class BassClientService extends ProfileService { continue; } if (store && !mPausedBroadcastSinks.contains(device)) { if (store) { sEventLogger.logd(TAG, "Add broadcast sink to paused cache: " + device); mPausedBroadcastSinks.add(device); } Loading Loading @@ -3167,18 +3167,12 @@ public class BassClientService extends ProfileService { } } /** Cache suspending sources */ /** Cache suspending sources when broadcast paused */ public void cacheSuspendingSources(int broadcastId) { sEventLogger.logd(TAG, "Cache suspending sources: " + broadcastId); List<Pair<BluetoothLeBroadcastReceiveState, BluetoothDevice>> sourcesToCache = getReceiveStateDevicePairs(broadcastId); if (!mPausedBroadcastSinks.isEmpty()) { Log.w(TAG, "cacheSuspendingSources(), paused broadcast sinks are replaced"); sEventLogger.logd(TAG, "Clear broadcast sinks paused cache"); mPausedBroadcastSinks.clear(); } for (Pair<BluetoothLeBroadcastReceiveState, BluetoothDevice> pair : sourcesToCache) { mPausedBroadcastSinks.add(pair.second); } Loading Loading @@ -3210,8 +3204,9 @@ public class BassClientService extends ProfileService { public void resumeReceiversSourceSynchronization() { sEventLogger.logd(TAG, "Resume receivers source synchronization"); while (!mPausedBroadcastSinks.isEmpty()) { BluetoothDevice sink = mPausedBroadcastSinks.remove(); Iterator<BluetoothDevice> iterator = mPausedBroadcastSinks.iterator(); while (iterator.hasNext()) { BluetoothDevice sink = iterator.next(); sEventLogger.logd(TAG, "Remove broadcast sink from paused cache: " + sink); BluetoothLeBroadcastMetadata metadata = mBroadcastMetadataMap.get(sink); Loading @@ -3219,9 +3214,11 @@ public class BassClientService extends ProfileService { if (metadata == null) { Log.w( TAG, "resumeReceiversSourceSynchronization: failed to get metadata to resume" + " sink: " "resumeReceiversSourceSynchronization: failed to get metadata to" + " resume sink: " + sink); // remove the device from mPausedBroadcastSinks iterator.remove(); continue; } Loading @@ -3244,6 +3241,8 @@ public class BassClientService extends ProfileService { if (statusCode != BluetoothStatusCodes.SUCCESS) { mCallbacks.notifySourceModifyFailed(sink, sourceId, statusCode); // remove the device from mPausedBroadcastSinks iterator.remove(); continue; } Loading Loading @@ -3275,11 +3274,13 @@ public class BassClientService extends ProfileService { } else { Log.w( TAG, "resumeReceiversSourceSynchronization: failed to get metadata to resume" + " sink: " "resumeReceiversSourceSynchronization: failed to get metadata to" + " resume sink: " + sink); } } // remove the device from mPausedBroadcastSinks iterator.remove(); } } Loading
android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientServiceTest.java +163 −177 Original line number Diff line number Diff line Loading @@ -1438,6 +1438,66 @@ public class BassClientServiceTest { TestUtils.waitForLooperToFinishScheduledTask(mBassClientService.getCallbacks().getLooper()); } private void prepareRemoteSourceState(BluetoothLeBroadcastMetadata meta, boolean isBisSynced) { for (BassClientStateMachine sm : mStateMachines.values()) { if (sm.getDevice().equals(mCurrentDevice)) { injectRemoteSourceStateSourceAdded( sm, meta, TEST_SOURCE_ID, BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_IDLE, meta.isEncrypted() ? BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING : BluetoothLeBroadcastReceiveState .BIG_ENCRYPTION_STATE_NOT_ENCRYPTED, null); if (isBisSynced) { // Update receiver state injectRemoteSourceStateChanged( sm, meta, TEST_SOURCE_ID, BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_IDLE, meta.isEncrypted() ? BluetoothLeBroadcastReceiveState .BIG_ENCRYPTION_STATE_DECRYPTING : BluetoothLeBroadcastReceiveState .BIG_ENCRYPTION_STATE_NOT_ENCRYPTED, null, (long) 0x00000001); } } else if (sm.getDevice().equals(mCurrentDevice1)) { injectRemoteSourceStateSourceAdded( sm, meta, TEST_SOURCE_ID + 1, BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_IDLE, meta.isEncrypted() ? BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING : BluetoothLeBroadcastReceiveState .BIG_ENCRYPTION_STATE_NOT_ENCRYPTED, null); if (isBisSynced) { // Update receiver state injectRemoteSourceStateChanged( sm, meta, TEST_SOURCE_ID + 1, BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_IDLE, meta.isEncrypted() ? BluetoothLeBroadcastReceiveState .BIG_ENCRYPTION_STATE_DECRYPTING : BluetoothLeBroadcastReceiveState .BIG_ENCRYPTION_STATE_NOT_ENCRYPTED, null, (long) 0x00000002); } } } } /** * Test whether service.addSource() does send proper messages to all the state machines within * the Csip coordinated group Loading Loading @@ -1520,31 +1580,7 @@ public class BassClientServiceTest { onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE); BluetoothLeBroadcastMetadata meta = createBroadcastMetadata(TEST_BROADCAST_ID); verifyAddSourceForGroup(meta); for (BassClientStateMachine sm : mStateMachines.values()) { if (sm.getDevice().equals(mCurrentDevice)) { injectRemoteSourceStateSourceAdded( sm, meta, TEST_SOURCE_ID, BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_IDLE, meta.isEncrypted() ? BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING : BluetoothLeBroadcastReceiveState .BIG_ENCRYPTION_STATE_NOT_ENCRYPTED, null); } else if (sm.getDevice().equals(mCurrentDevice1)) { injectRemoteSourceStateSourceAdded( sm, meta, TEST_SOURCE_ID + 1, BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_IDLE, meta.isEncrypted() ? BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING : BluetoothLeBroadcastReceiveState .BIG_ENCRYPTION_STATE_NOT_ENCRYPTED, null); } } prepareRemoteSourceState(meta, false); // Update broadcast source using other member of the same group BluetoothLeBroadcastMetadata metaUpdate = Loading Loading @@ -1587,31 +1623,7 @@ public class BassClientServiceTest { onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE); BluetoothLeBroadcastMetadata meta = createBroadcastMetadata(TEST_BROADCAST_ID); verifyAddSourceForGroup(meta); for (BassClientStateMachine sm : mStateMachines.values()) { if (sm.getDevice().equals(mCurrentDevice)) { injectRemoteSourceStateSourceAdded( sm, meta, TEST_SOURCE_ID, BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_IDLE, meta.isEncrypted() ? BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING : BluetoothLeBroadcastReceiveState .BIG_ENCRYPTION_STATE_NOT_ENCRYPTED, null); } else if (sm.getDevice().equals(mCurrentDevice1)) { injectRemoteSourceStateSourceAdded( sm, meta, TEST_SOURCE_ID + 1, BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_IDLE, meta.isEncrypted() ? BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING : BluetoothLeBroadcastReceiveState .BIG_ENCRYPTION_STATE_NOT_ENCRYPTED, null); } } prepareRemoteSourceState(meta, false); // Remove broadcast source using other member of the same group mBassClientService.removeSource(mCurrentDevice1, TEST_SOURCE_ID + 1); Loading Loading @@ -1732,32 +1744,7 @@ public class BassClientServiceTest { onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE); BluetoothLeBroadcastMetadata meta = createBroadcastMetadata(TEST_BROADCAST_ID); verifyAddSourceForGroup(meta); // Inject source added for (BassClientStateMachine sm : mStateMachines.values()) { if (sm.getDevice().equals(mCurrentDevice)) { injectRemoteSourceStateSourceAdded( sm, meta, TEST_SOURCE_ID, BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_IDLE, meta.isEncrypted() ? BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING : BluetoothLeBroadcastReceiveState .BIG_ENCRYPTION_STATE_NOT_ENCRYPTED, null); } else if (sm.getDevice().equals(mCurrentDevice1)) { injectRemoteSourceStateSourceAdded( sm, meta, TEST_SOURCE_ID + 1, BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_IDLE, meta.isEncrypted() ? BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING : BluetoothLeBroadcastReceiveState .BIG_ENCRYPTION_STATE_NOT_ENCRYPTED, null); } } prepareRemoteSourceState(meta, false); // Remove broadcast source mBassClientService.removeSource(mCurrentDevice, TEST_SOURCE_ID); Loading Loading @@ -2089,31 +2076,7 @@ public class BassClientServiceTest { // Prepare valid source for group BluetoothLeBroadcastMetadata meta = createBroadcastMetadata(TEST_BROADCAST_ID); verifyAddSourceForGroup(meta); for (BassClientStateMachine sm : mStateMachines.values()) { if (sm.getDevice().equals(mCurrentDevice)) { injectRemoteSourceStateSourceAdded( sm, meta, TEST_SOURCE_ID, BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_IDLE, meta.isEncrypted() ? BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING : BluetoothLeBroadcastReceiveState .BIG_ENCRYPTION_STATE_NOT_ENCRYPTED, null); } else if (sm.getDevice().equals(mCurrentDevice1)) { injectRemoteSourceStateSourceAdded( sm, meta, TEST_SOURCE_ID + 1, BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_IDLE, meta.isEncrypted() ? BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING : BluetoothLeBroadcastReceiveState .BIG_ENCRYPTION_STATE_NOT_ENCRYPTED, null); } } prepareRemoteSourceState(meta, false); // Verify errors are reported for the entire group mBassClientService.modifySource(mCurrentDevice, TEST_SOURCE_ID, null); Loading Loading @@ -3619,31 +3582,7 @@ public class BassClientServiceTest { onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE); BluetoothLeBroadcastMetadata meta = createBroadcastMetadata(TEST_BROADCAST_ID); verifyAddSourceForGroup(meta); for (BassClientStateMachine sm : mStateMachines.values()) { if (sm.getDevice().equals(mCurrentDevice)) { injectRemoteSourceStateSourceAdded( sm, meta, TEST_SOURCE_ID, BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_IDLE, meta.isEncrypted() ? BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING : BluetoothLeBroadcastReceiveState .BIG_ENCRYPTION_STATE_NOT_ENCRYPTED, null); } else if (sm.getDevice().equals(mCurrentDevice1)) { injectRemoteSourceStateSourceAdded( sm, meta, TEST_SOURCE_ID + 1, BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_IDLE, meta.isEncrypted() ? BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING : BluetoothLeBroadcastReceiveState .BIG_ENCRYPTION_STATE_NOT_ENCRYPTED, null); } } prepareRemoteSourceState(meta, false); if (Flags.leaudioBroadcastAssistantPeripheralEntrustment()) { for (BassClientStateMachine sm : mStateMachines.values()) { Loading Loading @@ -3805,58 +3744,9 @@ public class BassClientServiceTest { .getAllBroadcastMetadata(); verifyAddSourceForGroup(meta); for (BassClientStateMachine sm : mStateMachines.values()) { if (sm.getDevice().equals(mCurrentDevice)) { injectRemoteSourceStateSourceAdded( sm, meta, TEST_SOURCE_ID, BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_IDLE, meta.isEncrypted() ? BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING : BluetoothLeBroadcastReceiveState .BIG_ENCRYPTION_STATE_NOT_ENCRYPTED, null); prepareRemoteSourceState(meta, true); // Update receiver state injectRemoteSourceStateChanged( sm, meta, TEST_SOURCE_ID, BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_IDLE, meta.isEncrypted() ? BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING : BluetoothLeBroadcastReceiveState .BIG_ENCRYPTION_STATE_NOT_ENCRYPTED, null, (long) 0x00000001); verify(mLeAudioService).activeBroadcastAssistantNotification(eq(true)); } else if (sm.getDevice().equals(mCurrentDevice1)) { injectRemoteSourceStateSourceAdded( sm, meta, TEST_SOURCE_ID + 1, BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_IDLE, meta.isEncrypted() ? BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING : BluetoothLeBroadcastReceiveState .BIG_ENCRYPTION_STATE_NOT_ENCRYPTED, null); // Update receiver state injectRemoteSourceStateChanged( sm, meta, TEST_SOURCE_ID + 1, BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_IDLE, meta.isEncrypted() ? BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING : BluetoothLeBroadcastReceiveState .BIG_ENCRYPTION_STATE_NOT_ENCRYPTED, null, (long) 0x00000002); } } /* Unicast would like to stream */ mBassClientService.handleUnicastSourceStreamStatusChange( Loading Loading @@ -4002,6 +3892,102 @@ public class BassClientServiceTest { } } @Test public void testHandleUnicastSourceStreamStatusChange_MultipleRequests() { mSetFlagsRule.enableFlags(Flags.FLAG_LEAUDIO_BROADCAST_ASSISTANT_PERIPHERAL_ENTRUSTMENT); prepareConnectedDeviceGroup(); startSearchingForSources(); onScanResult(mSourceDevice, TEST_BROADCAST_ID); onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE); BluetoothLeBroadcastMetadata meta = createBroadcastMetadata(TEST_BROADCAST_ID); /* Fake external broadcast - no Broadcast Metadata from LE Audio service */ doReturn(new ArrayList<BluetoothLeBroadcastMetadata>()) .when(mLeAudioService) .getAllBroadcastMetadata(); verifyAddSourceForGroup(meta); prepareRemoteSourceState(meta, true); verify(mLeAudioService).activeBroadcastAssistantNotification(eq(true)); /* Unicast would like to stream */ mBassClientService.handleUnicastSourceStreamStatusChange( 3 /* STATUS_LOCAL_STREAM_REQUESTED_NO_CONTEXT_VALIDATE */); /* Imitate broadcast source stop, sink notify about loosing BIS sync */ for (BassClientStateMachine sm : mStateMachines.values()) { // Inject source removed ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class); verify(sm, atLeast(1)).sendMessage(messageCaptor.capture()); Optional<Message> msg = messageCaptor.getAllValues().stream() .filter(m -> m.what == BassClientStateMachine.REMOVE_BCAST_SOURCE) .findFirst(); assertThat(msg.isPresent()).isEqualTo(true); if (sm.getDevice().equals(mCurrentDevice)) { assertThat(msg.get().arg1).isEqualTo(TEST_SOURCE_ID); injectRemoteSourceStateRemoval(sm, TEST_SOURCE_ID); } else if (sm.getDevice().equals(mCurrentDevice1)) { assertThat(msg.get().arg1).isEqualTo(TEST_SOURCE_ID + 1); injectRemoteSourceStateRemoval(sm, TEST_SOURCE_ID + 1); } } assertThat(mStateMachines.size()).isEqualTo(2); for (BassClientStateMachine sm : mStateMachines.values()) { Mockito.clearInvocations(sm); } // Make another stream request with no context validate // and verify sm didn't get REMOVE_BCAST_SOURCE mBassClientService.handleUnicastSourceStreamStatusChange( 3 /* STATUS_LOCAL_STREAM_REQUESTED_NO_CONTEXT_VALIDATE */); // Make another stream request // and verify sinks to resume remain unchanged later mBassClientService.handleUnicastSourceStreamStatusChange( 0 /* STATUS_LOCAL_STREAM_REQUESTED */); 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.REMOVE_BCAST_SOURCE) && (m.arg1 == TEST_SOURCE_ID)) .findFirst() .orElse(null); assertThat(msg).isNull(); } /* Unicast finished streaming */ mBassClientService.handleUnicastSourceStreamStatusChange( 2 /* STATUS_LOCAL_STREAM_SUSPENDED */); // Verify all group members resume with the previous cached source 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 testIsAnyReceiverReceivingBroadcast() { prepareConnectedDeviceGroup(); Loading