Loading android/app/src/com/android/bluetooth/bass_client/BassClientService.java +129 −39 Original line number Diff line number Diff line Loading @@ -169,6 +169,8 @@ public class BassClientService extends ProfileService { private final Map<BluetoothDevice, BluetoothLeBroadcastMetadata> mBroadcastMetadataMap = new ConcurrentHashMap<>(); private final HashSet<BluetoothDevice> mPausedBroadcastSinks = new HashSet<>(); private final Map<BluetoothDevice, Pair<Integer, Integer>> mSinksWaitingForPast = new HashMap<>(); private final Map<Integer, PauseType> mPausedBroadcastIds = new HashMap<>(); private final Deque<AddSourceData> mPendingAddSources = new ArrayDeque<>(); private final Map<Integer, HashSet<BluetoothDevice>> mLocalBroadcastReceivers = Loading Loading @@ -276,13 +278,6 @@ public class BassClientService extends ProfileService { { log("MESSAGE_BIG_MONITOR_TIMEOUT"); stopSourceReceivers(broadcastId); synchronized (mSearchScanCallbackLock) { // when searching is stopped then clear all sync // data if (mSearchScanCallback == null) { clearAllSyncData(); } } break; } default: Loading Loading @@ -1107,6 +1102,24 @@ public class BassClientService extends ProfileService { } } void syncRequestForPast(BluetoothDevice sink, int broadcastId, int sourceId) { log( "syncRequestForPast sink: " + sink + ", broadcastId: " + broadcastId + ", sourceId: " + sourceId); if (!leaudioBroadcastResyncHelper()) { return; } synchronized (mSinksWaitingForPast) { mSinksWaitingForPast.put(sink, new Pair<Integer, Integer>(broadcastId, sourceId)); } addSelectSourceRequest(broadcastId, true); } private void localNotifyReceiveStateChanged( BluetoothDevice sink, BluetoothLeBroadcastReceiveState receiveState) { int broadcastId = receiveState.getBroadcastId(); Loading @@ -1114,24 +1127,14 @@ public class BassClientService extends ProfileService { && !isLocalBroadcast(receiveState) && !isEmptyBluetoothDevice(receiveState.getSourceDevice()) && !isHostPauseType(broadcastId)) { boolean isReadyToAutoResync = false; if (receiveState.getPaSyncState() == BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCHRONIZED) { isReadyToAutoResync = true; } else { for (int i = 0; i < receiveState.getNumSubgroups(); i++) { Long syncState = receiveState.getBisSyncState().get(i); /* Synced to BIS */ if (syncState != BassConstants.BIS_SYNC_NOT_SYNC_TO_BIS && syncState != BassConstants.BIS_SYNC_FAILED_SYNC_TO_BIG) { isReadyToAutoResync = true; break; } } } if (isReadyToAutoResync) { if (isReceiverActive(receiveState) || receiveState.getPaSyncState() == BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCINFO_REQUEST) { mPausedBroadcastSinks.remove(sink); if (isAllReceiversActive(broadcastId) && mPausedBroadcastSinks.isEmpty()) { stopBigMonitoring(broadcastId, false); } } else if (!mPausedBroadcastIds.containsKey(broadcastId)) { if (mCachedBroadcasts.containsKey(broadcastId)) { addSelectSourceRequest(broadcastId, true); Loading @@ -1144,6 +1147,9 @@ public class BassClientService extends ProfileService { } } } else if (isEmptyBluetoothDevice(receiveState.getSourceDevice())) { synchronized (mSinksWaitingForPast) { mSinksWaitingForPast.remove(sink); } checkAndStopBigMonitoring(); } Loading Loading @@ -1584,8 +1590,9 @@ public class BassClientService extends ProfileService { if (toState == BluetoothProfile.STATE_DISCONNECTED) { mPendingGroupOp.remove(device); mPausedBroadcastSinks.remove(device); checkAndStopBigMonitoring(); synchronized (mSinksWaitingForPast) { mSinksWaitingForPast.remove(device); } int bondState = mAdapterService.getBondState(device); if (bondState == BluetoothDevice.BOND_NONE) { Loading @@ -1593,6 +1600,20 @@ public class BassClientService extends ProfileService { removeStateMachine(device); } checkAndStopBigMonitoring(); if (getConnectedDevices().isEmpty() || (mPausedBroadcastSinks.isEmpty() && mSinksWaitingForPast.isEmpty() && !isAnyConnectedDeviceSwitchingSource())) { synchronized (mSearchScanCallbackLock) { // when searching is stopped then clear all sync data if (mSearchScanCallback == null) { clearAllSyncData(); } } } /* Restore allowed context mask for unicast in case if last connected broadcast * delegator device which has external source disconnectes. */ Loading Loading @@ -2167,6 +2188,41 @@ public class BassClientService extends ProfileService { } mBisDiscoveryCounterMap.put(syncHandle, MAX_BIS_DISCOVERY_TRIES_NUM); synchronized (mSinksWaitingForPast) { Iterator<Map.Entry<BluetoothDevice, Pair<Integer, Integer>>> iterator = mSinksWaitingForPast.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry<BluetoothDevice, Pair<Integer, Integer>> entry = iterator.next(); BluetoothDevice sinkDevice = entry.getKey(); int broadcastIdForPast = entry.getValue().first; if (broadcastId == broadcastIdForPast) { int sourceId = entry.getValue().second; synchronized (mStateMachines) { BassClientStateMachine sm = getOrCreateStateMachine(sinkDevice); Message message = sm.obtainMessage( BassClientStateMachine.INITIATE_PA_SYNC_TRANSFER); message.arg1 = syncHandle; message.arg2 = sourceId; sm.sendMessage(message); } synchronized (mPendingSourcesToAdd) { Iterator<AddSourceData> addIterator = mPendingSourcesToAdd.iterator(); while (addIterator.hasNext()) { AddSourceData pendingSourcesToAdd = addIterator.next(); if (pendingSourcesToAdd.mSourceMetadata.getBroadcastId() == broadcastId && pendingSourcesToAdd.mSink.equals(sinkDevice)) { addIterator.remove(); } } } iterator.remove(); } } } synchronized (mPendingSourcesToAdd) { Iterator<AddSourceData> iterator = mPendingSourcesToAdd.iterator(); while (iterator.hasNext()) { Loading Loading @@ -3698,14 +3754,23 @@ public class BassClientService extends ProfileService { .filter(e -> e.getBroadcastId() == metadata.getBroadcastId()) .findAny(); if (leaudioBroadcastResyncHelper() && receiveState.isPresent() && (receiveState.get().getPaSyncState() == BluetoothLeBroadcastReceiveState .PA_SYNC_STATE_SYNCINFO_REQUEST || receiveState.get().getPaSyncState() == BluetoothLeBroadcastReceiveState .PA_SYNC_STATE_SYNCHRONIZED)) { iterator.remove(); continue; } List<Integer> activeSyncedSrc = getActiveSyncedSources(); if (receiveState.isPresent() && (!leaudioBroadcastResyncHelper() || isLocalBroadcast(metadata) || receiveState.get().getPaSyncState() == BluetoothLeBroadcastReceiveState .PA_SYNC_STATE_SYNCHRONIZED || activeSyncedSrc.contains( getSyncHandleForBroadcastId(metadata.getBroadcastId())))) { int sourceId = receiveState.get().getSourceId(); Loading Loading @@ -3862,24 +3927,49 @@ public class BassClientService extends ProfileService { return false; } private Set<Integer> getExternalBroadcastsActiveOnSinks() { HashSet<Integer> syncedBroadcasts = new HashSet<>(); for (BluetoothDevice device : getConnectedDevices()) { for (BluetoothLeBroadcastReceiveState receiveState : getAllSources(device)) { if (!isLocalBroadcast(receiveState)) { private boolean isReceiverActive(BluetoothLeBroadcastReceiveState receiveState) { if (receiveState.getPaSyncState() == BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCHRONIZED) { return true; } else { for (int i = 0; i < receiveState.getNumSubgroups(); i++) { Long syncState = receiveState.getBisSyncState().get(i); /* Synced to BIS */ if (syncState != BassConstants.BIS_SYNC_NOT_SYNC_TO_BIS && syncState != BassConstants.BIS_SYNC_FAILED_SYNC_TO_BIG) { return true; } } } return false; } private Set<Integer> getExternalBroadcastsActiveOnSinks() { HashSet<Integer> syncedBroadcasts = new HashSet<>(); for (BluetoothDevice device : getConnectedDevices()) { for (BluetoothLeBroadcastReceiveState receiveState : getAllSources(device)) { if (isLocalBroadcast(receiveState)) { continue; } if (isReceiverActive(receiveState)) { syncedBroadcasts.add(receiveState.getBroadcastId()); log("getExternalBroadcastsActiveOnSinks: " + receiveState); } } } return syncedBroadcasts; } private boolean isAllReceiversActive(int broadcastId) { for (BluetoothDevice device : getConnectedDevices()) { for (BluetoothLeBroadcastReceiveState receiveState : getAllSources(device)) { if (receiveState.getBroadcastId() == broadcastId && !isReceiverActive(receiveState)) { return false; } return syncedBroadcasts; } } return true; } /** Get sink devices synced to the broadcasts */ Loading android/app/src/com/android/bluetooth/bass_client/BassClientStateMachine.java +78 −59 Original line number Diff line number Diff line Loading @@ -84,8 +84,7 @@ import java.util.Scanner; import java.util.UUID; import java.util.stream.IntStream; @VisibleForTesting public class BassClientStateMachine extends StateMachine { class BassClientStateMachine extends StateMachine { private static final String TAG = "BassClientStateMachine"; @VisibleForTesting static final byte[] REMOTE_SCAN_STOP = {00}; @VisibleForTesting static final byte[] REMOTE_SCAN_START = {01}; Loading Loading @@ -113,6 +112,7 @@ public class BassClientStateMachine extends StateMachine { static final int REACHED_MAX_SOURCE_LIMIT = 16; static final int SWITCH_BCAST_SOURCE = 17; static final int CANCEL_PENDING_SOURCE_OPERATION = 18; static final int INITIATE_PA_SYNC_TRANSFER = 19; // NOTE: the value is not "final" - it is modified in the unit tests @VisibleForTesting private int mConnectTimeoutMs; Loading Loading @@ -816,43 +816,11 @@ public class BassClientStateMachine extends StateMachine { int state = recvState.getPaSyncState(); if (state == BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCINFO_REQUEST) { log("Initiate PAST procedure"); PeriodicAdvertisementResult result = mService.getPeriodicAdvertisementResult( recvState.getSourceDevice(), recvState.getBroadcastId()); if (result != null) { int syncHandle = result.getSyncHandle(); log("processPASyncState: syncHandle " + result.getSyncHandle()); if (syncHandle != BassConstants.INVALID_SYNC_HANDLE) { serviceData = 0x000000FF & recvState.getSourceId(); serviceData = serviceData << 8; // advA matches EXT_ADV_ADDRESS // also matches source address (as we would have written) serviceData = serviceData & (~BassConstants.ADV_ADDRESS_DONT_MATCHES_EXT_ADV_ADDRESS); serviceData = serviceData & (~BassConstants.ADV_ADDRESS_DONT_MATCHES_SOURCE_ADV_ADDRESS); log( "Initiate PAST for: " + mDevice + ", syncHandle: " + syncHandle + "serviceData" + serviceData); BluetoothMethodProxy.getInstance() .periodicAdvertisingManagerTransferSync( BassClientPeriodicAdvertisingManager .getPeriodicAdvertisingManager(), mDevice, serviceData, syncHandle); } } else { BluetoothLeBroadcastMetadata currentMetadata = getCurrentBroadcastMetadata(recvState.getSourceId()); int sourceId = recvState.getSourceId(); BluetoothLeBroadcastMetadata currentMetadata = getCurrentBroadcastMetadata(sourceId); if (mService.isLocalBroadcast(currentMetadata)) { int advHandle = currentMetadata.getSourceAdvertisingSid(); serviceData = 0x000000FF & recvState.getSourceId(); serviceData = 0x000000FF & sourceId; serviceData = serviceData << 8; // Address we set in the Source Address can differ from the address in the air serviceData = Loading @@ -873,9 +841,52 @@ public class BassClientStateMachine extends StateMachine { advHandle, mLocalPeriodicAdvCallback); } else { Log.e(TAG, "There is no valid sync handle for this Source"); int broadcastId = recvState.getBroadcastId(); PeriodicAdvertisementResult result = mService.getPeriodicAdvertisementResult( recvState.getSourceDevice(), broadcastId); if (result != null) { int syncHandle = result.getSyncHandle(); if (syncHandle != BassConstants.INVALID_SYNC_HANDLE) { initiatePaSyncTransfer(syncHandle, sourceId); return; } } mService.syncRequestForPast(mDevice, broadcastId, sourceId); } } } private void initiatePaSyncTransfer(int syncHandle, int sourceId) { if (syncHandle != BassConstants.INVALID_SYNC_HANDLE && sourceId != BassConstants.INVALID_SOURCE_ID) { int serviceData = 0x000000FF & sourceId; serviceData = serviceData << 8; // advA matches EXT_ADV_ADDRESS // also matches source address (as we would have written) serviceData = serviceData & (~BassConstants.ADV_ADDRESS_DONT_MATCHES_EXT_ADV_ADDRESS); serviceData = serviceData & (~BassConstants.ADV_ADDRESS_DONT_MATCHES_SOURCE_ADV_ADDRESS); log( "Initiate PAST for: " + mDevice + ", syncHandle: " + syncHandle + ", serviceData: " + serviceData); BluetoothMethodProxy.getInstance() .periodicAdvertisingManagerTransferSync( BassClientPeriodicAdvertisingManager.getPeriodicAdvertisingManager(), mDevice, serviceData, syncHandle); } else { Log.e( TAG, "Invalid syncHandle or sourceId for PAST, syncHandle: " + syncHandle + ", sourceId: " + sourceId); } } Loading Loading @@ -2350,6 +2361,11 @@ public class BassClientStateMachine extends StateMachine { int broadcastId = message.arg1; cancelPendingSourceOperation(broadcastId); break; case INITIATE_PA_SYNC_TRANSFER: int syncHandle = message.arg1; int sourceIdForPast = message.arg2; initiatePaSyncTransfer(syncHandle, sourceIdForPast); break; default: log("CONNECTED: not handled message:" + message.what); return NOT_HANDLED; Loading Loading @@ -2539,6 +2555,7 @@ public class BassClientStateMachine extends StateMachine { case REACHED_MAX_SOURCE_LIMIT: case SWITCH_BCAST_SOURCE: case PSYNC_ACTIVE_TIMEOUT: case INITIATE_PA_SYNC_TRANSFER: log( "defer the message: " + messageWhatToString(message.what) Loading Loading @@ -2646,6 +2663,8 @@ public class BassClientStateMachine extends StateMachine { return "CONNECT_TIMEOUT"; case CANCEL_PENDING_SOURCE_OPERATION: return "CANCEL_PENDING_SOURCE_OPERATION"; case INITIATE_PA_SYNC_TRANSFER: return "INITIATE_PA_SYNC_TRANSFER"; default: break; } Loading android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientServiceTest.java +177 −4 File changed.Preview size limit exceeded, changes collapsed. Show changes android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientStateMachineTest.java +38 −1 Original line number Diff line number Diff line Loading @@ -32,6 +32,7 @@ import static com.android.bluetooth.bass_client.BassClientStateMachine.CONNECT_T import static com.android.bluetooth.bass_client.BassClientStateMachine.DISCONNECT; import static com.android.bluetooth.bass_client.BassClientStateMachine.GATT_TXN_PROCESSED; import static com.android.bluetooth.bass_client.BassClientStateMachine.GATT_TXN_TIMEOUT; import static com.android.bluetooth.bass_client.BassClientStateMachine.INITIATE_PA_SYNC_TRANSFER; import static com.android.bluetooth.bass_client.BassClientStateMachine.PSYNC_ACTIVE_TIMEOUT; import static com.android.bluetooth.bass_client.BassClientStateMachine.REACHED_MAX_SOURCE_LIMIT; import static com.android.bluetooth.bass_client.BassClientStateMachine.READ_BASS_CHARACTERISTICS; Loading Loading @@ -894,12 +895,23 @@ public class BassClientStateMachineTest { when(characteristic.getValue()).thenReturn(value); when(mBassClientService.getPeriodicAdvertisementResult(any(), anyInt())) .thenReturn(paResult); when(paResult.getSyncHandle()).thenReturn(100); int syncHandle = 100; when(paResult.getSyncHandle()).thenReturn(syncHandle); Mockito.clearInvocations(callbacks); cb.onCharacteristicRead(null, characteristic, GATT_SUCCESS); TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); int serviceData = 0x000000FF & sourceId; serviceData = serviceData << 8; // advA matches EXT_ADV_ADDRESS // also matches source address (as we would have written) serviceData = serviceData & (~BassConstants.ADV_ADDRESS_DONT_MATCHES_EXT_ADV_ADDRESS); serviceData = serviceData & (~BassConstants.ADV_ADDRESS_DONT_MATCHES_SOURCE_ADV_ADDRESS); verify(mMethodProxy) .periodicAdvertisingManagerTransferSync( any(), any(), eq(serviceData), eq(syncHandle)); verify(callbacks) .notifyReceiveStateChanged(any(), eq(sourceId), receiveStateCaptor.capture()); Assert.assertEquals(receiveStateCaptor.getValue().getSourceDevice(), mSourceTestDevice); Loading Loading @@ -1706,6 +1718,26 @@ public class BassClientStateMachineTest { assertThat(mBassClientStateMachine.mPendingSourceId).isEqualTo(sid); } @Test public void sendInitiatePaSyncTransferMessage_inConnectedState() { initToConnectedState(); int syncHandle = 1234; int sourceId = 4321; mBassClientStateMachine.sendMessage(INITIATE_PA_SYNC_TRANSFER, syncHandle, sourceId); TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); int serviceData = 0x000000FF & sourceId; serviceData = serviceData << 8; // advA matches EXT_ADV_ADDRESS // also matches source address (as we would have written) serviceData = serviceData & (~BassConstants.ADV_ADDRESS_DONT_MATCHES_EXT_ADV_ADDRESS); serviceData = serviceData & (~BassConstants.ADV_ADDRESS_DONT_MATCHES_SOURCE_ADV_ADDRESS); verify(mMethodProxy) .periodicAdvertisingManagerTransferSync( any(), any(), eq(serviceData), eq(syncHandle)); } @Test public void sendConnectMessage_inConnectedProcessingState_doesNotChangeState() { initToConnectedProcessingState(); Loading Loading @@ -1906,6 +1938,11 @@ public class BassClientStateMachineTest { mBassClientStateMachine.sendMessage(SWITCH_BCAST_SOURCE); TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); assertThat(mBassClientStateMachine.hasDeferredMessagesSuper(SWITCH_BCAST_SOURCE)).isTrue(); mBassClientStateMachine.sendMessage(INITIATE_PA_SYNC_TRANSFER); TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); assertThat(mBassClientStateMachine.hasDeferredMessagesSuper(INITIATE_PA_SYNC_TRANSFER)) .isTrue(); } @Test Loading Loading
android/app/src/com/android/bluetooth/bass_client/BassClientService.java +129 −39 Original line number Diff line number Diff line Loading @@ -169,6 +169,8 @@ public class BassClientService extends ProfileService { private final Map<BluetoothDevice, BluetoothLeBroadcastMetadata> mBroadcastMetadataMap = new ConcurrentHashMap<>(); private final HashSet<BluetoothDevice> mPausedBroadcastSinks = new HashSet<>(); private final Map<BluetoothDevice, Pair<Integer, Integer>> mSinksWaitingForPast = new HashMap<>(); private final Map<Integer, PauseType> mPausedBroadcastIds = new HashMap<>(); private final Deque<AddSourceData> mPendingAddSources = new ArrayDeque<>(); private final Map<Integer, HashSet<BluetoothDevice>> mLocalBroadcastReceivers = Loading Loading @@ -276,13 +278,6 @@ public class BassClientService extends ProfileService { { log("MESSAGE_BIG_MONITOR_TIMEOUT"); stopSourceReceivers(broadcastId); synchronized (mSearchScanCallbackLock) { // when searching is stopped then clear all sync // data if (mSearchScanCallback == null) { clearAllSyncData(); } } break; } default: Loading Loading @@ -1107,6 +1102,24 @@ public class BassClientService extends ProfileService { } } void syncRequestForPast(BluetoothDevice sink, int broadcastId, int sourceId) { log( "syncRequestForPast sink: " + sink + ", broadcastId: " + broadcastId + ", sourceId: " + sourceId); if (!leaudioBroadcastResyncHelper()) { return; } synchronized (mSinksWaitingForPast) { mSinksWaitingForPast.put(sink, new Pair<Integer, Integer>(broadcastId, sourceId)); } addSelectSourceRequest(broadcastId, true); } private void localNotifyReceiveStateChanged( BluetoothDevice sink, BluetoothLeBroadcastReceiveState receiveState) { int broadcastId = receiveState.getBroadcastId(); Loading @@ -1114,24 +1127,14 @@ public class BassClientService extends ProfileService { && !isLocalBroadcast(receiveState) && !isEmptyBluetoothDevice(receiveState.getSourceDevice()) && !isHostPauseType(broadcastId)) { boolean isReadyToAutoResync = false; if (receiveState.getPaSyncState() == BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCHRONIZED) { isReadyToAutoResync = true; } else { for (int i = 0; i < receiveState.getNumSubgroups(); i++) { Long syncState = receiveState.getBisSyncState().get(i); /* Synced to BIS */ if (syncState != BassConstants.BIS_SYNC_NOT_SYNC_TO_BIS && syncState != BassConstants.BIS_SYNC_FAILED_SYNC_TO_BIG) { isReadyToAutoResync = true; break; } } } if (isReadyToAutoResync) { if (isReceiverActive(receiveState) || receiveState.getPaSyncState() == BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCINFO_REQUEST) { mPausedBroadcastSinks.remove(sink); if (isAllReceiversActive(broadcastId) && mPausedBroadcastSinks.isEmpty()) { stopBigMonitoring(broadcastId, false); } } else if (!mPausedBroadcastIds.containsKey(broadcastId)) { if (mCachedBroadcasts.containsKey(broadcastId)) { addSelectSourceRequest(broadcastId, true); Loading @@ -1144,6 +1147,9 @@ public class BassClientService extends ProfileService { } } } else if (isEmptyBluetoothDevice(receiveState.getSourceDevice())) { synchronized (mSinksWaitingForPast) { mSinksWaitingForPast.remove(sink); } checkAndStopBigMonitoring(); } Loading Loading @@ -1584,8 +1590,9 @@ public class BassClientService extends ProfileService { if (toState == BluetoothProfile.STATE_DISCONNECTED) { mPendingGroupOp.remove(device); mPausedBroadcastSinks.remove(device); checkAndStopBigMonitoring(); synchronized (mSinksWaitingForPast) { mSinksWaitingForPast.remove(device); } int bondState = mAdapterService.getBondState(device); if (bondState == BluetoothDevice.BOND_NONE) { Loading @@ -1593,6 +1600,20 @@ public class BassClientService extends ProfileService { removeStateMachine(device); } checkAndStopBigMonitoring(); if (getConnectedDevices().isEmpty() || (mPausedBroadcastSinks.isEmpty() && mSinksWaitingForPast.isEmpty() && !isAnyConnectedDeviceSwitchingSource())) { synchronized (mSearchScanCallbackLock) { // when searching is stopped then clear all sync data if (mSearchScanCallback == null) { clearAllSyncData(); } } } /* Restore allowed context mask for unicast in case if last connected broadcast * delegator device which has external source disconnectes. */ Loading Loading @@ -2167,6 +2188,41 @@ public class BassClientService extends ProfileService { } mBisDiscoveryCounterMap.put(syncHandle, MAX_BIS_DISCOVERY_TRIES_NUM); synchronized (mSinksWaitingForPast) { Iterator<Map.Entry<BluetoothDevice, Pair<Integer, Integer>>> iterator = mSinksWaitingForPast.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry<BluetoothDevice, Pair<Integer, Integer>> entry = iterator.next(); BluetoothDevice sinkDevice = entry.getKey(); int broadcastIdForPast = entry.getValue().first; if (broadcastId == broadcastIdForPast) { int sourceId = entry.getValue().second; synchronized (mStateMachines) { BassClientStateMachine sm = getOrCreateStateMachine(sinkDevice); Message message = sm.obtainMessage( BassClientStateMachine.INITIATE_PA_SYNC_TRANSFER); message.arg1 = syncHandle; message.arg2 = sourceId; sm.sendMessage(message); } synchronized (mPendingSourcesToAdd) { Iterator<AddSourceData> addIterator = mPendingSourcesToAdd.iterator(); while (addIterator.hasNext()) { AddSourceData pendingSourcesToAdd = addIterator.next(); if (pendingSourcesToAdd.mSourceMetadata.getBroadcastId() == broadcastId && pendingSourcesToAdd.mSink.equals(sinkDevice)) { addIterator.remove(); } } } iterator.remove(); } } } synchronized (mPendingSourcesToAdd) { Iterator<AddSourceData> iterator = mPendingSourcesToAdd.iterator(); while (iterator.hasNext()) { Loading Loading @@ -3698,14 +3754,23 @@ public class BassClientService extends ProfileService { .filter(e -> e.getBroadcastId() == metadata.getBroadcastId()) .findAny(); if (leaudioBroadcastResyncHelper() && receiveState.isPresent() && (receiveState.get().getPaSyncState() == BluetoothLeBroadcastReceiveState .PA_SYNC_STATE_SYNCINFO_REQUEST || receiveState.get().getPaSyncState() == BluetoothLeBroadcastReceiveState .PA_SYNC_STATE_SYNCHRONIZED)) { iterator.remove(); continue; } List<Integer> activeSyncedSrc = getActiveSyncedSources(); if (receiveState.isPresent() && (!leaudioBroadcastResyncHelper() || isLocalBroadcast(metadata) || receiveState.get().getPaSyncState() == BluetoothLeBroadcastReceiveState .PA_SYNC_STATE_SYNCHRONIZED || activeSyncedSrc.contains( getSyncHandleForBroadcastId(metadata.getBroadcastId())))) { int sourceId = receiveState.get().getSourceId(); Loading Loading @@ -3862,24 +3927,49 @@ public class BassClientService extends ProfileService { return false; } private Set<Integer> getExternalBroadcastsActiveOnSinks() { HashSet<Integer> syncedBroadcasts = new HashSet<>(); for (BluetoothDevice device : getConnectedDevices()) { for (BluetoothLeBroadcastReceiveState receiveState : getAllSources(device)) { if (!isLocalBroadcast(receiveState)) { private boolean isReceiverActive(BluetoothLeBroadcastReceiveState receiveState) { if (receiveState.getPaSyncState() == BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCHRONIZED) { return true; } else { for (int i = 0; i < receiveState.getNumSubgroups(); i++) { Long syncState = receiveState.getBisSyncState().get(i); /* Synced to BIS */ if (syncState != BassConstants.BIS_SYNC_NOT_SYNC_TO_BIS && syncState != BassConstants.BIS_SYNC_FAILED_SYNC_TO_BIG) { return true; } } } return false; } private Set<Integer> getExternalBroadcastsActiveOnSinks() { HashSet<Integer> syncedBroadcasts = new HashSet<>(); for (BluetoothDevice device : getConnectedDevices()) { for (BluetoothLeBroadcastReceiveState receiveState : getAllSources(device)) { if (isLocalBroadcast(receiveState)) { continue; } if (isReceiverActive(receiveState)) { syncedBroadcasts.add(receiveState.getBroadcastId()); log("getExternalBroadcastsActiveOnSinks: " + receiveState); } } } return syncedBroadcasts; } private boolean isAllReceiversActive(int broadcastId) { for (BluetoothDevice device : getConnectedDevices()) { for (BluetoothLeBroadcastReceiveState receiveState : getAllSources(device)) { if (receiveState.getBroadcastId() == broadcastId && !isReceiverActive(receiveState)) { return false; } return syncedBroadcasts; } } return true; } /** Get sink devices synced to the broadcasts */ Loading
android/app/src/com/android/bluetooth/bass_client/BassClientStateMachine.java +78 −59 Original line number Diff line number Diff line Loading @@ -84,8 +84,7 @@ import java.util.Scanner; import java.util.UUID; import java.util.stream.IntStream; @VisibleForTesting public class BassClientStateMachine extends StateMachine { class BassClientStateMachine extends StateMachine { private static final String TAG = "BassClientStateMachine"; @VisibleForTesting static final byte[] REMOTE_SCAN_STOP = {00}; @VisibleForTesting static final byte[] REMOTE_SCAN_START = {01}; Loading Loading @@ -113,6 +112,7 @@ public class BassClientStateMachine extends StateMachine { static final int REACHED_MAX_SOURCE_LIMIT = 16; static final int SWITCH_BCAST_SOURCE = 17; static final int CANCEL_PENDING_SOURCE_OPERATION = 18; static final int INITIATE_PA_SYNC_TRANSFER = 19; // NOTE: the value is not "final" - it is modified in the unit tests @VisibleForTesting private int mConnectTimeoutMs; Loading Loading @@ -816,43 +816,11 @@ public class BassClientStateMachine extends StateMachine { int state = recvState.getPaSyncState(); if (state == BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCINFO_REQUEST) { log("Initiate PAST procedure"); PeriodicAdvertisementResult result = mService.getPeriodicAdvertisementResult( recvState.getSourceDevice(), recvState.getBroadcastId()); if (result != null) { int syncHandle = result.getSyncHandle(); log("processPASyncState: syncHandle " + result.getSyncHandle()); if (syncHandle != BassConstants.INVALID_SYNC_HANDLE) { serviceData = 0x000000FF & recvState.getSourceId(); serviceData = serviceData << 8; // advA matches EXT_ADV_ADDRESS // also matches source address (as we would have written) serviceData = serviceData & (~BassConstants.ADV_ADDRESS_DONT_MATCHES_EXT_ADV_ADDRESS); serviceData = serviceData & (~BassConstants.ADV_ADDRESS_DONT_MATCHES_SOURCE_ADV_ADDRESS); log( "Initiate PAST for: " + mDevice + ", syncHandle: " + syncHandle + "serviceData" + serviceData); BluetoothMethodProxy.getInstance() .periodicAdvertisingManagerTransferSync( BassClientPeriodicAdvertisingManager .getPeriodicAdvertisingManager(), mDevice, serviceData, syncHandle); } } else { BluetoothLeBroadcastMetadata currentMetadata = getCurrentBroadcastMetadata(recvState.getSourceId()); int sourceId = recvState.getSourceId(); BluetoothLeBroadcastMetadata currentMetadata = getCurrentBroadcastMetadata(sourceId); if (mService.isLocalBroadcast(currentMetadata)) { int advHandle = currentMetadata.getSourceAdvertisingSid(); serviceData = 0x000000FF & recvState.getSourceId(); serviceData = 0x000000FF & sourceId; serviceData = serviceData << 8; // Address we set in the Source Address can differ from the address in the air serviceData = Loading @@ -873,9 +841,52 @@ public class BassClientStateMachine extends StateMachine { advHandle, mLocalPeriodicAdvCallback); } else { Log.e(TAG, "There is no valid sync handle for this Source"); int broadcastId = recvState.getBroadcastId(); PeriodicAdvertisementResult result = mService.getPeriodicAdvertisementResult( recvState.getSourceDevice(), broadcastId); if (result != null) { int syncHandle = result.getSyncHandle(); if (syncHandle != BassConstants.INVALID_SYNC_HANDLE) { initiatePaSyncTransfer(syncHandle, sourceId); return; } } mService.syncRequestForPast(mDevice, broadcastId, sourceId); } } } private void initiatePaSyncTransfer(int syncHandle, int sourceId) { if (syncHandle != BassConstants.INVALID_SYNC_HANDLE && sourceId != BassConstants.INVALID_SOURCE_ID) { int serviceData = 0x000000FF & sourceId; serviceData = serviceData << 8; // advA matches EXT_ADV_ADDRESS // also matches source address (as we would have written) serviceData = serviceData & (~BassConstants.ADV_ADDRESS_DONT_MATCHES_EXT_ADV_ADDRESS); serviceData = serviceData & (~BassConstants.ADV_ADDRESS_DONT_MATCHES_SOURCE_ADV_ADDRESS); log( "Initiate PAST for: " + mDevice + ", syncHandle: " + syncHandle + ", serviceData: " + serviceData); BluetoothMethodProxy.getInstance() .periodicAdvertisingManagerTransferSync( BassClientPeriodicAdvertisingManager.getPeriodicAdvertisingManager(), mDevice, serviceData, syncHandle); } else { Log.e( TAG, "Invalid syncHandle or sourceId for PAST, syncHandle: " + syncHandle + ", sourceId: " + sourceId); } } Loading Loading @@ -2350,6 +2361,11 @@ public class BassClientStateMachine extends StateMachine { int broadcastId = message.arg1; cancelPendingSourceOperation(broadcastId); break; case INITIATE_PA_SYNC_TRANSFER: int syncHandle = message.arg1; int sourceIdForPast = message.arg2; initiatePaSyncTransfer(syncHandle, sourceIdForPast); break; default: log("CONNECTED: not handled message:" + message.what); return NOT_HANDLED; Loading Loading @@ -2539,6 +2555,7 @@ public class BassClientStateMachine extends StateMachine { case REACHED_MAX_SOURCE_LIMIT: case SWITCH_BCAST_SOURCE: case PSYNC_ACTIVE_TIMEOUT: case INITIATE_PA_SYNC_TRANSFER: log( "defer the message: " + messageWhatToString(message.what) Loading Loading @@ -2646,6 +2663,8 @@ public class BassClientStateMachine extends StateMachine { return "CONNECT_TIMEOUT"; case CANCEL_PENDING_SOURCE_OPERATION: return "CANCEL_PENDING_SOURCE_OPERATION"; case INITIATE_PA_SYNC_TRANSFER: return "INITIATE_PA_SYNC_TRANSFER"; default: break; } Loading
android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientServiceTest.java +177 −4 File changed.Preview size limit exceeded, changes collapsed. Show changes
android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientStateMachineTest.java +38 −1 Original line number Diff line number Diff line Loading @@ -32,6 +32,7 @@ import static com.android.bluetooth.bass_client.BassClientStateMachine.CONNECT_T import static com.android.bluetooth.bass_client.BassClientStateMachine.DISCONNECT; import static com.android.bluetooth.bass_client.BassClientStateMachine.GATT_TXN_PROCESSED; import static com.android.bluetooth.bass_client.BassClientStateMachine.GATT_TXN_TIMEOUT; import static com.android.bluetooth.bass_client.BassClientStateMachine.INITIATE_PA_SYNC_TRANSFER; import static com.android.bluetooth.bass_client.BassClientStateMachine.PSYNC_ACTIVE_TIMEOUT; import static com.android.bluetooth.bass_client.BassClientStateMachine.REACHED_MAX_SOURCE_LIMIT; import static com.android.bluetooth.bass_client.BassClientStateMachine.READ_BASS_CHARACTERISTICS; Loading Loading @@ -894,12 +895,23 @@ public class BassClientStateMachineTest { when(characteristic.getValue()).thenReturn(value); when(mBassClientService.getPeriodicAdvertisementResult(any(), anyInt())) .thenReturn(paResult); when(paResult.getSyncHandle()).thenReturn(100); int syncHandle = 100; when(paResult.getSyncHandle()).thenReturn(syncHandle); Mockito.clearInvocations(callbacks); cb.onCharacteristicRead(null, characteristic, GATT_SUCCESS); TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); int serviceData = 0x000000FF & sourceId; serviceData = serviceData << 8; // advA matches EXT_ADV_ADDRESS // also matches source address (as we would have written) serviceData = serviceData & (~BassConstants.ADV_ADDRESS_DONT_MATCHES_EXT_ADV_ADDRESS); serviceData = serviceData & (~BassConstants.ADV_ADDRESS_DONT_MATCHES_SOURCE_ADV_ADDRESS); verify(mMethodProxy) .periodicAdvertisingManagerTransferSync( any(), any(), eq(serviceData), eq(syncHandle)); verify(callbacks) .notifyReceiveStateChanged(any(), eq(sourceId), receiveStateCaptor.capture()); Assert.assertEquals(receiveStateCaptor.getValue().getSourceDevice(), mSourceTestDevice); Loading Loading @@ -1706,6 +1718,26 @@ public class BassClientStateMachineTest { assertThat(mBassClientStateMachine.mPendingSourceId).isEqualTo(sid); } @Test public void sendInitiatePaSyncTransferMessage_inConnectedState() { initToConnectedState(); int syncHandle = 1234; int sourceId = 4321; mBassClientStateMachine.sendMessage(INITIATE_PA_SYNC_TRANSFER, syncHandle, sourceId); TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); int serviceData = 0x000000FF & sourceId; serviceData = serviceData << 8; // advA matches EXT_ADV_ADDRESS // also matches source address (as we would have written) serviceData = serviceData & (~BassConstants.ADV_ADDRESS_DONT_MATCHES_EXT_ADV_ADDRESS); serviceData = serviceData & (~BassConstants.ADV_ADDRESS_DONT_MATCHES_SOURCE_ADV_ADDRESS); verify(mMethodProxy) .periodicAdvertisingManagerTransferSync( any(), any(), eq(serviceData), eq(syncHandle)); } @Test public void sendConnectMessage_inConnectedProcessingState_doesNotChangeState() { initToConnectedProcessingState(); Loading Loading @@ -1906,6 +1938,11 @@ public class BassClientStateMachineTest { mBassClientStateMachine.sendMessage(SWITCH_BCAST_SOURCE); TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); assertThat(mBassClientStateMachine.hasDeferredMessagesSuper(SWITCH_BCAST_SOURCE)).isTrue(); mBassClientStateMachine.sendMessage(INITIATE_PA_SYNC_TRANSFER); TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); assertThat(mBassClientStateMachine.hasDeferredMessagesSuper(INITIATE_PA_SYNC_TRANSFER)) .isTrue(); } @Test Loading