Loading android/app/src/com/android/bluetooth/bass_client/BassClientService.java +8 −4 Original line number Diff line number Diff line Loading @@ -3017,14 +3017,18 @@ public class BassClientService extends ProfileService { stopBigMonitoring(metaData.getBroadcastId(), true); } if (metaData != null && stateMachine.isSyncedToTheSource(sourceId)) { if (stateMachine.isSyncedToTheSource(sourceId)) { sEventLogger.logd( TAG, "Remove Broadcast Source(Force lost PA sync): " + ("device: " + device) + (", sourceId: " + sourceId) + (", broadcastId: " + metaData.getBroadcastId()) + (", broadcastName: " + metaData.getBroadcastName())); + (", broadcastId: " + ((metaData == null) ? BassConstants.INVALID_BROADCAST_ID : metaData.getBroadcastId())) + (", broadcastName: " + ((metaData == null) ? "" : metaData.getBroadcastName()))); log("Force source to lost PA sync"); Message message = Loading @@ -3032,9 +3036,9 @@ public class BassClientService extends ProfileService { message.arg1 = sourceId; message.arg2 = BassConstants.PA_SYNC_DO_NOT_SYNC; /* Pending remove set. Remove source once not synchronized to PA */ /* MetaData can be null if source is from remote's receive state */ message.obj = metaData; stateMachine.sendMessage(message); continue; } Loading android/app/src/com/android/bluetooth/bass_client/BassClientStateMachine.java +21 −17 Original line number Diff line number Diff line Loading @@ -1875,7 +1875,7 @@ public class BassClientStateMachine extends StateMachine { return res; } private byte[] convertBroadcastMetadataToUpdateSourceByteArray( private byte[] convertToUpdateSourceByteArray( int sourceId, BluetoothLeBroadcastMetadata metaData, int paSync) { BluetoothLeBroadcastReceiveState existingState = getBroadcastReceiveStateForSourceId(sourceId); Loading @@ -1883,8 +1883,10 @@ public class BassClientStateMachine extends StateMachine { log("no existing SI for update source op"); return null; } List<BluetoothLeBroadcastSubgroup> subGroups = metaData.getSubgroups(); byte numSubGroups = (byte) subGroups.size(); int numSubGroups = (metaData != null) ? metaData.getSubgroups().size() : existingState.getNumSubgroups(); byte[] res = new byte[UPDATE_SOURCE_FIXED_LENGTH + numSubGroups * 5]; int offset = 0; // Opcode Loading @@ -1904,23 +1906,22 @@ public class BassClientStateMachine extends StateMachine { res[offset++] = (byte) 0xFF; res[offset++] = (byte) 0xFF; // Num_Subgroups res[offset++] = numSubGroups; res[offset++] = (byte) numSubGroups; for (BluetoothLeBroadcastSubgroup subGroup : subGroups) { int bisIndexValue; for (int i = 0; i < numSubGroups; i++) { int bisIndexValue = existingState.getBisSyncState().get(i).intValue(); if (paSync == BassConstants.PA_SYNC_DO_NOT_SYNC) { bisIndexValue = 0; } else if (paSync == BassConstants.PA_SYNC_PAST_AVAILABLE || paSync == BassConstants.PA_SYNC_PAST_NOT_AVAILABLE) { bisIndexValue = getBisSyncFromChannelPreference(subGroup.getChannels()); } else if (metaData != null && (paSync == BassConstants.PA_SYNC_PAST_AVAILABLE || paSync == BassConstants.PA_SYNC_PAST_NOT_AVAILABLE)) { bisIndexValue = getBisSyncFromChannelPreference( metaData.getSubgroups().get(i).getChannels()); // Let sink decide to which BIS sync if there is no channel preference if (bisIndexValue == 0) { bisIndexValue = 0xFFFFFFFF; } } else { bisIndexValue = existingState.getBisSyncState().get(subGroups.indexOf(subGroup)).intValue(); } log("UPDATE_BCAST_SOURCE: bisIndexValue : " + bisIndexValue); // BIS_Sync Loading Loading @@ -2235,9 +2236,9 @@ public class BassClientStateMachine extends StateMachine { int sourceId = message.arg1; int paSync = message.arg2; log("Updating Broadcast source: " + metaData); // Convert the source from either metadata or remote receive state byte[] updateSourceInfo = convertBroadcastMetadataToUpdateSourceByteArray( sourceId, metaData, paSync); convertToUpdateSourceByteArray(sourceId, metaData, paSync); if (updateSourceInfo == null) { Log.e(TAG, "update source: source Info is NULL"); break; Loading @@ -2249,7 +2250,9 @@ public class BassClientStateMachine extends StateMachine { if (paSync == BassConstants.PA_SYNC_DO_NOT_SYNC) { setPendingRemove(sourceId, true); } if (metaData.isEncrypted() && (metaData.getBroadcastCode() != null)) { if (metaData != null && metaData.isEncrypted() && metaData.getBroadcastCode() != null) { mSetBroadcastCodePending = true; } mPendingMetadata = metaData; Loading @@ -2258,9 +2261,10 @@ public class BassClientStateMachine extends StateMachine { GATT_TXN_TIMEOUT, UPDATE_BCAST_SOURCE, BassConstants.GATT_TXN_TIMEOUT_MS); // convertToUpdateSourceByteArray ensures receive state valid for sourceId sendMessageDelayed( CANCEL_PENDING_SOURCE_OPERATION, metaData.getBroadcastId(), getBroadcastReceiveStateForSourceId(sourceId).getBroadcastId(), BassConstants.SOURCE_OPERATION_TIMEOUT_MS); } else { Log.e(TAG, "UPDATE_BCAST_SOURCE: no Bluetooth Gatt handle, Fatal"); Loading android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientServiceTest.java +51 −0 Original line number Diff line number Diff line Loading @@ -1868,6 +1868,57 @@ public class BassClientServiceTest { } } /** * Test whether service.removeSource() does send modify source if source is from remote receive * state. In this case, assistant should be able to remove source which was not managed by BASS * service (external manager/no source metadata) */ @Test public void testRemoveSourceForGroupAndTriggerModifySourceWithoutMetadata() { prepareConnectedDeviceGroup(); startSearchingForSources(); onScanResult(mSourceDevice, TEST_BROADCAST_ID); onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE); BluetoothLeBroadcastMetadata meta = createBroadcastMetadata(TEST_BROADCAST_ID); for (BassClientStateMachine sm : mStateMachines.values()) { injectRemoteSourceStateSourceAdded( sm, meta, TEST_SOURCE_ID, BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCHRONIZED, meta.isEncrypted() ? BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING : BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_NOT_ENCRYPTED, null); // no current broadcast metadata for external broadcast source doReturn(null).when(sm).getCurrentBroadcastMetadata(eq(TEST_SOURCE_ID)); doReturn(true).when(sm).isSyncedToTheSource(eq(TEST_SOURCE_ID)); } for (BassClientStateMachine sm : mStateMachines.values()) { ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class); mBassClientService.removeSource(sm.getDevice(), TEST_SOURCE_ID); // Verify device get update source verify(sm, atLeast(1)).sendMessage(messageCaptor.capture()); Optional<Message> msg = messageCaptor.getAllValues().stream() .filter(m -> m.what == BassClientStateMachine.UPDATE_BCAST_SOURCE) .findFirst(); assertThat(msg.isPresent()).isEqualTo(true); assertThat(msg.get().arg1).isEqualTo(TEST_SOURCE_ID); assertThat(msg.get().arg2).isEqualTo(BassConstants.PA_SYNC_DO_NOT_SYNC); // Verify metadata is null assertThat(msg.get().obj).isEqualTo(null); } for (BassClientStateMachine sm : mStateMachines.values()) { injectRemoteSourceStateRemoval(sm, TEST_SOURCE_ID); } } /** Test whether the group operation flag is set on addSource() and removed on removeSource */ @Test public void testGroupStickyFlagSetUnset() { Loading android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientStateMachineTest.java +34 −18 Original line number Diff line number Diff line Loading @@ -1583,23 +1583,6 @@ public class BassClientStateMachineTest { 0x00, // broadcastIdBytes (byte) BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_IDLE, (byte) BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_CODE_REQUIRED, // 16 bytes badBroadcastCode 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, // numSubGroups // SubGroup #1 0x00, Loading Loading @@ -2405,7 +2388,7 @@ public class BassClientStateMachineTest { Utils.getByteAddress(mSourceTestDevice)[1], Utils.getByteAddress(mSourceTestDevice)[0], // sourceAddress 0x00, // sourceAdvSid 0x00, (byte) (TEST_BROADCAST_ID & 0xFF), 0x00, 0x00, // broadcastIdBytes (byte) paSync, Loading Loading @@ -2648,6 +2631,39 @@ public class BassClientStateMachineTest { verify(callbacks).notifyBassStateReady(eq(mTestDevice)); } @Test public void updateBroadcastSource_withoutMetadata() { int sourceId = 1; int paSync = BassConstants.PA_SYNC_DO_NOT_SYNC; prepareInitialReceiveStateForGatt(); generateBroadcastReceiveStatesAndVerify( mSourceTestDevice, TEST_SOURCE_ID, BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCHRONIZED, BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING, 0x1L); BassClientStateMachine.BluetoothGattTestableWrapper btGatt = Mockito.mock(BassClientStateMachine.BluetoothGattTestableWrapper.class); mBassClientStateMachine.mBluetoothGatt = btGatt; BluetoothGattCharacteristic scanControlPoint = Mockito.mock(BluetoothGattCharacteristic.class); mBassClientStateMachine.mBroadcastScanControlPoint = scanControlPoint; mBassClientStateMachine.mPendingOperation = 0; mBassClientStateMachine.mPendingSourceId = 0; mBassClientStateMachine.mPendingMetadata = null; // update source without metadata sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage(UPDATE_BCAST_SOURCE, sourceId, paSync, null), BassClientStateMachine.ConnectedProcessing.class); assertThat(mBassClientStateMachine.mPendingOperation).isEqualTo(UPDATE_BCAST_SOURCE); assertThat(mBassClientStateMachine.mPendingSourceId).isEqualTo(sourceId); } private void initToConnectingState() { allowConnection(true); allowConnectGatt(true); Loading Loading
android/app/src/com/android/bluetooth/bass_client/BassClientService.java +8 −4 Original line number Diff line number Diff line Loading @@ -3017,14 +3017,18 @@ public class BassClientService extends ProfileService { stopBigMonitoring(metaData.getBroadcastId(), true); } if (metaData != null && stateMachine.isSyncedToTheSource(sourceId)) { if (stateMachine.isSyncedToTheSource(sourceId)) { sEventLogger.logd( TAG, "Remove Broadcast Source(Force lost PA sync): " + ("device: " + device) + (", sourceId: " + sourceId) + (", broadcastId: " + metaData.getBroadcastId()) + (", broadcastName: " + metaData.getBroadcastName())); + (", broadcastId: " + ((metaData == null) ? BassConstants.INVALID_BROADCAST_ID : metaData.getBroadcastId())) + (", broadcastName: " + ((metaData == null) ? "" : metaData.getBroadcastName()))); log("Force source to lost PA sync"); Message message = Loading @@ -3032,9 +3036,9 @@ public class BassClientService extends ProfileService { message.arg1 = sourceId; message.arg2 = BassConstants.PA_SYNC_DO_NOT_SYNC; /* Pending remove set. Remove source once not synchronized to PA */ /* MetaData can be null if source is from remote's receive state */ message.obj = metaData; stateMachine.sendMessage(message); continue; } Loading
android/app/src/com/android/bluetooth/bass_client/BassClientStateMachine.java +21 −17 Original line number Diff line number Diff line Loading @@ -1875,7 +1875,7 @@ public class BassClientStateMachine extends StateMachine { return res; } private byte[] convertBroadcastMetadataToUpdateSourceByteArray( private byte[] convertToUpdateSourceByteArray( int sourceId, BluetoothLeBroadcastMetadata metaData, int paSync) { BluetoothLeBroadcastReceiveState existingState = getBroadcastReceiveStateForSourceId(sourceId); Loading @@ -1883,8 +1883,10 @@ public class BassClientStateMachine extends StateMachine { log("no existing SI for update source op"); return null; } List<BluetoothLeBroadcastSubgroup> subGroups = metaData.getSubgroups(); byte numSubGroups = (byte) subGroups.size(); int numSubGroups = (metaData != null) ? metaData.getSubgroups().size() : existingState.getNumSubgroups(); byte[] res = new byte[UPDATE_SOURCE_FIXED_LENGTH + numSubGroups * 5]; int offset = 0; // Opcode Loading @@ -1904,23 +1906,22 @@ public class BassClientStateMachine extends StateMachine { res[offset++] = (byte) 0xFF; res[offset++] = (byte) 0xFF; // Num_Subgroups res[offset++] = numSubGroups; res[offset++] = (byte) numSubGroups; for (BluetoothLeBroadcastSubgroup subGroup : subGroups) { int bisIndexValue; for (int i = 0; i < numSubGroups; i++) { int bisIndexValue = existingState.getBisSyncState().get(i).intValue(); if (paSync == BassConstants.PA_SYNC_DO_NOT_SYNC) { bisIndexValue = 0; } else if (paSync == BassConstants.PA_SYNC_PAST_AVAILABLE || paSync == BassConstants.PA_SYNC_PAST_NOT_AVAILABLE) { bisIndexValue = getBisSyncFromChannelPreference(subGroup.getChannels()); } else if (metaData != null && (paSync == BassConstants.PA_SYNC_PAST_AVAILABLE || paSync == BassConstants.PA_SYNC_PAST_NOT_AVAILABLE)) { bisIndexValue = getBisSyncFromChannelPreference( metaData.getSubgroups().get(i).getChannels()); // Let sink decide to which BIS sync if there is no channel preference if (bisIndexValue == 0) { bisIndexValue = 0xFFFFFFFF; } } else { bisIndexValue = existingState.getBisSyncState().get(subGroups.indexOf(subGroup)).intValue(); } log("UPDATE_BCAST_SOURCE: bisIndexValue : " + bisIndexValue); // BIS_Sync Loading Loading @@ -2235,9 +2236,9 @@ public class BassClientStateMachine extends StateMachine { int sourceId = message.arg1; int paSync = message.arg2; log("Updating Broadcast source: " + metaData); // Convert the source from either metadata or remote receive state byte[] updateSourceInfo = convertBroadcastMetadataToUpdateSourceByteArray( sourceId, metaData, paSync); convertToUpdateSourceByteArray(sourceId, metaData, paSync); if (updateSourceInfo == null) { Log.e(TAG, "update source: source Info is NULL"); break; Loading @@ -2249,7 +2250,9 @@ public class BassClientStateMachine extends StateMachine { if (paSync == BassConstants.PA_SYNC_DO_NOT_SYNC) { setPendingRemove(sourceId, true); } if (metaData.isEncrypted() && (metaData.getBroadcastCode() != null)) { if (metaData != null && metaData.isEncrypted() && metaData.getBroadcastCode() != null) { mSetBroadcastCodePending = true; } mPendingMetadata = metaData; Loading @@ -2258,9 +2261,10 @@ public class BassClientStateMachine extends StateMachine { GATT_TXN_TIMEOUT, UPDATE_BCAST_SOURCE, BassConstants.GATT_TXN_TIMEOUT_MS); // convertToUpdateSourceByteArray ensures receive state valid for sourceId sendMessageDelayed( CANCEL_PENDING_SOURCE_OPERATION, metaData.getBroadcastId(), getBroadcastReceiveStateForSourceId(sourceId).getBroadcastId(), BassConstants.SOURCE_OPERATION_TIMEOUT_MS); } else { Log.e(TAG, "UPDATE_BCAST_SOURCE: no Bluetooth Gatt handle, Fatal"); Loading
android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientServiceTest.java +51 −0 Original line number Diff line number Diff line Loading @@ -1868,6 +1868,57 @@ public class BassClientServiceTest { } } /** * Test whether service.removeSource() does send modify source if source is from remote receive * state. In this case, assistant should be able to remove source which was not managed by BASS * service (external manager/no source metadata) */ @Test public void testRemoveSourceForGroupAndTriggerModifySourceWithoutMetadata() { prepareConnectedDeviceGroup(); startSearchingForSources(); onScanResult(mSourceDevice, TEST_BROADCAST_ID); onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE); BluetoothLeBroadcastMetadata meta = createBroadcastMetadata(TEST_BROADCAST_ID); for (BassClientStateMachine sm : mStateMachines.values()) { injectRemoteSourceStateSourceAdded( sm, meta, TEST_SOURCE_ID, BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCHRONIZED, meta.isEncrypted() ? BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING : BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_NOT_ENCRYPTED, null); // no current broadcast metadata for external broadcast source doReturn(null).when(sm).getCurrentBroadcastMetadata(eq(TEST_SOURCE_ID)); doReturn(true).when(sm).isSyncedToTheSource(eq(TEST_SOURCE_ID)); } for (BassClientStateMachine sm : mStateMachines.values()) { ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class); mBassClientService.removeSource(sm.getDevice(), TEST_SOURCE_ID); // Verify device get update source verify(sm, atLeast(1)).sendMessage(messageCaptor.capture()); Optional<Message> msg = messageCaptor.getAllValues().stream() .filter(m -> m.what == BassClientStateMachine.UPDATE_BCAST_SOURCE) .findFirst(); assertThat(msg.isPresent()).isEqualTo(true); assertThat(msg.get().arg1).isEqualTo(TEST_SOURCE_ID); assertThat(msg.get().arg2).isEqualTo(BassConstants.PA_SYNC_DO_NOT_SYNC); // Verify metadata is null assertThat(msg.get().obj).isEqualTo(null); } for (BassClientStateMachine sm : mStateMachines.values()) { injectRemoteSourceStateRemoval(sm, TEST_SOURCE_ID); } } /** Test whether the group operation flag is set on addSource() and removed on removeSource */ @Test public void testGroupStickyFlagSetUnset() { Loading
android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientStateMachineTest.java +34 −18 Original line number Diff line number Diff line Loading @@ -1583,23 +1583,6 @@ public class BassClientStateMachineTest { 0x00, // broadcastIdBytes (byte) BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_IDLE, (byte) BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_CODE_REQUIRED, // 16 bytes badBroadcastCode 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, // numSubGroups // SubGroup #1 0x00, Loading Loading @@ -2405,7 +2388,7 @@ public class BassClientStateMachineTest { Utils.getByteAddress(mSourceTestDevice)[1], Utils.getByteAddress(mSourceTestDevice)[0], // sourceAddress 0x00, // sourceAdvSid 0x00, (byte) (TEST_BROADCAST_ID & 0xFF), 0x00, 0x00, // broadcastIdBytes (byte) paSync, Loading Loading @@ -2648,6 +2631,39 @@ public class BassClientStateMachineTest { verify(callbacks).notifyBassStateReady(eq(mTestDevice)); } @Test public void updateBroadcastSource_withoutMetadata() { int sourceId = 1; int paSync = BassConstants.PA_SYNC_DO_NOT_SYNC; prepareInitialReceiveStateForGatt(); generateBroadcastReceiveStatesAndVerify( mSourceTestDevice, TEST_SOURCE_ID, BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCHRONIZED, BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING, 0x1L); BassClientStateMachine.BluetoothGattTestableWrapper btGatt = Mockito.mock(BassClientStateMachine.BluetoothGattTestableWrapper.class); mBassClientStateMachine.mBluetoothGatt = btGatt; BluetoothGattCharacteristic scanControlPoint = Mockito.mock(BluetoothGattCharacteristic.class); mBassClientStateMachine.mBroadcastScanControlPoint = scanControlPoint; mBassClientStateMachine.mPendingOperation = 0; mBassClientStateMachine.mPendingSourceId = 0; mBassClientStateMachine.mPendingMetadata = null; // update source without metadata sendMessageAndVerifyTransition( mBassClientStateMachine.obtainMessage(UPDATE_BCAST_SOURCE, sourceId, paSync, null), BassClientStateMachine.ConnectedProcessing.class); assertThat(mBassClientStateMachine.mPendingOperation).isEqualTo(UPDATE_BCAST_SOURCE); assertThat(mBassClientStateMachine.mPendingSourceId).isEqualTo(sourceId); } private void initToConnectingState() { allowConnection(true); allowConnectGatt(true); Loading