diff options
22 files changed, 794 insertions, 506 deletions
diff --git a/android/app/src/com/android/bluetooth/a2dp/A2dpStateMachine.java b/android/app/src/com/android/bluetooth/a2dp/A2dpStateMachine.java index a2e4d0b4d2..d02d7649d0 100644 --- a/android/app/src/com/android/bluetooth/a2dp/A2dpStateMachine.java +++ b/android/app/src/com/android/bluetooth/a2dp/A2dpStateMachine.java @@ -132,9 +132,7 @@ final class A2dpStateMachine extends StateMachine { public void doQuit() { log("doQuit for device " + mDevice); - if (Flags.a2dpBroadcastConnectionStateWhenTurnedOff() - && mConnectionState != STATE_DISCONNECTED - && mLastConnectionState != -1) { + if (mConnectionState != STATE_DISCONNECTED && mLastConnectionState != -1) { // Broadcast CONNECTION_STATE_CHANGED when A2dpService is turned off while // the device is connected broadcastConnectionState(STATE_DISCONNECTED, mConnectionState); diff --git a/android/app/src/com/android/bluetooth/bass_client/BassClientService.java b/android/app/src/com/android/bluetooth/bass_client/BassClientService.java index 4e585a4f83..be55eff42b 100644 --- a/android/app/src/com/android/bluetooth/bass_client/BassClientService.java +++ b/android/app/src/com/android/bluetooth/bass_client/BassClientService.java @@ -523,42 +523,22 @@ public class BassClientService extends ProfileService { return BluetoothProperties.isProfileBapBroadcastAssistEnabled().orElse(false); } - private static class SourceSyncRequest { - private final ScanResult mScanResult; - private final boolean mHasPriority; - private final int mSyncFailureCounter; - - SourceSyncRequest(ScanResult scanResult, boolean hasPriority, int syncFailureCounter) { - this.mScanResult = scanResult; - this.mHasPriority = hasPriority; - this.mSyncFailureCounter = syncFailureCounter; - } - - public ScanResult getScanResult() { - return mScanResult; - } + private record SourceSyncRequest( + ScanResult scanResult, boolean hasPriority, int syncFailureCounter) { public int getRssi() { - return mScanResult.getRssi(); - } - - public boolean hasPriority() { - return mHasPriority; - } - - public int getFailsCounter() { - return mSyncFailureCounter; + return scanResult.getRssi(); } @Override public String toString() { return "SourceSyncRequest{" - + "mScanResult=" - + mScanResult - + ", mHasPriority=" - + mHasPriority - + ", mSyncFailureCounter=" - + mSyncFailureCounter + + "scanResult=" + + scanResult + + ", hasPriority=" + + hasPriority + + ", syncFailureCounter=" + + syncFailureCounter + '}'; } } @@ -567,33 +547,21 @@ public class BassClientService extends ProfileService { new Comparator<SourceSyncRequest>() { @Override public int compare(SourceSyncRequest ssr1, SourceSyncRequest ssr2) { - if (ssr1.hasPriority() && !ssr2.hasPriority()) { + if (ssr1.hasPriority && !ssr2.hasPriority) { return -1; - } else if (!ssr1.hasPriority() && ssr2.hasPriority()) { + } else if (!ssr1.hasPriority && ssr2.hasPriority) { return 1; } else if (leaudioSortScansToSyncByFails() - && (ssr1.getFailsCounter() != ssr2.getFailsCounter())) { - return Integer.compare(ssr1.getFailsCounter(), ssr2.getFailsCounter()); + && (ssr1.syncFailureCounter != ssr2.syncFailureCounter)) { + return Integer.compare(ssr1.syncFailureCounter, ssr2.syncFailureCounter); } else { return Integer.compare(ssr2.getRssi(), ssr1.getRssi()); } } }; - private static class AddSourceData { - final BluetoothDevice mSink; - final BluetoothLeBroadcastMetadata mSourceMetadata; - final boolean mIsGroupOp; - - AddSourceData( - BluetoothDevice sink, - BluetoothLeBroadcastMetadata sourceMetadata, - boolean isGroupOp) { - mSink = sink; - mSourceMetadata = sourceMetadata; - mIsGroupOp = isGroupOp; - } - } + private record AddSourceData( + BluetoothDevice sink, BluetoothLeBroadcastMetadata sourceMetadata, boolean isGroupOp) {} void updatePeriodicAdvertisementResultMap( BluetoothDevice device, @@ -1624,7 +1592,7 @@ public class BassClientService extends ProfileService { } synchronized (mPendingSourcesToAdd) { mPendingSourcesToAdd.removeIf( - pendingSourcesToAdd -> pendingSourcesToAdd.mSink.equals(device)); + pendingSourcesToAdd -> pendingSourcesToAdd.sink.equals(device)); } int bondState = mAdapterService.getBondState(device); @@ -2150,7 +2118,7 @@ public class BassClientService extends ProfileService { while (iterator.hasNext()) { SourceSyncRequest sourceSyncRequest = iterator.next(); Integer queuedBroadcastId = - BassUtils.getBroadcastId(sourceSyncRequest.getScanResult()); + BassUtils.getBroadcastId(sourceSyncRequest.scanResult); if (!broadcastsToKeepSynced.contains(queuedBroadcastId)) { iterator.remove(); } @@ -2294,14 +2262,14 @@ public class BassClientService extends ProfileService { Iterator<AddSourceData> iterator = mPendingSourcesToAdd.iterator(); while (iterator.hasNext()) { AddSourceData pendingSourcesToAdd = iterator.next(); - if (pendingSourcesToAdd.mSourceMetadata.getBroadcastId() == broadcastId) { + if (pendingSourcesToAdd.sourceMetadata.getBroadcastId() == broadcastId) { if (!notifiedOfLost) { notifiedOfLost = true; mCallbacks.notifySourceLost(broadcastId); } mCallbacks.notifySourceAddFailed( - pendingSourcesToAdd.mSink, - pendingSourcesToAdd.mSourceMetadata, + pendingSourcesToAdd.sink, + pendingSourcesToAdd.sourceMetadata, BluetoothStatusCodes.ERROR_LOCAL_NOT_ENOUGH_RESOURCES); iterator.remove(); } @@ -2400,9 +2368,9 @@ public class BassClientService extends ProfileService { Iterator<AddSourceData> addIterator = mPendingSourcesToAdd.iterator(); while (addIterator.hasNext()) { AddSourceData pendingSourcesToAdd = addIterator.next(); - if (pendingSourcesToAdd.mSourceMetadata.getBroadcastId() + if (pendingSourcesToAdd.sourceMetadata.getBroadcastId() == broadcastId - && pendingSourcesToAdd.mSink.equals(sinkDevice)) { + && pendingSourcesToAdd.sink.equals(sinkDevice)) { addIterator.remove(); } } @@ -2416,15 +2384,15 @@ public class BassClientService extends ProfileService { Iterator<AddSourceData> iterator = mPendingSourcesToAdd.iterator(); while (iterator.hasNext()) { AddSourceData pendingSourceToAdd = iterator.next(); - if (pendingSourceToAdd.mSourceMetadata.getBroadcastId() == broadcastId) { + if (pendingSourceToAdd.sourceMetadata.getBroadcastId() == broadcastId) { boolean addSource = true; - if (pendingSourceToAdd.mIsGroupOp && !pendingSourcesToAdd.isEmpty()) { + if (pendingSourceToAdd.isGroupOp && !pendingSourcesToAdd.isEmpty()) { List<BluetoothDevice> deviceGroup = getTargetDeviceList( - pendingSourceToAdd.mSink, /* isGroupOp */ true); + pendingSourceToAdd.sink, /* isGroupOp */ true); for (AddSourceData addSourceData : pendingSourcesToAdd) { - if (addSourceData.mIsGroupOp - && deviceGroup.contains(addSourceData.mSink)) { + if (addSourceData.isGroupOp + && deviceGroup.contains(addSourceData.sink)) { addSource = false; } } @@ -2437,9 +2405,9 @@ public class BassClientService extends ProfileService { } for (AddSourceData addSourceData : pendingSourcesToAdd) { addSource( - addSourceData.mSink, - addSourceData.mSourceMetadata, - addSourceData.mIsGroupOp); + addSourceData.sink, + addSourceData.sourceMetadata, + addSourceData.isGroupOp); } } handleSelectSourceRequest(); @@ -2587,8 +2555,7 @@ public class BassClientService extends ProfileService { synchronized (mPendingSourcesToAdd) { mPendingSourcesToAdd.removeIf( pendingSourcesToAdd -> - pendingSourcesToAdd.mSourceMetadata.getBroadcastId() - == broadcastId); + pendingSourcesToAdd.sourceMetadata.getBroadcastId() == broadcastId); } synchronized (mSinksWaitingForPast) { mSinksWaitingForPast @@ -2855,7 +2822,7 @@ public class BassClientService extends ProfileService { return; } - scanRes = mSourceSyncRequestsQueue.poll().getScanResult(); + scanRes = mSourceSyncRequestsQueue.poll().scanResult; ScanRecord scanRecord = scanRes.getScanRecord(); sEventLogger.logd(TAG, "Select Broadcast Source, result: " + scanRes); @@ -3123,8 +3090,8 @@ public class BassClientService extends ProfileService { } for (SourceSyncRequest sourceSyncRequest : mSourceSyncRequestsQueue) { - if (BassUtils.getBroadcastId(sourceSyncRequest.getScanResult()) == broadcastId) { - return !priorityImportant || sourceSyncRequest.hasPriority(); + if (BassUtils.getBroadcastId(sourceSyncRequest.scanResult) == broadcastId) { + return !priorityImportant || sourceSyncRequest.hasPriority; } } } @@ -3938,10 +3905,9 @@ public class BassClientService extends ProfileService { Iterator<AddSourceData> iterator = mPendingSourcesToAdd.iterator(); while (iterator.hasNext()) { AddSourceData pendingSourcesToAdd = iterator.next(); - if (pendingSourcesToAdd.mSink.equals(sink)) { + if (pendingSourcesToAdd.sink.equals(sink)) { Log.d(TAG, "handleBassStateReady: retry adding source with device, " + sink); - addSource( - pendingSourcesToAdd.mSink, pendingSourcesToAdd.mSourceMetadata, false); + addSource(pendingSourcesToAdd.sink, pendingSourcesToAdd.sourceMetadata, false); iterator.remove(); return; } @@ -3956,10 +3922,10 @@ public class BassClientService extends ProfileService { Iterator<AddSourceData> iterator = mPendingSourcesToAdd.iterator(); while (iterator.hasNext()) { AddSourceData pendingSourcesToAdd = iterator.next(); - if (pendingSourcesToAdd.mSink.equals(sink)) { + if (pendingSourcesToAdd.sink.equals(sink)) { mCallbacks.notifySourceAddFailed( - pendingSourcesToAdd.mSink, - pendingSourcesToAdd.mSourceMetadata, + pendingSourcesToAdd.sink, + pendingSourcesToAdd.sourceMetadata, BluetoothStatusCodes.ERROR_REMOTE_NOT_ENOUGH_RESOURCES); iterator.remove(); return; @@ -4414,53 +4380,38 @@ public class BassClientService extends ProfileService { } private Set<Integer> getBroadcastIdsOfSyncedBroadcasters() { - HashSet<Integer> broadcastIds = new HashSet<>(); - List<Integer> activeSyncedSrc = new ArrayList<>(getActiveSyncedSources()); - for (int syncHandle : activeSyncedSrc) { - broadcastIds.add(getBroadcastIdForSyncHandle(syncHandle)); - } - return broadcastIds; + return getActiveSyncedSources().stream() + .map(this::getBroadcastIdForSyncHandle) + .collect(Collectors.toCollection(HashSet::new)); } private Set<Integer> getBroadcastIdsWaitingForPAST() { - HashSet<Integer> broadcastIds = new HashSet<>(); synchronized (mSinksWaitingForPast) { - for (Map.Entry<BluetoothDevice, Pair<Integer, Integer>> entry : - mSinksWaitingForPast.entrySet()) { - broadcastIds.add(entry.getValue().first); - } + return mSinksWaitingForPast.values().stream() + .map(pair -> pair.first) + .collect(Collectors.toCollection(HashSet::new)); } - return broadcastIds; } private Set<Integer> getBroadcastIdsWaitingForAddSource() { - HashSet<Integer> broadcastIds = new HashSet<>(); synchronized (mPendingSourcesToAdd) { - for (AddSourceData pendingSourcesToAdd : mPendingSourcesToAdd) { - broadcastIds.add(pendingSourcesToAdd.mSourceMetadata.getBroadcastId()); - } + return mPendingSourcesToAdd.stream() + .map(pendingSource -> pendingSource.sourceMetadata.getBroadcastId()) + .collect(Collectors.toCollection(HashSet::new)); } - return broadcastIds; } private Set<Integer> getPausedBroadcastIdsBasedOnSinks() { - HashSet<Integer> broadcastIds = new HashSet<>(); - for (BluetoothDevice pausedSink : mPausedBroadcastSinks) { - Map<Integer, BluetoothLeBroadcastMetadata> entry = - mBroadcastMetadataMap.getOrDefault(pausedSink, Collections.emptyMap()); - broadcastIds.addAll(entry.keySet()); - } - return broadcastIds; + return mPausedBroadcastSinks.stream() + .map(paused -> mBroadcastMetadataMap.getOrDefault(paused, Collections.emptyMap())) + .flatMap(entry -> entry.keySet().stream()) + .collect(Collectors.toCollection(HashSet::new)); } private Set<Integer> getUnintentionallyPausedBroadcastIds() { - HashSet<Integer> broadcastIds = new HashSet<>(); - for (int pausedBroadcastId : mPausedBroadcastIds.keySet()) { - if (isSinkUnintentionalPauseType(pausedBroadcastId)) { - broadcastIds.add(pausedBroadcastId); - } - } - return broadcastIds; + return mPausedBroadcastIds.keySet().stream() + .filter(this::isSinkUnintentionalPauseType) + .collect(Collectors.toCollection(HashSet::new)); } /** Handle broadcast state changed */ @@ -4533,9 +4484,9 @@ public class BassClientService extends ProfileService { case MSG_SOURCE_ADDED: case MSG_SOURCE_ADDED_FAILED: ObjParams param = (ObjParams) msg.obj; - sink = (BluetoothDevice) param.mObj1; + sink = param.device; sService.checkForPendingGroupOpRequest( - sink, reason, BassClientStateMachine.ADD_BCAST_SOURCE, param.mObj2); + sink, reason, BassClientStateMachine.ADD_BCAST_SOURCE, param.obj2); break; case MSG_SOURCE_REMOVED: case MSG_SOURCE_REMOVED_FAILED: @@ -4600,15 +4551,7 @@ public class BassClientService extends ProfileService { } } - private static class ObjParams { - final Object mObj1; - final Object mObj2; - - ObjParams(Object o1, Object o2) { - mObj1 = o1; - mObj2 = o2; - } - } + private record ObjParams(BluetoothDevice device, Object obj2) {} private static void invokeCallback( IBluetoothLeBroadcastAssistantCallback callback, Message msg) @@ -4636,14 +4579,14 @@ public class BassClientService extends ProfileService { break; case MSG_SOURCE_ADDED: param = (ObjParams) msg.obj; - sink = (BluetoothDevice) param.mObj1; + sink = param.device; callback.onSourceAdded(sink, sourceId, reason); break; case MSG_SOURCE_ADDED_FAILED: param = (ObjParams) msg.obj; - sink = (BluetoothDevice) param.mObj1; + sink = param.device; BluetoothLeBroadcastMetadata metadata = - (BluetoothLeBroadcastMetadata) param.mObj2; + (BluetoothLeBroadcastMetadata) param.obj2; callback.onSourceAddFailed(sink, metadata, reason); break; case MSG_SOURCE_MODIFIED: @@ -4662,9 +4605,9 @@ public class BassClientService extends ProfileService { break; case MSG_RECEIVESTATE_CHANGED: param = (ObjParams) msg.obj; - sink = (BluetoothDevice) param.mObj1; + sink = param.device; BluetoothLeBroadcastReceiveState state = - (BluetoothLeBroadcastReceiveState) param.mObj2; + (BluetoothLeBroadcastReceiveState) param.obj2; callback.onReceiveStateChanged(sink, sourceId, state); break; case MSG_SOURCE_LOST: diff --git a/android/app/src/com/android/bluetooth/btservice/AdapterService.java b/android/app/src/com/android/bluetooth/btservice/AdapterService.java index 60e06293d9..87f5daad12 100644 --- a/android/app/src/com/android/bluetooth/btservice/AdapterService.java +++ b/android/app/src/com/android/bluetooth/btservice/AdapterService.java @@ -26,6 +26,7 @@ import static android.Manifest.permission.MODIFY_PHONE_STATE; import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE; import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE; import static android.bluetooth.BluetoothAdapter.SCAN_MODE_NONE; +import static android.bluetooth.BluetoothAdapter.nameForState; import static android.bluetooth.BluetoothDevice.BATTERY_LEVEL_UNKNOWN; import static android.bluetooth.BluetoothDevice.BOND_NONE; import static android.bluetooth.BluetoothDevice.TRANSPORT_AUTO; @@ -279,7 +280,7 @@ public class AdapterService extends Service { mPreferredAudioProfilesCallbacks = new RemoteCallbackList<>(); private final RemoteCallbackList<IBluetoothQualityReportReadyCallback> mBluetoothQualityReportReadyCallbacks = new RemoteCallbackList<>(); - private final RemoteCallbackList<IBluetoothCallback> mRemoteCallbacks = + private final RemoteCallbackList<IBluetoothCallback> mSystemServerCallbacks = new RemoteCallbackList<>(); private final RemoteCallbackList<IBluetoothConnectionCallback> mBluetoothConnectionCallbacks = new RemoteCallbackList<>(); @@ -477,7 +478,7 @@ public class AdapterService extends Service { */ public void onProfileServiceStateChanged(ProfileService profile, int state) { if (state != BluetoothAdapter.STATE_ON && state != BluetoothAdapter.STATE_OFF) { - throw new IllegalArgumentException(BluetoothAdapter.nameForState(state)); + throw new IllegalArgumentException(nameForState(state)); } Message m = mHandler.obtainMessage(MESSAGE_PROFILE_SERVICE_STATE_CHANGED); m.obj = profile; @@ -1140,67 +1141,40 @@ public class AdapterService extends Service { } } - void updateAdapterName(String name) { - updateAdapterNameInternal(name); - } - - private void updateAdapterNameInternal(String name) { - int n = mRemoteCallbacks.beginBroadcast(); - Log.d(TAG, "updateAdapterName(" + name + ")"); - for (int i = 0; i < n; i++) { - try { - mRemoteCallbacks.getBroadcastItem(i).onAdapterNameChange(name); - } catch (RemoteException e) { - Log.d(TAG, "updateAdapterName() - Callback #" + i + " failed (" + e + ")"); - } + private void broadcastToSystemServerCallbacks( + String logAction, RemoteExceptionIgnoringConsumer<IBluetoothCallback> action) { + final int itemCount = mSystemServerCallbacks.beginBroadcast(); + Log.d(TAG, "Broadcasting [" + logAction + "] to " + itemCount + " receivers."); + for (int i = 0; i < itemCount; i++) { + action.accept(mSystemServerCallbacks.getBroadcastItem(i)); } - mRemoteCallbacks.finishBroadcast(); + mSystemServerCallbacks.finishBroadcast(); } - void updateAdapterAddress(String address) { - updateAdapterAddressInternal(address); + void updateAdapterName(String name) { + broadcastToSystemServerCallbacks( + "updateAdapterName(" + name + ")", (c) -> c.onAdapterNameChange(name)); } - private void updateAdapterAddressInternal(String address) { - int n = mRemoteCallbacks.beginBroadcast(); - Log.d(TAG, "updateAdapterAddress(" + BluetoothUtils.toAnonymizedAddress(address) + ")"); - for (int i = 0; i < n; i++) { - try { - mRemoteCallbacks.getBroadcastItem(i).onAdapterAddressChange(address); - } catch (RemoteException e) { - Log.d(TAG, "updateAdapterAddress() - Callback #" + i + " failed (" + e + ")"); - } - } - mRemoteCallbacks.finishBroadcast(); + void updateAdapterAddress(String address) { + broadcastToSystemServerCallbacks( + "updateAdapterAddress(" + BluetoothUtils.toAnonymizedAddress(address) + ")", + (c) -> c.onAdapterAddressChange(address)); } - void updateAdapterState(int prevState, int newState) { - mAdapterProperties.setState(newState); + void updateAdapterState(int from, int to) { + mAdapterProperties.setState(to); - // Only BluetoothManagerService should be registered - int n = mRemoteCallbacks.beginBroadcast(); - Log.d( - TAG, - "updateAdapterState() - Broadcasting state " - + BluetoothAdapter.nameForState(newState) - + " to " - + n - + " receivers."); - for (int i = 0; i < n; i++) { - try { - mRemoteCallbacks.getBroadcastItem(i).onBluetoothStateChange(prevState, newState); - } catch (RemoteException e) { - Log.d(TAG, "updateAdapterState() - Callback #" + i + " failed (" + e + ")"); - } - } - mRemoteCallbacks.finishBroadcast(); + broadcastToSystemServerCallbacks( + "updateAdapterState(" + nameForState(from) + ", " + nameForState(to) + ")", + (c) -> c.onBluetoothStateChange(from, to)); for (Map.Entry<BluetoothStateCallback, Executor> e : mLocalCallbacks.entrySet()) { - e.getValue().execute(() -> e.getKey().onBluetoothStateChange(prevState, newState)); + e.getValue().execute(() -> e.getKey().onBluetoothStateChange(from, to)); } // Turn the Adapter all the way off if we are disabling and the snoop log setting changed. - if (newState == BluetoothAdapter.STATE_BLE_TURNING_ON) { + if (to == BluetoothAdapter.STATE_BLE_TURNING_ON) { sSnoopLogSettingAtEnable = BluetoothProperties.snoop_log_mode() .orElse(BluetoothProperties.snoop_log_mode_values.EMPTY); @@ -1232,8 +1206,7 @@ public class AdapterService extends Service { BluetoothProperties.snoop_default_mode(value); } } - } else if (newState == BluetoothAdapter.STATE_BLE_ON - && prevState != BluetoothAdapter.STATE_OFF) { + } else if (to == BluetoothAdapter.STATE_BLE_ON && from != BluetoothAdapter.STATE_OFF) { var snoopLogSetting = BluetoothProperties.snoop_log_mode() .orElse(BluetoothProperties.snoop_log_mode_values.EMPTY); @@ -1476,7 +1449,7 @@ public class AdapterService extends Service { mBluetoothConnectionCallbacks.kill(); - mRemoteCallbacks.kill(); + mSystemServerCallbacks.kill(); mMetadataListeners.values().forEach(v -> v.kill()); } @@ -6134,12 +6107,12 @@ public class AdapterService extends Service { @VisibleForTesting void registerRemoteCallback(IBluetoothCallback callback) { - mRemoteCallbacks.register(callback); + mSystemServerCallbacks.register(callback); } @VisibleForTesting void unregisterRemoteCallback(IBluetoothCallback callback) { - mRemoteCallbacks.unregister(callback); + mSystemServerCallbacks.unregister(callback); } @VisibleForTesting @@ -6719,9 +6692,7 @@ public class AdapterService extends Service { || currentState == BluetoothAdapter.STATE_TURNING_OFF || currentState == BluetoothAdapter.STATE_BLE_TURNING_OFF) { writer.println(); - writer.println( - "Impossible to dump native stack. state=" - + BluetoothAdapter.nameForState(currentState)); + writer.println("Impossible to dump native stack. state=" + nameForState(currentState)); writer.println(); writer.flush(); } else { diff --git a/android/app/src/com/android/bluetooth/btservice/RemoteDevices.java b/android/app/src/com/android/bluetooth/btservice/RemoteDevices.java index 9efcf1f779..89e80650b1 100644 --- a/android/app/src/com/android/bluetooth/btservice/RemoteDevices.java +++ b/android/app/src/com/android/bluetooth/btservice/RemoteDevices.java @@ -1477,10 +1477,25 @@ public class RemoteDevices { if (connectionState == BluetoothAdapter.STATE_CONNECTED) { connectionChangeConsumer = cb -> cb.onDeviceConnected(device); } else { + final int disconnectReason; + if (hciReason == 0x16 /* HCI_ERR_CONN_CAUSE_LOCAL_HOST */ + && mAdapterService.getDatabase().getKeyMissingCount(device) > 0) { + // Native stack disconnects the link on detecting the bond loss. Native GATT would + // return HCI_ERR_CONN_CAUSE_LOCAL_HOST in such case, but the apps should see + // HCI_ERR_AUTH_FAILURE. + Log.d( + TAG, + "aclStateChangeCallback() - disconnected due to bond loss for device=" + + device); + disconnectReason = 0x05; /* HCI_ERR_AUTH_FAILURE */ + } else { + disconnectReason = hciReason; + } connectionChangeConsumer = cb -> cb.onDeviceDisconnected( - device, AdapterService.hciToAndroidDisconnectReason(hciReason)); + device, + AdapterService.hciToAndroidDisconnectReason(disconnectReason)); } mAdapterService.aclStateChangeBroadcastCallback(connectionChangeConsumer); diff --git a/android/app/src/com/android/bluetooth/gatt/AdvertiseHelper.java b/android/app/src/com/android/bluetooth/gatt/AdvertiseHelper.java index f61f56cd16..5f3f763e44 100644 --- a/android/app/src/com/android/bluetooth/gatt/AdvertiseHelper.java +++ b/android/app/src/com/android/bluetooth/gatt/AdvertiseHelper.java @@ -44,22 +44,21 @@ class AdvertiseHelper { private static final int SERVICE_DATA_128_BIT_UUID = 0X21; private static final int MANUFACTURER_SPECIFIC_DATA = 0XFF; - public static byte[] advertiseDataToBytes(AdvertiseData data, String name) { + private AdvertiseHelper() {} + static byte[] advertiseDataToBytes(AdvertiseData data, String name) { if (data == null) { return new byte[0]; } - // Flags are added by lower layers of the stack, only if needed; - // no need to add them here. - + // Flags are added by lower layers of the stack, only if needed no need to add them here. ByteArrayOutputStream ret = new ByteArrayOutputStream(); if (data.getIncludeDeviceName()) { byte[] nameBytes = name.getBytes(StandardCharsets.UTF_8); int nameLength = nameBytes.length; - byte type; + final byte type; // TODO(jpawlowski) put a better limit on device name! if (nameLength > DEVICE_NAME_MAX) { @@ -76,7 +75,7 @@ class AdvertiseHelper { } for (int i = 0; i < data.getManufacturerSpecificData().size(); i++) { - int manufacturerId = data.getManufacturerSpecificData().keyAt(i); + final int manufacturerId = data.getManufacturerSpecificData().keyAt(i); byte[] manufacturerData = data.getManufacturerSpecificData().get(manufacturerId); int dataLen = 2 + (manufacturerData == null ? 0 : manufacturerData.length); @@ -106,7 +105,7 @@ class AdvertiseHelper { ByteArrayOutputStream serviceUuids128 = new ByteArrayOutputStream(); for (ParcelUuid parcelUuid : data.getServiceUuids()) { - byte[] uuid = BluetoothUuid.uuidToBytes(parcelUuid); + final byte[] uuid = BluetoothUuid.uuidToBytes(parcelUuid); if (uuid.length == BluetoothUuid.UUID_BYTES_16_BIT) { serviceUuids16.write(uuid, 0, uuid.length); @@ -141,31 +140,29 @@ class AdvertiseHelper { if (!data.getServiceData().isEmpty()) { for (ParcelUuid parcelUuid : data.getServiceData().keySet()) { - byte[] serviceData = data.getServiceData().get(parcelUuid); - - byte[] uuid = BluetoothUuid.uuidToBytes(parcelUuid); - int uuidLen = uuid.length; + final byte[] serviceData = data.getServiceData().get(parcelUuid); + final byte[] uuid = BluetoothUuid.uuidToBytes(parcelUuid); + final int uuidLen = uuid.length; + final int dataLen = uuidLen + (serviceData == null ? 0 : serviceData.length); - int dataLen = uuidLen + (serviceData == null ? 0 : serviceData.length); byte[] concatenated = new byte[dataLen]; - System.arraycopy(uuid, 0, concatenated, 0, uuidLen); if (serviceData != null) { System.arraycopy(serviceData, 0, concatenated, uuidLen, serviceData.length); } - if (uuid.length == BluetoothUuid.UUID_BYTES_16_BIT) { + if (uuidLen == BluetoothUuid.UUID_BYTES_16_BIT) { check_length(SERVICE_DATA_16_BIT_UUID, concatenated.length + 1); ret.write(concatenated.length + 1); ret.write(SERVICE_DATA_16_BIT_UUID); ret.write(concatenated, 0, concatenated.length); - } else if (uuid.length == BluetoothUuid.UUID_BYTES_32_BIT) { + } else if (uuidLen == BluetoothUuid.UUID_BYTES_32_BIT) { check_length(SERVICE_DATA_32_BIT_UUID, concatenated.length + 1); ret.write(concatenated.length + 1); ret.write(SERVICE_DATA_32_BIT_UUID); ret.write(concatenated, 0, concatenated.length); - } else /*if (uuid.length == BluetoothUuid.UUID_BYTES_128_BIT)*/ { + } else /*if (uuidLen == BluetoothUuid.UUID_BYTES_128_BIT)*/ { check_length(SERVICE_DATA_128_BIT_UUID, concatenated.length + 1); ret.write(concatenated.length + 1); ret.write(SERVICE_DATA_128_BIT_UUID); @@ -180,7 +177,7 @@ class AdvertiseHelper { ByteArrayOutputStream serviceUuids128 = new ByteArrayOutputStream(); for (ParcelUuid parcelUuid : data.getServiceSolicitationUuids()) { - byte[] uuid = BluetoothUuid.uuidToBytes(parcelUuid); + final byte[] uuid = BluetoothUuid.uuidToBytes(parcelUuid); if (uuid.length == BluetoothUuid.UUID_BYTES_16_BIT) { serviceUuids16.write(uuid, 0, uuid.length); diff --git a/android/app/tests/unit/src/com/android/bluetooth/a2dp/A2dpServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/a2dp/A2dpServiceTest.java index f336cf6a69..5ab01d1b32 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/a2dp/A2dpServiceTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/a2dp/A2dpServiceTest.java @@ -111,8 +111,7 @@ public class A2dpServiceTest { @Parameters(name = "{0}") public static List<FlagsParameterization> getParams() { - return FlagsParameterization.allCombinationsOf( - Flags.FLAG_A2DP_BROADCAST_CONNECTION_STATE_WHEN_TURNED_OFF); + return FlagsParameterization.allCombinationsOf(); } public A2dpServiceTest(FlagsParameterization flags) { @@ -194,11 +193,9 @@ public class A2dpServiceTest { mA2dpService.cleanup(); dispatchAtLeastOneMessage(); - if (Flags.a2dpBroadcastConnectionStateWhenTurnedOff()) { - // Verify that the intent CONNECTION_STATE_CHANGED is generated - // for the existing connections. - verifyConnectionStateIntent(mDevice, STATE_DISCONNECTED, STATE_CONNECTED); - } + // Verify that the intent CONNECTION_STATE_CHANGED is generated + // for the existing connections. + verifyConnectionStateIntent(mDevice, STATE_DISCONNECTED, STATE_CONNECTED); // Verify that setActiveDevice(null) was called during shutdown verify(mMockNativeInterface).setActiveDevice(null); @@ -641,11 +638,9 @@ public class A2dpServiceTest { assertThat(mA2dpService.getDevices()).contains(mDevice); // Device unbond - state machine is not removed mA2dpService.bondStateChangedFromTest(mDevice, BluetoothDevice.BOND_NONE); - if (Flags.a2dpBroadcastConnectionStateWhenTurnedOff()) { - // Verify that the intent CONNECTION_STATE_CHANGED is generated - // for the existing connections. - verifyConnectionStateIntent(mDevice, STATE_DISCONNECTED, STATE_CONNECTING); - } + // Verify that the intent CONNECTION_STATE_CHANGED is generated + // for the existing connections. + verifyConnectionStateIntent(mDevice, STATE_DISCONNECTED, STATE_CONNECTING); assertThat(mA2dpService.getDevices()).doesNotContain(mDevice); // A2DP stack event: CONNECTION_STATE_CONNECTED - state machine is not removed @@ -656,11 +651,9 @@ public class A2dpServiceTest { assertThat(mA2dpService.getDevices()).contains(mDevice); // Device unbond - state machine is not removed mA2dpService.bondStateChangedFromTest(mDevice, BluetoothDevice.BOND_NONE); - if (Flags.a2dpBroadcastConnectionStateWhenTurnedOff()) { - // Verify that the intent CONNECTION_STATE_CHANGED is generated - // for the existing connections. - verifyConnectionStateIntent(mDevice, STATE_DISCONNECTED, STATE_CONNECTED); - } + // Verify that the intent CONNECTION_STATE_CHANGED is generated + // for the existing connections. + verifyConnectionStateIntent(mDevice, STATE_DISCONNECTED, STATE_CONNECTED); assertThat(mA2dpService.getDevices()).doesNotContain(mDevice); // A2DP stack event: CONNECTION_STATE_DISCONNECTING - state machine is not removed @@ -672,11 +665,9 @@ public class A2dpServiceTest { assertThat(mA2dpService.getDevices()).contains(mDevice); // Device unbond - state machine is not removed mA2dpService.bondStateChangedFromTest(mDevice, BluetoothDevice.BOND_NONE); - if (Flags.a2dpBroadcastConnectionStateWhenTurnedOff()) { - // Verify that the intent CONNECTION_STATE_CHANGED is generated - // for the existing connections. - verifyConnectionStateIntent(mDevice, STATE_DISCONNECTED, STATE_DISCONNECTING); - } + // Verify that the intent CONNECTION_STATE_CHANGED is generated + // for the existing connections. + verifyConnectionStateIntent(mDevice, STATE_DISCONNECTED, STATE_DISCONNECTING); assertThat(mA2dpService.getDevices()).doesNotContain(mDevice); // A2DP stack event: CONNECTION_STATE_DISCONNECTED - state machine is not removed diff --git a/android/app/tests/unit/src/com/android/bluetooth/gatt/AdvertiseHelperTest.java b/android/app/tests/unit/src/com/android/bluetooth/gatt/AdvertiseHelperTest.java index 5f89c4b4ca..bf05afed39 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/gatt/AdvertiseHelperTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/gatt/AdvertiseHelperTest.java @@ -40,16 +40,12 @@ public class AdvertiseHelperTest { @Test public void advertiseDataToBytes() throws Exception { byte[] emptyBytes = AdvertiseHelper.advertiseDataToBytes(null, ""); - assertThat(emptyBytes.length).isEqualTo(0); int manufacturerId = 1; byte[] manufacturerData = new byte[] {0x30, 0x31, 0x32, 0x34}; - byte[] serviceData = new byte[] {0x10, 0x12, 0x14}; - byte[] transportDiscoveryData = new byte[] {0x40, 0x44, 0x48}; - AdvertiseData advertiseData = new AdvertiseData.Builder() .setIncludeDeviceName(true) @@ -62,18 +58,16 @@ public class AdvertiseHelperTest { new TransportDiscoveryData(transportDiscoveryData)) .build(); String deviceName = "TestDeviceName"; - int expectedAdvDataBytesLength = 86; - byte[] advDataBytes = AdvertiseHelper.advertiseDataToBytes(advertiseData, deviceName); - - String deviceNameLong = "TestDeviceNameLongTestDeviceName"; + byte[] advDataBytes = AdvertiseHelper.advertiseDataToBytes(advertiseData, deviceName); assertThat(advDataBytes.length).isEqualTo(expectedAdvDataBytesLength); + String deviceNameLong = "TestDeviceNameLongTestDeviceName"; int expectedAdvDataBytesLongNameLength = 98; + byte[] advDataBytesLongName = AdvertiseHelper.advertiseDataToBytes(advertiseData, deviceNameLong); - assertThat(advDataBytesLongName.length).isEqualTo(expectedAdvDataBytesLongNameLength); } diff --git a/android/app/tests/unit/src/com/android/bluetooth/le_scan/ScanBinderTest.java b/android/app/tests/unit/src/com/android/bluetooth/le_scan/ScanBinderTest.java new file mode 100644 index 0000000000..7687fb381d --- /dev/null +++ b/android/app/tests/unit/src/com/android/bluetooth/le_scan/ScanBinderTest.java @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.bluetooth.le_scan; + +import static com.android.bluetooth.TestUtils.MockitoRule; +import static com.android.bluetooth.TestUtils.getTestDevice; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.PendingIntent; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothManager; +import android.bluetooth.le.IPeriodicAdvertisingCallback; +import android.bluetooth.le.IScannerCallback; +import android.bluetooth.le.ScanFilter; +import android.bluetooth.le.ScanResult; +import android.bluetooth.le.ScanSettings; +import android.content.AttributionSource; +import android.content.Intent; +import android.os.WorkSource; + +import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; + +import java.util.ArrayList; +import java.util.List; + +/** Test cases for {@link ScanBinder}. */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class ScanBinderTest { + @Rule public final MockitoRule mMockitoRule = new MockitoRule(); + + @Mock private ScanController mScanController; + + private final AttributionSource mAttributionSource = + InstrumentationRegistry.getInstrumentation() + .getTargetContext() + .getSystemService(BluetoothManager.class) + .getAdapter() + .getAttributionSource(); + private final BluetoothDevice mDevice = getTestDevice(89); + private ScanBinder mBinder; + + @Before + public void setUp() { + when(mScanController.isAvailable()).thenReturn(true); + mBinder = new ScanBinder(mScanController); + } + + @Test + public void registerScanner() { + IScannerCallback callback = mock(IScannerCallback.class); + WorkSource workSource = mock(WorkSource.class); + + mBinder.registerScanner(callback, workSource, mAttributionSource); + verify(mScanController).registerScanner(callback, workSource, mAttributionSource); + } + + @Test + public void unregisterScanner() { + int scannerId = 1; + + mBinder.unregisterScanner(scannerId, mAttributionSource); + verify(mScanController).unregisterScanner(scannerId, mAttributionSource); + } + + @Test + public void startScan() { + int scannerId = 1; + ScanSettings settings = new ScanSettings.Builder().build(); + List<ScanFilter> filters = new ArrayList<>(); + + mBinder.startScan(scannerId, settings, filters, mAttributionSource); + verify(mScanController).startScan(scannerId, settings, filters, mAttributionSource); + } + + @Test + public void startScanForIntent() { + PendingIntent intent = + PendingIntent.getBroadcast( + InstrumentationRegistry.getInstrumentation().getTargetContext(), + 0, + new Intent(), + PendingIntent.FLAG_IMMUTABLE); + ScanSettings settings = new ScanSettings.Builder().build(); + List<ScanFilter> filters = new ArrayList<>(); + + mBinder.startScanForIntent(intent, settings, filters, mAttributionSource); + verify(mScanController) + .registerPiAndStartScan(intent, settings, filters, mAttributionSource); + } + + @Test + public void stopScan_withScannerId() { + int scannerId = 1; + + mBinder.stopScan(scannerId, mAttributionSource); + verify(mScanController).stopScan(scannerId, mAttributionSource); + } + + @Test + public void stopScan_withIntent() { + PendingIntent intent = + PendingIntent.getBroadcast( + InstrumentationRegistry.getInstrumentation().getTargetContext(), + 0, + new Intent(), + PendingIntent.FLAG_IMMUTABLE); + + mBinder.stopScanForIntent(intent, mAttributionSource); + verify(mScanController).stopScan(intent, mAttributionSource); + } + + @Test + public void flushPendingBatchResults() { + int scannerId = 1; + + mBinder.flushPendingBatchResults(scannerId, mAttributionSource); + verify(mScanController).flushPendingBatchResults(scannerId, mAttributionSource); + } + + @Test + public void registerSync() { + ScanResult scanResult = new ScanResult(mDevice, null, 0, 0); + int skip = 1; + int timeout = 2; + IPeriodicAdvertisingCallback callback = mock(IPeriodicAdvertisingCallback.class); + + mBinder.registerSync(scanResult, skip, timeout, callback, mAttributionSource); + verify(mScanController) + .registerSync(scanResult, skip, timeout, callback, mAttributionSource); + } + + @Test + public void unregisterSync() { + IPeriodicAdvertisingCallback callback = mock(IPeriodicAdvertisingCallback.class); + + mBinder.unregisterSync(callback, mAttributionSource); + verify(mScanController).unregisterSync(callback, mAttributionSource); + } + + @Test + public void transferSync() { + int serviceData = 1; + int syncHandle = 2; + + mBinder.transferSync(mDevice, serviceData, syncHandle, mAttributionSource); + verify(mScanController).transferSync(mDevice, serviceData, syncHandle, mAttributionSource); + } + + @Test + public void transferSetInfo() { + int serviceData = 1; + int advHandle = 2; + IPeriodicAdvertisingCallback callback = mock(IPeriodicAdvertisingCallback.class); + + mBinder.transferSetInfo(mDevice, serviceData, advHandle, callback, mAttributionSource); + verify(mScanController) + .transferSetInfo(mDevice, serviceData, advHandle, callback, mAttributionSource); + } + + @Test + public void numHwTrackFiltersAvailable() { + mBinder.numHwTrackFiltersAvailable(mAttributionSource); + verify(mScanController).numHwTrackFiltersAvailable(mAttributionSource); + } +} diff --git a/android/leaudio/app/.classpath b/android/leaudio/app/.classpath index 4a04201ca2..bbe97e501d 100644 --- a/android/leaudio/app/.classpath +++ b/android/leaudio/app/.classpath @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> <classpath> - <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11/"/> + <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17/"/> <classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/> <classpathentry kind="output" path="bin/default"/> </classpath> diff --git a/android/leaudio/gradle/wrapper/gradle-wrapper.jar b/android/leaudio/gradle/wrapper/gradle-wrapper.jar Binary files differindex f6b961fd5a..41d9927a4d 100644 --- a/android/leaudio/gradle/wrapper/gradle-wrapper.jar +++ b/android/leaudio/gradle/wrapper/gradle-wrapper.jar diff --git a/android/leaudio/gradle/wrapper/gradle-wrapper.properties b/android/leaudio/gradle/wrapper/gradle-wrapper.properties index bd98b5dbe3..aa991fceae 100644 --- a/android/leaudio/gradle/wrapper/gradle-wrapper.properties +++ b/android/leaudio/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Thu Oct 15 09:37:54 CEST 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip diff --git a/android/leaudio/gradlew b/android/leaudio/gradlew index cccdd3d517..1b6c787337 100755 --- a/android/leaudio/gradlew +++ b/android/leaudio/gradlew @@ -1,78 +1,129 @@ -#!/usr/bin/env sh +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +APP_BASE_NAME=${0##*/} # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -81,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" + JAVACMD=java which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the @@ -89,84 +140,95 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=$((i+1)) + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=$(save "$@") - -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" - -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" -fi +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" diff --git a/android/leaudio/gradlew.bat b/android/leaudio/gradlew.bat index e95643d6a2..ac1b06f938 100644 --- a/android/leaudio/gradlew.bat +++ b/android/leaudio/gradlew.bat @@ -1,3 +1,19 @@ +@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@@ -13,15 +29,18 @@ if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-set DEFAULT_JVM_OPTS=
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
-if "%ERRORLEVEL%" == "0" goto init
+if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@@ -35,7 +54,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
-if exist "%JAVA_EXE%" goto init
+if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
@@ -45,28 +64,14 @@ echo location of your Java installation. goto fail
-:init
-@rem Get command-line arguments, handling Windows variants
-
-if not "%OS%" == "Windows_NT" goto win9xME_args
-
-:win9xME_args
-@rem Slurp the command line arguments.
-set CMD_LINE_ARGS=
-set _SKIP=2
-
-:win9xME_args_slurp
-if "x%~1" == "x" goto execute
-
-set CMD_LINE_ARGS=%*
-
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
@rem Execute Gradle
-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
diff --git a/flags/a2dp.aconfig b/flags/a2dp.aconfig index ccf46f8bed..e7b9aa21b0 100644 --- a/flags/a2dp.aconfig +++ b/flags/a2dp.aconfig @@ -39,16 +39,6 @@ flag { } flag { - name: "a2dp_broadcast_connection_state_when_turned_off" - namespace: "bluetooth" - description: "Broadcast CONNECTION_STATE_CHANGED when A2dpService is turned off while a device is connected" - bug: "360034472" - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { name: "fix_avdt_rconfig_not_setting_l2cap" namespace: "bluetooth" description: "Set L2CAP flushable and high priority after A2DP reconfigure" @@ -59,26 +49,6 @@ flag { } flag { - name: "a2dp_source_threading_fix" - namespace: "bluetooth" - description: "Schedule A2DP source setup operations to bt_main_thread to prevent races" - bug: "374166531" - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { - name: "a2dp_clear_pending_start_on_session_restart" - namespace: "bluetooth" - description: "Clear the kPendingStart flag when the audio session is restarted for codec reconfiguration" - bug: "378524655" - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { name: "avdt_handle_suspend_cfm_bad_state" namespace: "bluetooth" description: "Close connection on AVDTP Suspend Confirmation with BAD STATE error" diff --git a/flags/system_service.aconfig b/flags/system_service.aconfig index c72b74a7a9..ea40eb33de 100644 --- a/flags/system_service.aconfig +++ b/flags/system_service.aconfig @@ -17,3 +17,13 @@ flag { description: "Replace binder call to the system server with a Messenger to enforce thread safety" bug: "321804999" } + +flag { + name: "system_server_remove_extra_thread_jump" + namespace: "bluetooth" + description: "Methods are unnecessarily re-posting on the system server thread" + bug: "402209603" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/service/src/com/android/server/bluetooth/BluetoothManagerService.java b/service/src/com/android/server/bluetooth/BluetoothManagerService.java index c5e64dfd5a..08a949d81a 100644 --- a/service/src/com/android/server/bluetooth/BluetoothManagerService.java +++ b/service/src/com/android/server/bluetooth/BluetoothManagerService.java @@ -1271,68 +1271,20 @@ class BluetoothManagerService { break; case MESSAGE_HANDLE_ENABLE_DELAYED: - // The Bluetooth is turning off, wait for STATE_OFF - if (!mState.oneOf(STATE_OFF)) { - if (mWaitForEnableRetry < MAX_WAIT_FOR_ENABLE_DISABLE_RETRIES) { - mWaitForEnableRetry++; - mHandler.sendEmptyMessageDelayed( - MESSAGE_HANDLE_ENABLE_DELAYED, ENABLE_DISABLE_DELAY_MS); - break; - } else { - Log.e(TAG, "Wait for STATE_OFF timeout"); - } - } - // Either state is changed to STATE_OFF or reaches the maximum retry, we - // should move forward to the next step. - mWaitForEnableRetry = 0; - mHandler.sendEmptyMessageDelayed( - MESSAGE_RESTART_BLUETOOTH_SERVICE, getServiceRestartMs()); - Log.d(TAG, "Handle enable is finished"); + Log.d(TAG, "MESSAGE_HANDLE_ENABLE_DELAYED: mAdapter=" + mAdapter); + + handleEnableDelayed(); break; case MESSAGE_HANDLE_DISABLE_DELAYED: boolean disabling = (msg.arg1 == 1); - Log.d(TAG, "MESSAGE_HANDLE_DISABLE_DELAYED: disabling:" + disabling); - if (!disabling) { - // The Bluetooth is turning on, wait for STATE_ON - if (!mState.oneOf(STATE_ON)) { - if (mWaitForDisableRetry < MAX_WAIT_FOR_ENABLE_DISABLE_RETRIES) { - mWaitForDisableRetry++; - mHandler.sendEmptyMessageDelayed( - MESSAGE_HANDLE_DISABLE_DELAYED, ENABLE_DISABLE_DELAY_MS); - break; - } else { - Log.e(TAG, "Wait for STATE_ON timeout"); - } - } - // Either state is changed to STATE_ON or reaches the maximum retry, we - // should move forward to the next step. - mWaitForDisableRetry = 0; - mEnable = false; - onToBleOn(); - // Wait for state exiting STATE_ON - Message disableDelayedMsg = - mHandler.obtainMessage(MESSAGE_HANDLE_DISABLE_DELAYED, 1, 0); - mHandler.sendMessageDelayed(disableDelayedMsg, ENABLE_DISABLE_DELAY_MS); - } else { - // The Bluetooth is turning off, wait for exiting STATE_ON - if (mState.oneOf(STATE_ON)) { - if (mWaitForDisableRetry < MAX_WAIT_FOR_ENABLE_DISABLE_RETRIES) { - mWaitForDisableRetry++; - Message disableDelayedMsg = - mHandler.obtainMessage( - MESSAGE_HANDLE_DISABLE_DELAYED, 1, 0); - mHandler.sendMessageDelayed( - disableDelayedMsg, ENABLE_DISABLE_DELAY_MS); - break; - } else { - Log.e(TAG, "Wait for exiting STATE_ON timeout"); - } - } - // Either state is exited from STATE_ON or reaches the maximum retry, we - // should move forward to the next step. - Log.d(TAG, "Handle disable is finished"); - } + + Log.d( + TAG, + ("MESSAGE_HANDLE_DISABLE_DELAYED(disabling=" + disabling + ")") + + (": mAdapter=" + mAdapter)); + + handleDisableDelayed(disabling); break; case MESSAGE_RESTORE_USER_SETTING_OFF: @@ -1684,6 +1636,66 @@ class BluetoothManagerService { } } + private void handleEnableDelayed() { + // The Bluetooth is turning off, wait for STATE_OFF + if (!mState.oneOf(STATE_OFF)) { + if (mWaitForEnableRetry < MAX_WAIT_FOR_ENABLE_DISABLE_RETRIES) { + mWaitForEnableRetry++; + mHandler.sendEmptyMessageDelayed( + MESSAGE_HANDLE_ENABLE_DELAYED, ENABLE_DISABLE_DELAY_MS); + return; + } else { + Log.e(TAG, "Wait for STATE_OFF timeout"); + } + } + // Either state is changed to STATE_OFF or reaches the maximum retry, we + // should move forward to the next step. + mWaitForEnableRetry = 0; + mHandler.sendEmptyMessageDelayed(MESSAGE_RESTART_BLUETOOTH_SERVICE, getServiceRestartMs()); + Log.d(TAG, "Handle enable is finished"); + } + + private void handleDisableDelayed(boolean disabling) { + if (!disabling) { + // The Bluetooth is turning on, wait for STATE_ON + if (!mState.oneOf(STATE_ON)) { + if (mWaitForDisableRetry < MAX_WAIT_FOR_ENABLE_DISABLE_RETRIES) { + mWaitForDisableRetry++; + mHandler.sendEmptyMessageDelayed( + MESSAGE_HANDLE_DISABLE_DELAYED, ENABLE_DISABLE_DELAY_MS); + return; + } else { + Log.e(TAG, "Wait for STATE_ON timeout"); + } + } + // Either state is changed to STATE_ON or reaches the maximum retry, we + // should move forward to the next step. + mWaitForDisableRetry = 0; + mEnable = false; + onToBleOn(); + // Wait for state exiting STATE_ON + Message disableDelayedMsg = + mHandler.obtainMessage(MESSAGE_HANDLE_DISABLE_DELAYED, 1, 0); + mHandler.sendMessageDelayed(disableDelayedMsg, ENABLE_DISABLE_DELAY_MS); + } else { + // The Bluetooth is turning off, wait for exiting STATE_ON + if (mState.oneOf(STATE_ON)) { + if (mWaitForDisableRetry < MAX_WAIT_FOR_ENABLE_DISABLE_RETRIES) { + mWaitForDisableRetry++; + Message disableDelayedMsg = + mHandler.obtainMessage(MESSAGE_HANDLE_DISABLE_DELAYED, 1, 0); + mHandler.sendMessageDelayed(disableDelayedMsg, ENABLE_DISABLE_DELAY_MS); + return; + } else { + Log.e(TAG, "Wait for exiting STATE_ON timeout"); + } + } + // Either state is exited from STATE_ON or reaches the maximum retry, we + // should move forward to the next step. + Log.d(TAG, "Handle disable is finished"); + } + } + private void offToBleOn() { if (!mState.oneOf(STATE_OFF)) { Log.e(TAG, "offToBleOn: Impossible transition from " + mState); diff --git a/system/btif/src/btif_a2dp_source.cc b/system/btif/src/btif_a2dp_source.cc index b8ace80b07..cf7b7f1957 100644 --- a/system/btif/src/btif_a2dp_source.cc +++ b/system/btif/src/btif_a2dp_source.cc @@ -329,19 +329,13 @@ static void btif_a2dp_source_accumulate_stats(BtifMediaStats* src, BtifMediaStat src->Reset(); } -/// Select the thread to run a2dp source actions on (a2dp encoder excluded). -static bluetooth::common::MessageLoopThread* local_thread() { - return com::android::bluetooth::flags::a2dp_source_threading_fix() ? get_main_thread() - : &btif_a2dp_source_thread; -} - bool btif_a2dp_source_init(void) { log::info(""); // Start A2DP Source media task btif_a2dp_source_thread.StartUp(); - local_thread()->DoInThread(base::BindOnce(&btif_a2dp_source_init_delayed)); + do_in_main_thread(base::BindOnce(&btif_a2dp_source_init_delayed)); return true; } @@ -422,7 +416,7 @@ static void btif_a2dp_source_init_delayed(void) { // the provider needs to be initialized earlier in order to ensure // get_a2dp_configuration and parse_a2dp_configuration can be // invoked before the stream is started. - bluetooth::audio::a2dp::init(local_thread(), &a2dp_stream_callbacks, + bluetooth::audio::a2dp::init(get_main_thread(), &a2dp_stream_callbacks, btif_av_is_a2dp_offload_enabled()); } @@ -439,7 +433,7 @@ static bool btif_a2dp_source_startup(void) { btif_a2dp_source_cb.tx_audio_queue = fixed_queue_new(SIZE_MAX); // Schedule the rest of the operations - local_thread()->DoInThread(base::BindOnce(&btif_a2dp_source_startup_delayed)); + do_in_main_thread(base::BindOnce(&btif_a2dp_source_startup_delayed)); return true; } @@ -451,7 +445,7 @@ static void btif_a2dp_source_startup_delayed() { log::fatal("unable to enable real time scheduling"); #endif } - if (!bluetooth::audio::a2dp::init(local_thread(), &a2dp_stream_callbacks, + if (!bluetooth::audio::a2dp::init(get_main_thread(), &a2dp_stream_callbacks, btif_av_is_a2dp_offload_enabled())) { log::warn("Failed to setup the bluetooth audio HAL"); } @@ -464,15 +458,14 @@ bool btif_a2dp_source_start_session(const RawAddress& peer_address, btif_a2dp_source_audio_tx_flush_req(); - if (local_thread()->DoInThread(base::BindOnce(&btif_a2dp_source_start_session_delayed, - peer_address, std::move(peer_ready_promise)))) { - return true; - } else { - // cannot set promise but triggers crash + if (do_in_main_thread(base::BindOnce(&btif_a2dp_source_start_session_delayed, peer_address, + std::move(peer_ready_promise))) != BT_STATUS_SUCCESS) { log::fatal("peer_address={} state={} fails to context switch", peer_address, btif_a2dp_source_cb.StateStr()); return false; } + + return true; } static void btif_a2dp_source_start_session_delayed(const RawAddress& peer_address, @@ -523,13 +516,8 @@ bool btif_a2dp_source_restart_session(const RawAddress& old_peer_address, bool btif_a2dp_source_end_session(const RawAddress& peer_address) { log::info("peer_address={} state={}", peer_address, btif_a2dp_source_cb.StateStr()); - if (com::android::bluetooth::flags::a2dp_source_threading_fix()) { - btif_a2dp_source_cleanup_codec(); - btif_a2dp_source_end_session_delayed(peer_address); - } else { - local_thread()->DoInThread(base::BindOnce(&btif_a2dp_source_end_session_delayed, peer_address)); - btif_a2dp_source_cleanup_codec(); - } + btif_a2dp_source_cleanup_codec(); + btif_a2dp_source_end_session_delayed(peer_address); return true; } @@ -549,7 +537,7 @@ static void btif_a2dp_source_end_session_delayed(const RawAddress& peer_address) void btif_a2dp_source_allow_low_latency_audio(bool allowed) { log::info("allowed={}", allowed); - local_thread()->DoInThread( + do_in_main_thread( base::BindOnce(bluetooth::audio::a2dp::set_audio_low_latency_mode_allowed, allowed)); } @@ -564,13 +552,7 @@ void btif_a2dp_source_shutdown(std::promise<void> shutdown_complete_promise) { /* Make sure no channels are restarted while shutting down */ btif_a2dp_source_cb.SetState(BtifA2dpSource::kStateShuttingDown); - // TODO(b/374166531) Remove the check for get_main_thread. - if (local_thread() != get_main_thread()) { - local_thread()->DoInThread(base::BindOnce(&btif_a2dp_source_shutdown_delayed, - std::move(shutdown_complete_promise))); - } else { - btif_a2dp_source_shutdown_delayed(std::move(shutdown_complete_promise)); - } + btif_a2dp_source_shutdown_delayed(std::move(shutdown_complete_promise)); } static void btif_a2dp_source_shutdown_delayed(std::promise<void> shutdown_complete_promise) { @@ -680,7 +662,7 @@ static void btif_a2dp_source_cleanup_codec() { log::info("state={}", btif_a2dp_source_cb.StateStr()); // Must stop media task first before cleaning up the encoder btif_a2dp_source_stop_audio_req(); - local_thread()->DoInThread(base::BindOnce(&btif_a2dp_source_cleanup_codec_delayed)); + do_in_main_thread(base::BindOnce(&btif_a2dp_source_cleanup_codec_delayed)); } static void btif_a2dp_source_cleanup_codec_delayed() { @@ -694,13 +676,13 @@ static void btif_a2dp_source_cleanup_codec_delayed() { void btif_a2dp_source_start_audio_req(void) { log::info("state={}", btif_a2dp_source_cb.StateStr()); - local_thread()->DoInThread(base::BindOnce(&btif_a2dp_source_audio_tx_start_event)); + do_in_main_thread(base::BindOnce(&btif_a2dp_source_audio_tx_start_event)); } void btif_a2dp_source_stop_audio_req(void) { log::info("state={}", btif_a2dp_source_cb.StateStr()); - local_thread()->DoInThread(base::BindOnce(&btif_a2dp_source_audio_tx_stop_event)); + do_in_main_thread(base::BindOnce(&btif_a2dp_source_audio_tx_stop_event)); } void btif_a2dp_source_encoder_user_config_update_req( @@ -710,9 +692,9 @@ void btif_a2dp_source_encoder_user_config_update_req( log::info("peer_address={} state={} {} codec_preference(s)", peer_address, btif_a2dp_source_cb.StateStr(), codec_user_preferences.size()); - if (!local_thread()->DoInThread( - base::BindOnce(&btif_a2dp_source_encoder_user_config_update_event, peer_address, - codec_user_preferences, std::move(peer_ready_promise)))) { + if (do_in_main_thread(base::BindOnce(&btif_a2dp_source_encoder_user_config_update_event, + peer_address, codec_user_preferences, + std::move(peer_ready_promise))) != BT_STATUS_SUCCESS) { // cannot set promise but triggers crash log::fatal("peer_address={} state={} fails to context switch", peer_address, btif_a2dp_source_cb.StateStr()); @@ -755,7 +737,7 @@ static void btif_a2dp_source_encoder_user_config_update_event( void btif_a2dp_source_feeding_update_req(const btav_a2dp_codec_config_t& codec_audio_config) { log::info("state={}", btif_a2dp_source_cb.StateStr()); - local_thread()->DoInThread( + do_in_main_thread( base::BindOnce(&btif_a2dp_source_audio_feeding_update_event, codec_audio_config)); } @@ -1069,7 +1051,7 @@ static void btif_a2dp_source_audio_tx_flush_event(void) { static bool btif_a2dp_source_audio_tx_flush_req(void) { log::info("state={}", btif_a2dp_source_cb.StateStr()); - local_thread()->DoInThread(base::BindOnce(&btif_a2dp_source_audio_tx_flush_event)); + do_in_main_thread(base::BindOnce(&btif_a2dp_source_audio_tx_flush_event)); return true; } diff --git a/system/btif/src/btif_av.cc b/system/btif/src/btif_av.cc index e4f784a39c..101ebcc4a0 100644 --- a/system/btif/src/btif_av.cc +++ b/system/btif/src/btif_av.cc @@ -2400,12 +2400,10 @@ bool BtifAvStateMachine::StateOpened::ProcessEvent(uint32_t event, void* p_data) std::promise<void> peer_ready_promise; std::future<void> peer_ready_future = peer_ready_promise.get_future(); - if (com::android::bluetooth::flags::a2dp_clear_pending_start_on_session_restart()) { - // The stream may not be restarted without an explicit request from the - // Bluetooth Audio HAL. Any start request that was pending before the - // reconfiguration is invalidated when the session is ended. - peer_.ClearFlags(BtifAvPeer::kFlagPendingStart); - } + // The stream may not be restarted without an explicit request from the + // Bluetooth Audio HAL. Any start request that was pending before the + // reconfiguration is invalidated when the session is ended. + peer_.ClearFlags(BtifAvPeer::kFlagPendingStart); btif_a2dp_source_start_session(peer_.PeerAddress(), std::move(peer_ready_promise)); } diff --git a/system/gd/hci/distance_measurement_manager.cc b/system/gd/hci/distance_measurement_manager.cc index 4fe3449b6b..c4761c9646 100644 --- a/system/gd/hci/distance_measurement_manager.cc +++ b/system/gd/hci/distance_measurement_manager.cc @@ -337,7 +337,11 @@ struct DistanceMeasurementManager::impl : bluetooth::hal::RangingHalCallback { } } - void stop() { hci_layer_->UnregisterLeEventHandler(hci::SubeventCode::TRANSMIT_POWER_REPORTING); } + void stop() { + hci_layer_->UnregisterLeEventHandler(hci::SubeventCode::TRANSMIT_POWER_REPORTING); + cs_requester_trackers_.clear(); + cs_responder_trackers_.clear(); + } void register_distance_measurement_callbacks(DistanceMeasurementCallbacks* callbacks) { distance_measurement_callbacks_ = callbacks; @@ -2618,17 +2622,17 @@ struct DistanceMeasurementManager::impl : bluetooth::hal::RangingHalCallback { v1.insert(v1.end(), v2.begin(), v2.end()); } - os::Handler* handler_; - hal::RangingHal* ranging_hal_; - hci::Controller* controller_; - hci::HciLayer* hci_layer_; - hci::AclManager* acl_manager_; - hci::DistanceMeasurementInterface* distance_measurement_interface_; + os::Handler* handler_ = nullptr; + hal::RangingHal* ranging_hal_ = nullptr; + hci::Controller* controller_ = nullptr; + hci::HciLayer* hci_layer_ = nullptr; + hci::AclManager* acl_manager_ = nullptr; + hci::DistanceMeasurementInterface* distance_measurement_interface_ = nullptr; std::unordered_map<Address, RSSITracker> rssi_trackers; std::unordered_map<uint16_t, CsTracker> cs_requester_trackers_; std::unordered_map<uint16_t, CsTracker> cs_responder_trackers_; std::unordered_map<uint16_t, uint16_t> gatt_mtus_; - DistanceMeasurementCallbacks* distance_measurement_callbacks_; + DistanceMeasurementCallbacks* distance_measurement_callbacks_ = nullptr; CsOptionalSubfeaturesSupported cs_subfeature_supported_; uint8_t num_antennas_supported_ = 0x01; bool local_support_phase_based_ranging_ = false; diff --git a/system/gd/hci/distance_measurement_manager_mock.h b/system/gd/hci/distance_measurement_manager_mock.h index 827c096670..f08c6db45a 100644 --- a/system/gd/hci/distance_measurement_manager_mock.h +++ b/system/gd/hci/distance_measurement_manager_mock.h @@ -31,12 +31,22 @@ struct DistanceMeasurementManager::impl : public bluetooth::hci::LeAddressManage namespace testing { class MockDistanceMeasurementCallbacks : public DistanceMeasurementCallbacks { +public: MOCK_METHOD(void, OnDistanceMeasurementStarted, (Address, DistanceMeasurementMethod)); MOCK_METHOD(void, OnDistanceMeasurementStopped, (Address, DistanceMeasurementErrorCode, DistanceMeasurementMethod)); MOCK_METHOD(void, OnDistanceMeasurementResult, (Address, uint32_t, uint32_t, int, int, int, int, uint64_t, int8_t, double, DistanceMeasurementDetectedAttackLevel, double, DistanceMeasurementMethod)); + MOCK_METHOD(void, OnRasFragmentReady, + (Address address, uint16_t procedure_counter, bool is_last, + std::vector<uint8_t> raw_data)); + MOCK_METHOD(void, OnVendorSpecificCharacteristics, + (std::vector<hal::VendorSpecificCharacteristic> vendor_specific_characteristics)); + MOCK_METHOD(void, OnVendorSpecificReply, + (Address address, std::vector<bluetooth::hal::VendorSpecificCharacteristic> + vendor_specific_characteristics)); + MOCK_METHOD(void, OnHandleVendorSpecificReplyComplete, (Address address, bool success)); }; class MockDistanceMeasurementManager : public DistanceMeasurementManager { diff --git a/system/gd/hci/distance_measurement_manager_test.cc b/system/gd/hci/distance_measurement_manager_test.cc index b7f2abe8b3..160144bda7 100644 --- a/system/gd/hci/distance_measurement_manager_test.cc +++ b/system/gd/hci/distance_measurement_manager_test.cc @@ -24,14 +24,23 @@ #include "hal/ranging_hal.h" #include "hal/ranging_hal_mock.h" #include "hci/acl_manager_mock.h" +#include "hci/address.h" #include "hci/controller.h" #include "hci/controller_mock.h" +#include "hci/distance_measurement_manager_mock.h" #include "hci/hci_layer.h" #include "hci/hci_layer_fake.h" #include "module.h" +#include "packet/packet_view.h" +#include "ras/ras_packets.h" +using testing::AtLeast; using testing::Return; +namespace { +constexpr auto kTimeout = std::chrono::seconds(1); +} + namespace bluetooth { namespace hci { namespace { @@ -49,6 +58,57 @@ protected: void ListDependencies(ModuleList* /* list */) const override {} }; +struct CsReadCapabilitiesCompleteEvent { + ErrorCode error_code = ErrorCode::SUCCESS; + uint8_t num_config_supported = 4; + uint16_t max_consecutive_procedures_supported = 0; + uint8_t num_antennas_supported = 2; + uint8_t max_antenna_paths_supported = 4; + CsRoleSupported roles_supported = {/*initiator=*/1, /*reflector=*/1}; + CsOptionalModesSupported modes_supported = {/*mode_3=*/1}; + CsRttCapability rtt_capability = {/*rtt_aa_only_n=*/1, /*rtt_sounding_n=*/1, + /*rtt_random_payload_n=*/1}; + uint8_t rtt_aa_only_n = 1; + uint8_t rtt_sounding_n = 1; + uint8_t rtt_random_payload_n = 1; + CsOptionalNadmSoundingCapability nadm_sounding_capability = { + /*normalized_attack_detector_metric=*/1}; + CsOptionalNadmRandomCapability nadm_random_capability = {/*normalized_attack_detector_metric=*/1}; + CsOptionalCsSyncPhysSupported cs_sync_phys_supported = {/*le_2m_phy=*/1}; + CsOptionalSubfeaturesSupported subfeatures_supported = {/*no_frequency_actuation_error=*/1, + /*channel_selection_algorithm=*/1, + /*phase_based_ranging=*/1}; + CsOptionalTIp1TimesSupported t_ip1_times_supported = { + /*support_10_microsecond=*/1, /*support_20_microsecond=*/1, + /*support_30_microsecond=*/1, /*support_40_microsecond=*/1, + /*support_50_microsecond=*/1, /*support_60_microsecond=*/1, + /*support_80_microsecond=*/1}; + CsOptionalTIp2TimesSupported t_ip2_times_supported = { + /*support_10_microsecond=*/1, /*support_20_microsecond=*/1, + /*support_30_microsecond=*/1, + /*support_40_microsecond=*/1, /*support_50_microsecond=*/1, + /*support_60_microsecond=*/1, /*support_80_microsecond=*/1}; + CsOptionalTFcsTimesSupported t_fcs_times_supported = { + /*support_15_microsecond=*/1, /*support_20_microsecond=*/1, + /*support_30_microsecond=*/1, /*support_40_microsecond=*/1, + /*support_50_microsecond=*/1, + /*support_60_microsecond=*/1, /*support_80_microsecond=*/1, + /*support_100_microsecond=*/1, + /*support_120_microsecond=*/1}; + CsOptionalTPmTimesSupported t_pm_times_supported = {/*support_10_microsecond=*/1, + /*support_20_microsecond=*/1}; + uint8_t t_sw_time_supported = 1; + uint8_t tx_snr_capability = 1; +}; + +struct StartMeasurementParameters { + Address remote_address = Address::FromString("12:34:56:78:9a:bc").value(); + uint16_t connection_handle = 64; + Role local_hci_role = Role::CENTRAL; + uint16_t interval = 200; // 200ms + DistanceMeasurementMethod method = DistanceMeasurementMethod::METHOD_CS; +}; + class DistanceMeasurementManagerTest : public ::testing::Test { protected: void SetUp() override { @@ -65,10 +125,12 @@ protected: ASSERT_NE(client_handler_, nullptr); EXPECT_CALL(*mock_controller_, SupportsBleChannelSounding()).WillOnce(Return(true)); - EXPECT_CALL(*mock_ranging_hal_, IsBound()).WillOnce(Return(true)); + EXPECT_CALL(*mock_ranging_hal_, IsBound()).Times(AtLeast(1)).WillRepeatedly(Return(true)); handler_ = fake_registry_.GetTestHandler(); dm_manager_ = fake_registry_.Start<DistanceMeasurementManager>(&thread_, handler_); + + dm_manager_->RegisterDistanceMeasurementCallbacks(&mock_dm_callbacks_); } void TearDown() override { @@ -77,6 +139,44 @@ protected: fake_registry_.StopAll(); } + std::future<void> GetDmSessionFuture() { + log::assert_that(dm_session_promise_ == nullptr, "Promises promises ... Only one at a time"); + dm_session_promise_ = std::make_unique<std::promise<void>>(); + return dm_session_promise_->get_future(); + } + + void sync_client_handler() { + log::assert_that(thread_.GetReactor()->WaitForIdle(kTimeout), + "assert failed: thread_.GetReactor()->WaitForIdle(kTimeout)"); + } + + static std::unique_ptr<LeCsReadLocalSupportedCapabilitiesCompleteBuilder> + GetLocalSupportedCapabilitiesCompleteEvent( + const CsReadCapabilitiesCompleteEvent& cs_cap_complete_event) { + return LeCsReadLocalSupportedCapabilitiesCompleteBuilder::Create( + /*num_hci_command_packets=*/0xFF, cs_cap_complete_event.error_code, + cs_cap_complete_event.num_config_supported, + cs_cap_complete_event.max_consecutive_procedures_supported, + cs_cap_complete_event.num_antennas_supported, + cs_cap_complete_event.max_antenna_paths_supported, + cs_cap_complete_event.roles_supported, cs_cap_complete_event.modes_supported, + cs_cap_complete_event.rtt_capability, cs_cap_complete_event.rtt_aa_only_n, + cs_cap_complete_event.rtt_sounding_n, cs_cap_complete_event.rtt_random_payload_n, + cs_cap_complete_event.nadm_sounding_capability, + cs_cap_complete_event.nadm_random_capability, + cs_cap_complete_event.cs_sync_phys_supported, + cs_cap_complete_event.subfeatures_supported, + cs_cap_complete_event.t_ip1_times_supported, + cs_cap_complete_event.t_ip2_times_supported, + cs_cap_complete_event.t_fcs_times_supported, cs_cap_complete_event.t_pm_times_supported, + cs_cap_complete_event.t_sw_time_supported, cs_cap_complete_event.tx_snr_capability); + } + + void StartMeasurement(const StartMeasurementParameters& params) { + dm_manager_->StartDistanceMeasurement(params.remote_address, params.connection_handle, + params.local_hci_role, params.interval, params.method); + } + protected: TestModuleRegistry fake_registry_; HciLayerFake* test_hci_layer_ = nullptr; @@ -88,12 +188,38 @@ protected: os::Handler* handler_ = nullptr; DistanceMeasurementManager* dm_manager_ = nullptr; + testing::MockDistanceMeasurementCallbacks mock_dm_callbacks_; + std::unique_ptr<std::promise<void>> dm_session_promise_; }; TEST_F(DistanceMeasurementManagerTest, setup_teardown) { EXPECT_NE(mock_ranging_hal_->GetRangingHalCallback(), nullptr); } +TEST_F(DistanceMeasurementManagerTest, fail_read_local_cs_capabilities) { + StartMeasurementParameters params; + auto dm_session_future = GetDmSessionFuture(); + EXPECT_CALL(mock_dm_callbacks_, + OnDistanceMeasurementStopped(params.remote_address, + DistanceMeasurementErrorCode::REASON_INTERNAL_ERROR, + DistanceMeasurementMethod::METHOD_CS)) + .WillOnce([this](const Address& /*address*/, DistanceMeasurementErrorCode /*error_code*/, + DistanceMeasurementMethod /*method*/) { + ASSERT_NE(dm_session_promise_, nullptr); + dm_session_promise_->set_value(); + dm_session_promise_.reset(); + }); + + CsReadCapabilitiesCompleteEvent read_cs_complete_event; + read_cs_complete_event.error_code = ErrorCode::COMMAND_DISALLOWED; + test_hci_layer_->IncomingEvent( + GetLocalSupportedCapabilitiesCompleteEvent(read_cs_complete_event)); + + StartMeasurement(params); + + dm_session_future.wait_for(kTimeout); + sync_client_handler(); +} } // namespace } // namespace hci } // namespace bluetooth diff --git a/system/main/shim/stack.cc b/system/main/shim/stack.cc index 14447d75b3..bb8994aee0 100644 --- a/system/main/shim/stack.cc +++ b/system/main/shim/stack.cc @@ -169,8 +169,11 @@ void Stack::Stop() { log::assert_that(is_running_, "Gd stack not running"); is_running_ = false; - stack_handler_->Clear(); - + if (!com::android::bluetooth::flags::same_handler_for_all_modules()) { + // Clear the handler only if the flag is not defined, otherwise it will be cleared by the + // registry + stack_handler_->Clear(); + } WakelockManager::Get().Acquire(); std::promise<void> promise; @@ -192,7 +195,14 @@ void Stack::Stop() { delete management_handler_; delete management_thread_; - delete stack_handler_; + if (!com::android::bluetooth::flags::same_handler_for_all_modules()) { + // delete the handler only if the flag is not defined, otherwise it will be deleted by the + // registry + delete stack_handler_; + } + + // stack_handler_ is already deleted by the registry in handle_shut_down, just set it to nullptr + // to avoid any potential use-after-free stack_handler_ = nullptr; stack_thread_->Stop(); |