diff options
44 files changed, 883 insertions, 515 deletions
diff --git a/android/app/aidl/android/bluetooth/IBluetoothA2dp.aidl b/android/app/aidl/android/bluetooth/IBluetoothA2dp.aidl index bcfc95585d..fa5d1362ac 100644 --- a/android/app/aidl/android/bluetooth/IBluetoothA2dp.aidl +++ b/android/app/aidl/android/bluetooth/IBluetoothA2dp.aidl @@ -53,7 +53,7 @@ interface IBluetoothA2dp { boolean isA2dpPlaying(in BluetoothDevice device, in AttributionSource attributionSource); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)") List<BluetoothCodecType> getSupportedCodecTypes(); - @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})") + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED}, conditional=true)") BluetoothCodecStatus getCodecStatus(in BluetoothDevice device, in AttributionSource attributionSource); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED}, conditional=true)") oneway void setCodecConfigPreference(in BluetoothDevice device, in BluetoothCodecConfig codecConfig, in AttributionSource attributionSource); diff --git a/android/app/src/com/android/bluetooth/a2dp/A2dpService.java b/android/app/src/com/android/bluetooth/a2dp/A2dpService.java index 136a0d4621..100076127b 100644 --- a/android/app/src/com/android/bluetooth/a2dp/A2dpService.java +++ b/android/app/src/com/android/bluetooth/a2dp/A2dpService.java @@ -50,7 +50,6 @@ import android.media.BluetoothProfileConnectionInfo; import android.os.Build; import android.os.Bundle; import android.os.Handler; -import android.os.HandlerThread; import android.os.Looper; import android.sysprop.BluetoothProperties; import android.util.Log; @@ -1515,12 +1514,14 @@ public class A2dpService extends ProfileService { @Override public BluetoothCodecStatus getCodecStatus( BluetoothDevice device, AttributionSource source) { + requireNonNull(device); A2dpService service = getServiceAndEnforceConnect(source); if (service == null) { return null; } - service.enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, null); + Utils.enforceCdmAssociationIfNotBluetoothPrivileged( + service, service.mCompanionDeviceManager, source, device); return service.getCodecStatus(device); } @@ -1530,6 +1531,7 @@ public class A2dpService extends ProfileService { BluetoothDevice device, BluetoothCodecConfig codecConfig, AttributionSource source) { + requireNonNull(device); A2dpService service = getServiceAndEnforceConnect(source); if (service == null) { return; 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 9b24ff60cc..0daef0175a 100644 --- a/android/app/src/com/android/bluetooth/bass_client/BassClientService.java +++ b/android/app/src/com/android/bluetooth/bass_client/BassClientService.java @@ -2499,6 +2499,28 @@ public class BassClientService extends ProfileService { BluetoothDevice srcDevice = getDeviceForSyncHandle(syncHandle); mSyncHandleToDeviceMap.remove(syncHandle); int broadcastId = getBroadcastIdForSyncHandle(syncHandle); + if (leaudioMonitorUnicastSourceWhenManagedByBroadcastDelegator()) { + synchronized (mPendingSourcesToAdd) { + Iterator<AddSourceData> iterator = mPendingSourcesToAdd.iterator(); + while (iterator.hasNext()) { + AddSourceData pendingSourcesToAdd = iterator.next(); + if (pendingSourcesToAdd.mSourceMetadata.getBroadcastId() == broadcastId) { + iterator.remove(); + } + } + } + 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(); + int broadcastIdForPast = entry.getValue().first; + if (broadcastId == broadcastIdForPast) { + iterator.remove(); + } + } + } + } mSyncHandleToBroadcastIdMap.remove(syncHandle); if (srcDevice != null) { mPeriodicAdvertisementResultMap.get(srcDevice).remove(broadcastId); @@ -4054,7 +4076,10 @@ public class BassClientService extends ProfileService { mUnicastSourceStreamStatus = Optional.of(status); if (status == STATUS_LOCAL_STREAM_REQUESTED) { - if (areReceiversReceivingOnlyExternalBroadcast(getConnectedDevices())) { + if ((leaudioMonitorUnicastSourceWhenManagedByBroadcastDelegator() + && hasPrimaryDeviceManagedExternalBroadcast()) + || (!leaudioMonitorUnicastSourceWhenManagedByBroadcastDelegator() + && areReceiversReceivingOnlyExternalBroadcast(getConnectedDevices()))) { if (leaudioBroadcastAssistantPeripheralEntrustment()) { cacheSuspendingSources(BassConstants.INVALID_BROADCAST_ID); List<Pair<BluetoothLeBroadcastReceiveState, BluetoothDevice>> sourcesToStop = @@ -4067,11 +4092,13 @@ public class BassClientService extends ProfileService { suspendAllReceiversSourceSynchronization(); } } - for (Map.Entry<Integer, PauseType> entry : mPausedBroadcastIds.entrySet()) { - Integer broadcastId = entry.getKey(); - PauseType pauseType = entry.getValue(); - if (pauseType != PauseType.HOST_INTENTIONAL) { - suspendReceiversSourceSynchronization(broadcastId); + if (!leaudioMonitorUnicastSourceWhenManagedByBroadcastDelegator()) { + for (Map.Entry<Integer, PauseType> entry : mPausedBroadcastIds.entrySet()) { + Integer broadcastId = entry.getKey(); + PauseType pauseType = entry.getValue(); + if (pauseType != PauseType.HOST_INTENTIONAL) { + suspendReceiversSourceSynchronization(broadcastId); + } } } } else if (status == STATUS_LOCAL_STREAM_SUSPENDED) { diff --git a/android/app/src/com/android/bluetooth/btservice/AbstractionLayer.java b/android/app/src/com/android/bluetooth/btservice/AbstractionLayer.java index 981b2db0c7..9ebe10d181 100644 --- a/android/app/src/com/android/bluetooth/btservice/AbstractionLayer.java +++ b/android/app/src/com/android/bluetooth/btservice/AbstractionLayer.java @@ -50,6 +50,7 @@ public final class AbstractionLayer { static final int BT_PROPERTY_REMOTE_ASHA_TRUNCATED_HISYNCID = 0X16; static final int BT_PROPERTY_REMOTE_MODEL_NUM = 0x17; static final int BT_PROPERTY_LPP_OFFLOAD_FEATURES = 0x1B; + static final int BT_PROPERTY_UUIDS_LE = 0x1C; public static final int BT_DEVICE_TYPE_BREDR = 0x01; public static final int BT_DEVICE_TYPE_BLE = 0x02; diff --git a/android/app/src/com/android/bluetooth/btservice/RemoteDevices.java b/android/app/src/com/android/bluetooth/btservice/RemoteDevices.java index f0bad17e64..f297d905ac 100644 --- a/android/app/src/com/android/bluetooth/btservice/RemoteDevices.java +++ b/android/app/src/com/android/bluetooth/btservice/RemoteDevices.java @@ -345,7 +345,8 @@ public class RemoteDevices { private String mModelName; @VisibleForTesting int mBondState; @VisibleForTesting int mDeviceType; - @VisibleForTesting ParcelUuid[] mUuids; + @VisibleForTesting ParcelUuid[] mUuidsBrEdr; + @VisibleForTesting ParcelUuid[] mUuidsLe; private BluetoothSinkAudioPolicy mAudioPolicy; DeviceProperties() { @@ -506,20 +507,71 @@ public class RemoteDevices { } /** - * @return the mUuids + * @return the UUIDs on LE and Classic transport */ ParcelUuid[] getUuids() { synchronized (mObject) { - return mUuids; + /* When we bond dual mode device, and discover LE and Classic services, stack would + * return LE and Classic UUIDs separately, but Java apps expect them merged. + */ + int combinedUuidsLength = + (mUuidsBrEdr != null ? mUuidsBrEdr.length : 0) + + (mUuidsLe != null ? mUuidsLe.length : 0); + if (!Flags.separateServiceStorage() || combinedUuidsLength == 0) { + return mUuidsBrEdr; + } + + java.util.LinkedHashSet<ParcelUuid> result = + new java.util.LinkedHashSet<ParcelUuid>(); + if (mUuidsBrEdr != null) { + for (ParcelUuid uuid : mUuidsBrEdr) { + result.add(uuid); + } + } + + if (mUuidsLe != null) { + for (ParcelUuid uuid : mUuidsLe) { + result.add(uuid); + } + } + + return result.toArray(new ParcelUuid[combinedUuidsLength]); + } + } + + /** + * @return just classic transport UUIDS + */ + ParcelUuid[] getUuidsBrEdr() { + synchronized (mObject) { + return mUuidsBrEdr; + } + } + + /** + * @param uuids the mUuidsBrEdr to set + */ + void setUuidsBrEdr(ParcelUuid[] uuids) { + synchronized (mObject) { + this.mUuidsBrEdr = uuids; + } + } + + /** + * @return the mUuidsLe + */ + ParcelUuid[] getUuidsLe() { + synchronized (mObject) { + return mUuidsLe; } } /** - * @param uuids the mUuids to set + * @param uuids the mUuidsLe to set */ - void setUuids(ParcelUuid[] uuids) { + void setUuidsLe(ParcelUuid[] uuids) { synchronized (mObject) { - this.mUuids = uuids; + this.mUuidsLe = uuids; } } @@ -636,7 +688,8 @@ public class RemoteDevices { cachedBluetoothDevice issued a connect using the local cached copy of uuids, without waiting for the ACTION_UUID intent. This was resulting in multiple calls to connect().*/ - mUuids = null; + mUuidsBrEdr = null; + mUuidsLe = null; mAlias = null; } } @@ -988,147 +1041,168 @@ public class RemoteDevices { return; } + boolean uuids_updated = false; + for (int j = 0; j < types.length; j++) { type = types[j]; val = values[j]; - if (val.length > 0) { - synchronized (mObject) { - debugLog("Update property, device=" + bdDevice + ", type: " + type); - switch (type) { - case AbstractionLayer.BT_PROPERTY_BDNAME: - final String newName = new String(val); - if (newName.equals(deviceProperties.getName())) { - debugLog("Skip name update for " + bdDevice); - break; - } - deviceProperties.setName(newName); - List<String> wordBreakdownList = - MetricsLogger.getInstance().getWordBreakdownList(newName); - if (SdkLevel.isAtLeastU()) { - MetricsLogger.getInstance() - .uploadRestrictedBluetothDeviceName(wordBreakdownList); - } - intent = new Intent(BluetoothDevice.ACTION_NAME_CHANGED); - intent.putExtra(BluetoothDevice.EXTRA_DEVICE, bdDevice); - intent.putExtra(BluetoothDevice.EXTRA_NAME, deviceProperties.getName()); - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - mAdapterService.sendBroadcast( - intent, - BLUETOOTH_CONNECT, - Utils.getTempBroadcastOptions().toBundle()); - debugLog("Remote device name is: " + deviceProperties.getName()); - break; - case AbstractionLayer.BT_PROPERTY_REMOTE_FRIENDLY_NAME: - deviceProperties.setAlias(bdDevice, new String(val)); - debugLog("Remote device alias is: " + deviceProperties.getAlias()); - break; - case AbstractionLayer.BT_PROPERTY_BDADDR: - deviceProperties.setAddress(val); - debugLog( - "Remote Address is:" - + Utils.getRedactedAddressStringFromByte(val)); + if (val.length == 0) { + continue; + } + + synchronized (mObject) { + debugLog("Update property, device=" + bdDevice + ", type: " + type); + switch (type) { + case AbstractionLayer.BT_PROPERTY_BDNAME: + final String newName = new String(val); + if (newName.equals(deviceProperties.getName())) { + debugLog("Skip name update for " + bdDevice); break; - case AbstractionLayer.BT_PROPERTY_CLASS_OF_DEVICE: - final int newBluetoothClass = Utils.byteArrayToInt(val); - if (newBluetoothClass == deviceProperties.getBluetoothClass()) { - debugLog( - "Skip class update, device=" - + bdDevice - + ", cod=0x" - + Integer.toHexString(newBluetoothClass)); - break; - } - deviceProperties.setBluetoothClass(newBluetoothClass); - intent = new Intent(BluetoothDevice.ACTION_CLASS_CHANGED); - intent.putExtra(BluetoothDevice.EXTRA_DEVICE, bdDevice); - intent.putExtra( - BluetoothDevice.EXTRA_CLASS, - new BluetoothClass(deviceProperties.getBluetoothClass())); - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - mAdapterService.sendBroadcast( - intent, - BLUETOOTH_CONNECT, - Utils.getTempBroadcastOptions().toBundle()); + } + deviceProperties.setName(newName); + List<String> wordBreakdownList = + MetricsLogger.getInstance().getWordBreakdownList(newName); + if (SdkLevel.isAtLeastU()) { + MetricsLogger.getInstance() + .uploadRestrictedBluetothDeviceName(wordBreakdownList); + } + intent = new Intent(BluetoothDevice.ACTION_NAME_CHANGED); + intent.putExtra(BluetoothDevice.EXTRA_DEVICE, bdDevice); + intent.putExtra(BluetoothDevice.EXTRA_NAME, deviceProperties.getName()); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + mAdapterService.sendBroadcast( + intent, + BLUETOOTH_CONNECT, + Utils.getTempBroadcastOptions().toBundle()); + debugLog("Remote device name is: " + deviceProperties.getName()); + break; + case AbstractionLayer.BT_PROPERTY_REMOTE_FRIENDLY_NAME: + deviceProperties.setAlias(bdDevice, new String(val)); + debugLog("Remote device alias is: " + deviceProperties.getAlias()); + break; + case AbstractionLayer.BT_PROPERTY_BDADDR: + deviceProperties.setAddress(val); + debugLog( + "Remote Address is:" + Utils.getRedactedAddressStringFromByte(val)); + break; + case AbstractionLayer.BT_PROPERTY_CLASS_OF_DEVICE: + final int newBluetoothClass = Utils.byteArrayToInt(val); + if (newBluetoothClass == deviceProperties.getBluetoothClass()) { debugLog( - "Remote class update, device=" + "Skip class update, device=" + bdDevice + ", cod=0x" + Integer.toHexString(newBluetoothClass)); break; - case AbstractionLayer.BT_PROPERTY_UUIDS: + } + deviceProperties.setBluetoothClass(newBluetoothClass); + intent = new Intent(BluetoothDevice.ACTION_CLASS_CHANGED); + intent.putExtra(BluetoothDevice.EXTRA_DEVICE, bdDevice); + intent.putExtra( + BluetoothDevice.EXTRA_CLASS, + new BluetoothClass(deviceProperties.getBluetoothClass())); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + mAdapterService.sendBroadcast( + intent, + BLUETOOTH_CONNECT, + Utils.getTempBroadcastOptions().toBundle()); + debugLog( + "Remote class update, device=" + + bdDevice + + ", cod=0x" + + Integer.toHexString(newBluetoothClass)); + break; + case AbstractionLayer.BT_PROPERTY_UUIDS: + case AbstractionLayer.BT_PROPERTY_UUIDS_LE: + if (type == AbstractionLayer.BT_PROPERTY_UUIDS) { final ParcelUuid[] newUuids = Utils.byteArrayToUuid(val); - if (areUuidsEqual(newUuids, deviceProperties.getUuids())) { + if (areUuidsEqual(newUuids, deviceProperties.getUuidsBrEdr())) { // SDP Skip adding UUIDs to property cache if equal debugLog("Skip uuids update for " + bdDevice.getAddress()); MetricsLogger.getInstance() .cacheCount(BluetoothProtoEnums.SDP_UUIDS_EQUAL_SKIP, 1); break; } - deviceProperties.setUuids(newUuids); - if (mAdapterService.getState() == BluetoothAdapter.STATE_ON) { - // SDP Adding UUIDs to property cache and sending intent - MetricsLogger.getInstance() - .cacheCount( - BluetoothProtoEnums.SDP_ADD_UUID_WITH_INTENT, 1); - mAdapterService.deviceUuidUpdated(bdDevice); - sendUuidIntent(bdDevice, deviceProperties, true); - } else if (mAdapterService.getState() - == BluetoothAdapter.STATE_BLE_ON) { - // SDP Adding UUIDs to property cache but with no intent - MetricsLogger.getInstance() - .cacheCount( - BluetoothProtoEnums.SDP_ADD_UUID_WITH_NO_INTENT, 1); - mAdapterService.deviceUuidUpdated(bdDevice); - } else { - // SDP Silently dropping UUIDs and with no intent + deviceProperties.setUuidsBrEdr(newUuids); + } else if (type == AbstractionLayer.BT_PROPERTY_UUIDS_LE) { + final ParcelUuid[] newUuidsLe = Utils.byteArrayToUuid(val); + if (areUuidsEqual(newUuidsLe, deviceProperties.getUuidsLe())) { + // SDP Skip adding UUIDs to property cache if equal + debugLog("Skip LE uuids update for " + bdDevice.getAddress()); MetricsLogger.getInstance() - .cacheCount(BluetoothProtoEnums.SDP_DROP_UUID, 1); - } - break; - case AbstractionLayer.BT_PROPERTY_TYPE_OF_DEVICE: - if (deviceProperties.isConsolidated()) { + .cacheCount(BluetoothProtoEnums.SDP_UUIDS_EQUAL_SKIP, 1); break; } - // The device type from hal layer, defined in bluetooth.h, - // matches the type defined in BluetoothDevice.java - deviceProperties.setDeviceType(Utils.byteArrayToInt(val)); - break; - case AbstractionLayer.BT_PROPERTY_REMOTE_RSSI: - // RSSI from hal is in one byte - deviceProperties.setRssi(val[0]); - break; - case AbstractionLayer.BT_PROPERTY_REMOTE_IS_COORDINATED_SET_MEMBER: - deviceProperties.setIsCoordinatedSetMember(val[0] != 0); - break; - case AbstractionLayer.BT_PROPERTY_REMOTE_ASHA_CAPABILITY: - deviceProperties.setAshaCapability(val[0]); - break; - case AbstractionLayer.BT_PROPERTY_REMOTE_ASHA_TRUNCATED_HISYNCID: - deviceProperties.setAshaTruncatedHiSyncId(val[0]); - break; - case AbstractionLayer.BT_PROPERTY_REMOTE_MODEL_NUM: - final String modelName = new String(val); - debugLog("Remote device model name: " + modelName); - deviceProperties.setModelName(modelName); - BluetoothStatsLog.write( - BluetoothStatsLog.BLUETOOTH_DEVICE_INFO_REPORTED, - mAdapterService.obfuscateAddress(bdDevice), - BluetoothProtoEnums.DEVICE_INFO_INTERNAL, - LOG_SOURCE_DIS, - null, - modelName, - null, - null, - mAdapterService.getMetricId(bdDevice), - bdDevice.getAddressType(), - 0, - 0, - 0); + deviceProperties.setUuidsLe(newUuidsLe); + } + uuids_updated = true; + break; + case AbstractionLayer.BT_PROPERTY_TYPE_OF_DEVICE: + if (deviceProperties.isConsolidated()) { break; - } + } + // The device type from hal layer, defined in bluetooth.h, + // matches the type defined in BluetoothDevice.java + deviceProperties.setDeviceType(Utils.byteArrayToInt(val)); + break; + case AbstractionLayer.BT_PROPERTY_REMOTE_RSSI: + // RSSI from hal is in one byte + deviceProperties.setRssi(val[0]); + break; + case AbstractionLayer.BT_PROPERTY_REMOTE_IS_COORDINATED_SET_MEMBER: + deviceProperties.setIsCoordinatedSetMember(val[0] != 0); + break; + case AbstractionLayer.BT_PROPERTY_REMOTE_ASHA_CAPABILITY: + deviceProperties.setAshaCapability(val[0]); + break; + case AbstractionLayer.BT_PROPERTY_REMOTE_ASHA_TRUNCATED_HISYNCID: + deviceProperties.setAshaTruncatedHiSyncId(val[0]); + break; + case AbstractionLayer.BT_PROPERTY_REMOTE_MODEL_NUM: + final String modelName = new String(val); + debugLog("Remote device model name: " + modelName); + deviceProperties.setModelName(modelName); + BluetoothStatsLog.write( + BluetoothStatsLog.BLUETOOTH_DEVICE_INFO_REPORTED, + mAdapterService.obfuscateAddress(bdDevice), + BluetoothProtoEnums.DEVICE_INFO_INTERNAL, + LOG_SOURCE_DIS, + null, + modelName, + null, + null, + mAdapterService.getMetricId(bdDevice), + bdDevice.getAddressType(), + 0, + 0, + 0); + break; } } } + + if (!uuids_updated) { + return; + } + + /* uuids_updated == true + * We might have received LE and BREDR UUIDS separately, ensure that UUID intent is sent + * just once */ + + if (mAdapterService.getState() == BluetoothAdapter.STATE_ON) { + // SDP Adding UUIDs to property cache and sending intent + MetricsLogger.getInstance().cacheCount(BluetoothProtoEnums.SDP_ADD_UUID_WITH_INTENT, 1); + mAdapterService.deviceUuidUpdated(bdDevice); + sendUuidIntent(bdDevice, deviceProperties, true); + } else if (mAdapterService.getState() == BluetoothAdapter.STATE_BLE_ON) { + // SDP Adding UUIDs to property cache but with no intent + MetricsLogger.getInstance() + .cacheCount(BluetoothProtoEnums.SDP_ADD_UUID_WITH_NO_INTENT, 1); + mAdapterService.deviceUuidUpdated(bdDevice); + } else { + // SDP Silently dropping UUIDs and with no intent + MetricsLogger.getInstance().cacheCount(BluetoothProtoEnums.SDP_DROP_UUID, 1); + } } void deviceFoundCallback(byte[] address) { diff --git a/android/app/src/com/android/bluetooth/gatt/AdvertiseBinder.kt b/android/app/src/com/android/bluetooth/gatt/AdvertiseBinder.kt index 92aaf31101..66415cd82f 100644 --- a/android/app/src/com/android/bluetooth/gatt/AdvertiseBinder.kt +++ b/android/app/src/com/android/bluetooth/gatt/AdvertiseBinder.kt @@ -66,28 +66,31 @@ class AdvertiseBinder( mContext.enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, null) } - getManager(source) - ?.startAdvertisingSet( - parameters, - advertiseData, - scanResponse, - periodicParameters, - periodicData, - duration, - maxExtAdvEvents, - serverIf, - callback, - source, - ) + getManager(source)?.let { + it.doOnAdvertiseThread { + it.startAdvertisingSet( + parameters, + advertiseData, + scanResponse, + periodicParameters, + periodicData, + duration, + maxExtAdvEvents, + serverIf, + callback, + source, + ) + } + } } override fun stopAdvertisingSet(callback: IAdvertisingSetCallback, source: AttributionSource) { - getManager(source)?.stopAdvertisingSet(callback) + getManager(source)?.let { it.doOnAdvertiseThread { it.stopAdvertisingSet(callback) } } } override fun getOwnAddress(advertiserId: Int, source: AttributionSource) { mContext.enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, null) - getManager(source)?.getOwnAddress(advertiserId) + getManager(source)?.let { it.doOnAdvertiseThread { it.getOwnAddress(advertiserId) } } } override fun enableAdvertisingSet( @@ -97,7 +100,11 @@ class AdvertiseBinder( maxExtAdvEvents: Int, source: AttributionSource, ) { - getManager(source)?.enableAdvertisingSet(advertiserId, enable, duration, maxExtAdvEvents) + getManager(source)?.let { + it.doOnAdvertiseThread { + it.enableAdvertisingSet(advertiserId, enable, duration, maxExtAdvEvents) + } + } } override fun setAdvertisingData( @@ -105,7 +112,9 @@ class AdvertiseBinder( data: AdvertiseData?, source: AttributionSource, ) { - getManager(source)?.setAdvertisingData(advertiserId, data) + getManager(source)?.let { + it.doOnAdvertiseThread { it.setAdvertisingData(advertiserId, data) } + } } override fun setScanResponseData( @@ -113,7 +122,9 @@ class AdvertiseBinder( data: AdvertiseData?, source: AttributionSource, ) { - getManager(source)?.setScanResponseData(advertiserId, data) + getManager(source)?.let { + it.doOnAdvertiseThread { it.setScanResponseData(advertiserId, data) } + } } override fun setAdvertisingParameters( @@ -127,7 +138,9 @@ class AdvertiseBinder( ) { mContext.enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, null) } - getManager(source)?.setAdvertisingParameters(advertiserId, parameters) + getManager(source)?.let { + it.doOnAdvertiseThread { it.setAdvertisingParameters(advertiserId, parameters) } + } } override fun setPeriodicAdvertisingParameters( @@ -135,7 +148,9 @@ class AdvertiseBinder( parameters: PeriodicAdvertisingParameters?, source: AttributionSource, ) { - getManager(source)?.setPeriodicAdvertisingParameters(advertiserId, parameters) + getManager(source)?.let { + it.doOnAdvertiseThread { it.setPeriodicAdvertisingParameters(advertiserId, parameters) } + } } override fun setPeriodicAdvertisingData( @@ -143,7 +158,9 @@ class AdvertiseBinder( data: AdvertiseData?, source: AttributionSource, ) { - getManager(source)?.setPeriodicAdvertisingData(advertiserId, data) + getManager(source)?.let { + it.doOnAdvertiseThread { it.setPeriodicAdvertisingData(advertiserId, data) } + } } override fun setPeriodicAdvertisingEnable( @@ -151,6 +168,8 @@ class AdvertiseBinder( enable: Boolean, source: AttributionSource, ) { - getManager(source)?.setPeriodicAdvertisingEnable(advertiserId, enable) + getManager(source)?.let { + it.doOnAdvertiseThread { it.setPeriodicAdvertisingEnable(advertiserId, enable) } + } } } diff --git a/android/app/src/com/android/bluetooth/gatt/AdvertiseManager.java b/android/app/src/com/android/bluetooth/gatt/AdvertiseManager.java index 7e89f99055..6ed719cedc 100644 --- a/android/app/src/com/android/bluetooth/gatt/AdvertiseManager.java +++ b/android/app/src/com/android/bluetooth/gatt/AdvertiseManager.java @@ -24,45 +24,52 @@ import android.bluetooth.le.PeriodicAdvertisingParameters; import android.content.AttributionSource; import android.os.Binder; import android.os.Handler; -import android.os.HandlerThread; import android.os.IBinder; -import android.os.IInterface; import android.os.Looper; import android.os.RemoteException; import android.util.Log; +import com.android.bluetooth.Utils; import com.android.bluetooth.btservice.AdapterService; -import com.android.internal.annotations.GuardedBy; +import com.android.bluetooth.flags.Flags; import com.android.internal.annotations.VisibleForTesting; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; -/** - * Manages Bluetooth LE advertising operations and interacts with bluedroid stack. TODO: add tests. - */ -@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) +/** Manages Bluetooth LE advertising operations. */ public class AdvertiseManager { private static final String TAG = GattServiceConfig.TAG_PREFIX + "AdvertiseManager"; - private final GattService mService; + private static final long RUN_SYNC_WAIT_TIME_MS = 2000L; + + private final AdapterService mService; private final AdvertiseManagerNativeInterface mNativeInterface; private final AdvertiseBinder mAdvertiseBinder; private final AdvertiserMap mAdvertiserMap; - @GuardedBy("itself") private final Map<IBinder, AdvertiserInfo> mAdvertisers = new HashMap<>(); - private Handler mHandler; - static int sTempRegistrationId = -1; + private final Handler mHandler; + private volatile boolean mIsAvailable = true; + @VisibleForTesting int mTempRegistrationId = -1; - AdvertiseManager(GattService service) { - this(service, AdvertiseManagerNativeInterface.getInstance(), new AdvertiserMap()); + AdvertiseManager(AdapterService service, Looper advertiseLooper) { + this( + service, + advertiseLooper, + AdvertiseManagerNativeInterface.getInstance(), + new AdvertiserMap()); } @VisibleForTesting AdvertiseManager( - GattService service, + AdapterService service, + Looper advertiseLooper, AdvertiseManagerNativeInterface nativeInterface, AdvertiserMap advertiserMap) { Log.d(TAG, "advertise manager created"); @@ -70,42 +77,26 @@ public class AdvertiseManager { mNativeInterface = nativeInterface; mAdvertiserMap = advertiserMap; - // Start a HandlerThread that handles advertising operations mNativeInterface.init(this); - HandlerThread thread = new HandlerThread("BluetoothAdvertiseManager"); - thread.start(); - mHandler = new Handler(thread.getLooper()); + mHandler = new Handler(advertiseLooper); mAdvertiseBinder = new AdvertiseBinder(service, this); } - // TODO(b/327849650): We shouldn't need this, it should be safe to do in the cleanup method. But - // it would be a logic change. - void clear() { - mAdvertiserMap.clear(); - } - void cleanup() { Log.d(TAG, "cleanup()"); - mAdvertiseBinder.cleanup(); - mNativeInterface.cleanup(); - synchronized (mAdvertisers) { - mAdvertisers.clear(); - } - sTempRegistrationId = -1; - - if (mHandler != null) { - // Shut down the thread - mHandler.removeCallbacksAndMessages(null); - Looper looper = mHandler.getLooper(); - if (looper != null) { - looper.quit(); - } - mHandler = null; - } + mIsAvailable = false; + mHandler.removeCallbacksAndMessages(null); + forceRunSyncOnAdvertiseThread( + () -> { + mAdvertiserMap.clear(); + mAdvertiseBinder.cleanup(); + mNativeInterface.cleanup(); + mAdvertisers.clear(); + }); } void dump(StringBuilder sb) { - mAdvertiserMap.dump(sb); + forceRunSyncOnAdvertiseThread(() -> mAdvertiserMap.dump(sb)); } AdvertiseBinder getBinder() { @@ -129,8 +120,12 @@ public class AdvertiseManager { } } + private interface CallbackWrapper { + void call() throws RemoteException; + } + IBinder toBinder(IAdvertisingSetCallback e) { - return ((IInterface) e).asBinder(); + return e.asBinder(); } class AdvertisingSetDeathRecipient implements IBinder.DeathRecipient { @@ -145,25 +140,22 @@ public class AdvertiseManager { @Override public void binderDied() { Log.d(TAG, "Binder is dead - unregistering advertising set (" + mPackageName + ")!"); - stopAdvertisingSet(callback); + doOnAdvertiseThread(() -> stopAdvertisingSet(callback)); } } - Map.Entry<IBinder, AdvertiserInfo> findAdvertiser(int advertiserId) { + private Map.Entry<IBinder, AdvertiserInfo> findAdvertiser(int advertiserId) { Map.Entry<IBinder, AdvertiserInfo> entry = null; - synchronized (mAdvertisers) { - for (Map.Entry<IBinder, AdvertiserInfo> e : mAdvertisers.entrySet()) { - if (e.getValue().id == advertiserId) { - entry = e; - break; - } + for (Map.Entry<IBinder, AdvertiserInfo> e : mAdvertisers.entrySet()) { + if (e.getValue().id == advertiserId) { + entry = e; + break; } } return entry; } - void onAdvertisingSetStarted(int regId, int advertiserId, int txPower, int status) - throws Exception { + void onAdvertisingSetStarted(int regId, int advertiserId, int txPower, int status) { Log.d( TAG, "onAdvertisingSetStarted() - regId=" @@ -172,6 +164,7 @@ public class AdvertiseManager { + advertiserId + ", status=" + status); + checkThread(); Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(regId); @@ -191,26 +184,24 @@ public class AdvertiseManager { } else { IBinder binder = entry.getKey(); binder.unlinkToDeath(entry.getValue().deathRecipient, 0); - synchronized (mAdvertisers) { - mAdvertisers.remove(binder); - } + mAdvertisers.remove(binder); AppAdvertiseStats stats = mAdvertiserMap.getAppAdvertiseStatsById(regId); if (stats != null) { - int instanceCount; - synchronized (mAdvertisers) { - instanceCount = mAdvertisers.size(); - } - stats.recordAdvertiseStop(instanceCount); + stats.recordAdvertiseStop(mAdvertisers.size()); stats.recordAdvertiseErrorCount(status); } mAdvertiserMap.removeAppAdvertiseStats(regId); } - callback.onAdvertisingSetStarted(mAdvertiseBinder, advertiserId, txPower, status); + sendToCallback( + advertiserId, + () -> + callback.onAdvertisingSetStarted( + mAdvertiseBinder, advertiserId, txPower, status)); } - void onAdvertisingEnabled(int advertiserId, boolean enable, int status) throws Exception { + void onAdvertisingEnabled(int advertiserId, boolean enable, int status) { Log.d( TAG, "onAdvertisingSetEnabled() - advertiserId=" @@ -219,6 +210,7 @@ public class AdvertiseManager { + enable + ", status=" + status); + checkThread(); Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId); if (entry == null) { @@ -230,16 +222,13 @@ public class AdvertiseManager { } IAdvertisingSetCallback callback = entry.getValue().callback; - callback.onAdvertisingEnabled(advertiserId, enable, status); + sendToCallback( + advertiserId, () -> callback.onAdvertisingEnabled(advertiserId, enable, status)); if (!enable && status != 0) { AppAdvertiseStats stats = mAdvertiserMap.getAppAdvertiseStatsById(advertiserId); if (stats != null) { - int instanceCount; - synchronized (mAdvertisers) { - instanceCount = mAdvertisers.size(); - } - stats.recordAdvertiseStop(instanceCount); + stats.recordAdvertiseStop(mAdvertisers.size()); } } } @@ -255,6 +244,7 @@ public class AdvertiseManager { int serverIf, IAdvertisingSetCallback callback, AttributionSource attrSource) { + checkThread(); // If we are using an isolated server, force usage of an NRPA if (serverIf != 0 && parameters.getOwnAddressType() @@ -289,7 +279,7 @@ public class AdvertiseManager { throw new IllegalArgumentException("Can't link to advertiser's death"); } - String deviceName = AdapterService.getAdapterService().getName(); + String deviceName = mService.getName(); try { byte[] advDataBytes = AdvertiseHelper.advertiseDataToBytes(advertiseData, deviceName); byte[] scanResponseBytes = @@ -297,10 +287,8 @@ public class AdvertiseManager { byte[] periodicDataBytes = AdvertiseHelper.advertiseDataToBytes(periodicData, deviceName); - int cbId = --sTempRegistrationId; - synchronized (mAdvertisers) { - mAdvertisers.put(binder, new AdvertiserInfo(cbId, deathRecipient, callback)); - } + int cbId = --mTempRegistrationId; + mAdvertisers.put(binder, new AdvertiserInfo(cbId, deathRecipient, callback)); Log.d(TAG, "startAdvertisingSet() - reg_id=" + cbId + ", callback: " + binder); @@ -340,9 +328,9 @@ public class AdvertiseManager { } } - void onOwnAddressRead(int advertiserId, int addressType, String address) - throws RemoteException { + void onOwnAddressRead(int advertiserId, int addressType, String address) { Log.d(TAG, "onOwnAddressRead() advertiserId=" + advertiserId); + checkThread(); Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId); if (entry == null) { @@ -351,10 +339,12 @@ public class AdvertiseManager { } IAdvertisingSetCallback callback = entry.getValue().callback; - callback.onOwnAddressRead(advertiserId, addressType, address); + sendToCallback( + advertiserId, () -> callback.onOwnAddressRead(advertiserId, addressType, address)); } void getOwnAddress(int advertiserId) { + checkThread(); Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId); if (entry == null) { Log.w(TAG, "getOwnAddress() - bad advertiserId " + advertiserId); @@ -364,13 +354,11 @@ public class AdvertiseManager { } void stopAdvertisingSet(IAdvertisingSetCallback callback) { + checkThread(); IBinder binder = toBinder(callback); Log.d(TAG, "stopAdvertisingSet() " + binder); - AdvertiserInfo adv; - synchronized (mAdvertisers) { - adv = mAdvertisers.remove(binder); - } + AdvertiserInfo adv = mAdvertisers.remove(binder); if (adv == null) { Log.e(TAG, "stopAdvertisingSet() - no client found for callback"); return; @@ -397,6 +385,7 @@ public class AdvertiseManager { } void enableAdvertisingSet(int advertiserId, boolean enable, int duration, int maxExtAdvEvents) { + checkThread(); Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId); if (entry == null) { Log.w(TAG, "enableAdvertisingSet() - bad advertiserId " + advertiserId); @@ -408,12 +397,13 @@ public class AdvertiseManager { } void setAdvertisingData(int advertiserId, AdvertiseData data) { + checkThread(); Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId); if (entry == null) { Log.w(TAG, "setAdvertisingData() - bad advertiserId " + advertiserId); return; } - String deviceName = AdapterService.getAdapterService().getName(); + String deviceName = mService.getName(); try { mNativeInterface.setAdvertisingData( advertiserId, AdvertiseHelper.advertiseDataToBytes(data, deviceName)); @@ -430,12 +420,13 @@ public class AdvertiseManager { } void setScanResponseData(int advertiserId, AdvertiseData data) { + checkThread(); Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId); if (entry == null) { Log.w(TAG, "setScanResponseData() - bad advertiserId " + advertiserId); return; } - String deviceName = AdapterService.getAdapterService().getName(); + String deviceName = mService.getName(); try { mNativeInterface.setScanResponseData( advertiserId, AdvertiseHelper.advertiseDataToBytes(data, deviceName)); @@ -452,6 +443,7 @@ public class AdvertiseManager { } void setAdvertisingParameters(int advertiserId, AdvertisingSetParameters parameters) { + checkThread(); Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId); if (entry == null) { Log.w(TAG, "setAdvertisingParameters() - bad advertiserId " + advertiserId); @@ -464,6 +456,7 @@ public class AdvertiseManager { void setPeriodicAdvertisingParameters( int advertiserId, PeriodicAdvertisingParameters parameters) { + checkThread(); Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId); if (entry == null) { Log.w(TAG, "setPeriodicAdvertisingParameters() - bad advertiserId " + advertiserId); @@ -475,12 +468,13 @@ public class AdvertiseManager { } void setPeriodicAdvertisingData(int advertiserId, AdvertiseData data) { + checkThread(); Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId); if (entry == null) { Log.w(TAG, "setPeriodicAdvertisingData() - bad advertiserId " + advertiserId); return; } - String deviceName = AdapterService.getAdapterService().getName(); + String deviceName = mService.getName(); try { mNativeInterface.setPeriodicAdvertisingData( advertiserId, AdvertiseHelper.advertiseDataToBytes(data, deviceName)); @@ -497,6 +491,7 @@ public class AdvertiseManager { } void setPeriodicAdvertisingEnable(int advertiserId, boolean enable) { + checkThread(); Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId); if (entry == null) { Log.w(TAG, "setPeriodicAdvertisingEnable() - bad advertiserId " + advertiserId); @@ -505,7 +500,8 @@ public class AdvertiseManager { mNativeInterface.setPeriodicAdvertisingEnable(advertiserId, enable); } - void onAdvertisingDataSet(int advertiserId, int status) throws Exception { + void onAdvertisingDataSet(int advertiserId, int status) { + checkThread(); Log.d(TAG, "onAdvertisingDataSet() advertiserId=" + advertiserId + ", status=" + status); Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId); @@ -515,10 +511,11 @@ public class AdvertiseManager { } IAdvertisingSetCallback callback = entry.getValue().callback; - callback.onAdvertisingDataSet(advertiserId, status); + sendToCallback(advertiserId, () -> callback.onAdvertisingDataSet(advertiserId, status)); } - void onScanResponseDataSet(int advertiserId, int status) throws Exception { + void onScanResponseDataSet(int advertiserId, int status) { + checkThread(); Log.d(TAG, "onScanResponseDataSet() advertiserId=" + advertiserId + ", status=" + status); Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId); @@ -528,11 +525,10 @@ public class AdvertiseManager { } IAdvertisingSetCallback callback = entry.getValue().callback; - callback.onScanResponseDataSet(advertiserId, status); + sendToCallback(advertiserId, () -> callback.onScanResponseDataSet(advertiserId, status)); } - void onAdvertisingParametersUpdated(int advertiserId, int txPower, int status) - throws Exception { + void onAdvertisingParametersUpdated(int advertiserId, int txPower, int status) { Log.d( TAG, "onAdvertisingParametersUpdated() advertiserId=" @@ -541,6 +537,7 @@ public class AdvertiseManager { + txPower + ", status=" + status); + checkThread(); Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId); if (entry == null) { @@ -549,16 +546,19 @@ public class AdvertiseManager { } IAdvertisingSetCallback callback = entry.getValue().callback; - callback.onAdvertisingParametersUpdated(advertiserId, txPower, status); + sendToCallback( + advertiserId, + () -> callback.onAdvertisingParametersUpdated(advertiserId, txPower, status)); } - void onPeriodicAdvertisingParametersUpdated(int advertiserId, int status) throws Exception { + void onPeriodicAdvertisingParametersUpdated(int advertiserId, int status) { Log.d( TAG, "onPeriodicAdvertisingParametersUpdated() advertiserId=" + advertiserId + ", status=" + status); + checkThread(); Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId); if (entry == null) { @@ -569,16 +569,19 @@ public class AdvertiseManager { } IAdvertisingSetCallback callback = entry.getValue().callback; - callback.onPeriodicAdvertisingParametersUpdated(advertiserId, status); + sendToCallback( + advertiserId, + () -> callback.onPeriodicAdvertisingParametersUpdated(advertiserId, status)); } - void onPeriodicAdvertisingDataSet(int advertiserId, int status) throws Exception { + void onPeriodicAdvertisingDataSet(int advertiserId, int status) { Log.d( TAG, "onPeriodicAdvertisingDataSet() advertiserId=" + advertiserId + ", status=" + status); + checkThread(); Map.Entry<IBinder, AdvertiserInfo> entry = findAdvertiser(advertiserId); if (entry == null) { @@ -587,11 +590,11 @@ public class AdvertiseManager { } IAdvertisingSetCallback callback = entry.getValue().callback; - callback.onPeriodicAdvertisingDataSet(advertiserId, status); + sendToCallback( + advertiserId, () -> callback.onPeriodicAdvertisingDataSet(advertiserId, status)); } - void onPeriodicAdvertisingEnabled(int advertiserId, boolean enable, int status) - throws Exception { + void onPeriodicAdvertisingEnabled(int advertiserId, boolean enable, int status) { Log.d( TAG, "onPeriodicAdvertisingEnabled() advertiserId=" @@ -604,9 +607,12 @@ public class AdvertiseManager { Log.i(TAG, "onAdvertisingSetEnable() - bad advertiserId " + advertiserId); return; } + checkThread(); IAdvertisingSetCallback callback = entry.getValue().callback; - callback.onPeriodicAdvertisingEnabled(advertiserId, enable, status); + sendToCallback( + advertiserId, + () -> callback.onPeriodicAdvertisingEnabled(advertiserId, enable, status)); AppAdvertiseStats stats = mAdvertiserMap.getAppAdvertiseStatsById(advertiserId); if (stats != null) { @@ -614,4 +620,52 @@ public class AdvertiseManager { } } + void doOnAdvertiseThread(Runnable r) { + if (mIsAvailable) { + if (Flags.advertiseThread()) { + mHandler.post( + () -> { + if (mIsAvailable) { + r.run(); + } + }); + } else { + r.run(); + } + } + } + + private void forceRunSyncOnAdvertiseThread(Runnable r) { + if (!Flags.advertiseThread()) { + r.run(); + return; + } + final CompletableFuture<Void> future = new CompletableFuture<>(); + mHandler.postAtFrontOfQueue( + () -> { + r.run(); + future.complete(null); + }); + try { + future.get(RUN_SYNC_WAIT_TIME_MS, TimeUnit.MILLISECONDS); + } catch (InterruptedException | TimeoutException | ExecutionException e) { + Log.w(TAG, "Unable to complete sync task: " + e); + } + } + + private void checkThread() { + if (Flags.advertiseThread() + && !mHandler.getLooper().isCurrentThread() + && !Utils.isInstrumentationTestMode()) { + throw new IllegalStateException("Not on advertise thread"); + } + } + + private void sendToCallback(int advertiserId, CallbackWrapper wrapper) { + try { + wrapper.call(); + } catch (RemoteException e) { + Log.i(TAG, "RemoteException in callback for advertiserId: " + advertiserId); + } + } } diff --git a/android/app/src/com/android/bluetooth/gatt/AdvertiseManagerNativeInterface.java b/android/app/src/com/android/bluetooth/gatt/AdvertiseManagerNativeInterface.java index 215c709970..6cdd47f97e 100644 --- a/android/app/src/com/android/bluetooth/gatt/AdvertiseManagerNativeInterface.java +++ b/android/app/src/com/android/bluetooth/gatt/AdvertiseManagerNativeInterface.java @@ -19,11 +19,12 @@ package com.android.bluetooth.gatt; import android.bluetooth.le.AdvertisingSetParameters; import android.bluetooth.le.PeriodicAdvertisingParameters; +import androidx.annotation.VisibleForTesting; + import com.android.internal.annotations.GuardedBy; -import com.android.internal.annotations.VisibleForTesting; /** Native interface for AdvertiseManager */ -@VisibleForTesting +@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) public class AdvertiseManagerNativeInterface { private static final String TAG = AdvertiseManagerNativeInterface.class.getSimpleName(); @@ -121,43 +122,47 @@ public class AdvertiseManagerNativeInterface { setPeriodicAdvertisingEnableNative(advertiserId, enable); } - void onAdvertisingSetStarted(int regId, int advertiserId, int txPower, int status) - throws Exception { - mManager.onAdvertisingSetStarted(regId, advertiserId, txPower, status); + void onAdvertisingSetStarted(int regId, int advertiserId, int txPower, int status) { + mManager.doOnAdvertiseThread( + () -> mManager.onAdvertisingSetStarted(regId, advertiserId, txPower, status)); } - void onOwnAddressRead(int advertiserId, int addressType, String address) throws Exception { - mManager.onOwnAddressRead(advertiserId, addressType, address); + void onOwnAddressRead(int advertiserId, int addressType, String address) { + mManager.doOnAdvertiseThread( + () -> mManager.onOwnAddressRead(advertiserId, addressType, address)); } - void onAdvertisingEnabled(int advertiserId, boolean enable, int status) throws Exception { - mManager.onAdvertisingEnabled(advertiserId, enable, status); + void onAdvertisingEnabled(int advertiserId, boolean enable, int status) { + mManager.doOnAdvertiseThread( + () -> mManager.onAdvertisingEnabled(advertiserId, enable, status)); } - void onAdvertisingDataSet(int advertiserId, int status) throws Exception { - mManager.onAdvertisingDataSet(advertiserId, status); + void onAdvertisingDataSet(int advertiserId, int status) { + mManager.doOnAdvertiseThread(() -> mManager.onAdvertisingDataSet(advertiserId, status)); } - void onScanResponseDataSet(int advertiserId, int status) throws Exception { - mManager.onScanResponseDataSet(advertiserId, status); + void onScanResponseDataSet(int advertiserId, int status) { + mManager.doOnAdvertiseThread(() -> mManager.onScanResponseDataSet(advertiserId, status)); } - void onAdvertisingParametersUpdated(int advertiserId, int txPower, int status) - throws Exception { - mManager.onAdvertisingParametersUpdated(advertiserId, txPower, status); + void onAdvertisingParametersUpdated(int advertiserId, int txPower, int status) { + mManager.doOnAdvertiseThread( + () -> mManager.onAdvertisingParametersUpdated(advertiserId, txPower, status)); } - void onPeriodicAdvertisingParametersUpdated(int advertiserId, int status) throws Exception { - mManager.onPeriodicAdvertisingParametersUpdated(advertiserId, status); + void onPeriodicAdvertisingParametersUpdated(int advertiserId, int status) { + mManager.doOnAdvertiseThread( + () -> mManager.onPeriodicAdvertisingParametersUpdated(advertiserId, status)); } - void onPeriodicAdvertisingDataSet(int advertiserId, int status) throws Exception { - mManager.onPeriodicAdvertisingDataSet(advertiserId, status); + void onPeriodicAdvertisingDataSet(int advertiserId, int status) { + mManager.doOnAdvertiseThread( + () -> mManager.onPeriodicAdvertisingDataSet(advertiserId, status)); } - void onPeriodicAdvertisingEnabled(int advertiserId, boolean enable, int status) - throws Exception { - mManager.onPeriodicAdvertisingEnabled(advertiserId, enable, status); + void onPeriodicAdvertisingEnabled(int advertiserId, boolean enable, int status) { + mManager.doOnAdvertiseThread( + () -> mManager.onPeriodicAdvertisingEnabled(advertiserId, enable, status)); } private native void initializeNative(); diff --git a/android/app/src/com/android/bluetooth/gatt/GattService.java b/android/app/src/com/android/bluetooth/gatt/GattService.java index cc2e759f61..e9563f8908 100644 --- a/android/app/src/com/android/bluetooth/gatt/GattService.java +++ b/android/app/src/com/android/bluetooth/gatt/GattService.java @@ -51,6 +51,7 @@ import android.content.pm.PackageManager.PackageInfoFlags; import android.content.res.Resources; import android.os.Binder; import android.os.Build; +import android.os.HandlerThread; import android.os.IBinder; import android.os.ParcelUuid; import android.os.RemoteException; @@ -156,6 +157,7 @@ public class GattService extends ProfileService { private final DistanceMeasurementManager mDistanceMeasurementManager; private final ActivityManager mActivityManager; private final PackageManager mPackageManager; + private final HandlerThread mHandlerThread; public GattService(AdapterService adapterService) { super(requireNonNull(adapterService)); @@ -169,7 +171,12 @@ public class GattService extends ProfileService { mNativeInterface = GattObjectsFactory.getInstance().getNativeInterface(); mNativeInterface.init(this); - mAdvertiseManager = new AdvertiseManager(this); + + // Create a thread to handle LE operations + mHandlerThread = new HandlerThread("Bluetooth LE"); + mHandlerThread.start(); + + mAdvertiseManager = new AdvertiseManager(mAdapterService, mHandlerThread.getLooper()); if (!Flags.scanManagerRefactor()) { mScanController = new ScanController(adapterService); @@ -209,7 +216,6 @@ public class GattService extends ProfileService { if (mScanController != null) { mScanController.stop(); } - mAdvertiseManager.clear(); mClientMap.clear(); mRestrictedHandles.clear(); mServerMap.clear(); @@ -224,6 +230,7 @@ public class GattService extends ProfileService { mNativeInterface.cleanup(); mAdvertiseManager.cleanup(); mDistanceMeasurementManager.cleanup(); + mHandlerThread.quit(); } /** This is only used when Flags.scanManagerRefactor() is true. */ diff --git a/android/app/src/com/android/bluetooth/hfp/HeadsetStateMachine.java b/android/app/src/com/android/bluetooth/hfp/HeadsetStateMachine.java index fd56faa613..fb0ed9d2f0 100644 --- a/android/app/src/com/android/bluetooth/hfp/HeadsetStateMachine.java +++ b/android/app/src/com/android/bluetooth/hfp/HeadsetStateMachine.java @@ -55,7 +55,6 @@ import com.android.bluetooth.btservice.AdapterService; import com.android.bluetooth.btservice.MetricsLogger; import com.android.bluetooth.btservice.ProfileService; import com.android.bluetooth.btservice.storage.DatabaseManager; -import com.android.bluetooth.flags.Flags; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.State; import com.android.internal.util.StateMachine; @@ -1122,12 +1121,6 @@ class HeadsetStateMachine extends StateMachine { } } break; - case INTENT_SCO_VOLUME_CHANGED: - if (Flags.hfpAllowVolumeChangeWithoutSco()) { - // when flag is removed, remove INTENT_SCO_VOLUME_CHANGED case in AudioOn - processIntentScoVolume((Intent) message.obj, mDevice); - } - break; case INTENT_CONNECTION_ACCESS_REPLY: handleAccessPermissionResult((Intent) message.obj); break; @@ -1630,8 +1623,6 @@ class HeadsetStateMachine extends StateMachine { break; } case INTENT_SCO_VOLUME_CHANGED: - // TODO: b/362313390 Remove this case once the fix is in place because this - // message will be handled by the ConnectedBase state. processIntentScoVolume((Intent) message.obj, mDevice); break; case STACK_EVENT: diff --git a/android/app/src/com/android/bluetooth/le_scan/ScanController.java b/android/app/src/com/android/bluetooth/le_scan/ScanController.java index 22aecf7c6d..7f39fdaaf1 100644 --- a/android/app/src/com/android/bluetooth/le_scan/ScanController.java +++ b/android/app/src/com/android/bluetooth/le_scan/ScanController.java @@ -759,14 +759,15 @@ public class ScanController { } } } - if (permittedResults.isEmpty()) { - return; - } } if (client.hasDisavowedLocation) { permittedResults.removeIf(mLocationDenylistPredicate); } + if (permittedResults.isEmpty()) { + mScanManager.callbackDone(scannerId, status); + return; + } if (app.mCallback != null) { app.mCallback.onBatchScanResults(permittedResults); @@ -792,6 +793,9 @@ public class ScanController { @SuppressWarnings("NonApiType") private void sendBatchScanResults( ScannerMap.ScannerApp app, ScanClient client, ArrayList<ScanResult> results) { + if (results.isEmpty()) { + return; + } try { if (app.mCallback != null) { if (mScanManager.isAutoBatchScanClientEnabled(client)) { @@ -835,14 +839,11 @@ public class ScanController { } } } - if (permittedResults.isEmpty()) { - return; - } } if (client.filters == null || client.filters.isEmpty()) { sendBatchScanResults(app, client, permittedResults); - // TODO: Question to reviewer: Shouldn't there be a return here? + return; } // Reconstruct the scan results. ArrayList<ScanResult> results = new ArrayList<ScanResult>(); diff --git a/android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientServiceTest.java index cc3304c21d..c1c7734f2e 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientServiceTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientServiceTest.java @@ -6360,7 +6360,8 @@ public class BassClientServiceTest { @Test @EnableFlags({ Flags.FLAG_LEAUDIO_BROADCAST_RESYNC_HELPER, - Flags.FLAG_LEAUDIO_BROADCAST_EXTRACT_PERIODIC_SCANNER_FROM_STATE_MACHINE + Flags.FLAG_LEAUDIO_BROADCAST_EXTRACT_PERIODIC_SCANNER_FROM_STATE_MACHINE, + Flags.FLAG_LEAUDIO_MONITOR_UNICAST_SOURCE_WHEN_MANAGED_BY_BROADCAST_DELEGATOR }) public void sinkUnintentional_handleUnicastSourceStreamStatusChange_withoutScanning() { sinkUnintentionalWithoutScanning(); @@ -6369,7 +6370,6 @@ public class BassClientServiceTest { mBassClientService.handleUnicastSourceStreamStatusChange( 0 /* STATUS_LOCAL_STREAM_REQUESTED */); verifyStopBigMonitoringWithUnsync(); - verifyRemoveMessageAndInjectSourceRemoval(); checkNoResumeSynchronizationByBig(); /* Unicast finished streaming */ @@ -6382,7 +6382,8 @@ public class BassClientServiceTest { @Test @EnableFlags({ Flags.FLAG_LEAUDIO_BROADCAST_RESYNC_HELPER, - Flags.FLAG_LEAUDIO_BROADCAST_EXTRACT_PERIODIC_SCANNER_FROM_STATE_MACHINE + Flags.FLAG_LEAUDIO_BROADCAST_EXTRACT_PERIODIC_SCANNER_FROM_STATE_MACHINE, + Flags.FLAG_LEAUDIO_MONITOR_UNICAST_SOURCE_WHEN_MANAGED_BY_BROADCAST_DELEGATOR }) public void sinkUnintentional_handleUnicastSourceStreamStatusChange_duringScanning() { sinkUnintentionalDuringScanning(); @@ -6391,7 +6392,6 @@ public class BassClientServiceTest { mBassClientService.handleUnicastSourceStreamStatusChange( 0 /* STATUS_LOCAL_STREAM_REQUESTED */); verifyStopBigMonitoringWithoutUnsync(); - verifyRemoveMessageAndInjectSourceRemoval(); checkNoResumeSynchronizationByBig(); /* Unicast finished streaming */ @@ -6654,6 +6654,44 @@ public class BassClientServiceTest { @Test @EnableFlags({ Flags.FLAG_LEAUDIO_BROADCAST_RESYNC_HELPER, + Flags.FLAG_LEAUDIO_BROADCAST_EXTRACT_PERIODIC_SCANNER_FROM_STATE_MACHINE, + Flags.FLAG_LEAUDIO_BROADCAST_ASSISTANT_PERIPHERAL_ENTRUSTMENT, + Flags.FLAG_LEAUDIO_MONITOR_UNICAST_SOURCE_WHEN_MANAGED_BY_BROADCAST_DELEGATOR + }) + public void hostIntentional_handleUnicastSourceStreamStatusChange_beforeResumeCompleted() { + prepareSynchronizedPairAndStopSearching(); + + /* Unicast would like to stream */ + mBassClientService.handleUnicastSourceStreamStatusChange( + 0 /* STATUS_LOCAL_STREAM_REQUESTED */); + checkNoSinkPause(); + + /* Unicast finished streaming */ + mBassClientService.handleUnicastSourceStreamStatusChange( + 2 /* STATUS_LOCAL_STREAM_SUSPENDED */); + mInOrderMethodProxy + .verify(mMethodProxy) + .periodicAdvertisingManagerRegisterSync( + any(), any(), anyInt(), anyInt(), any(), any()); + + /* Unicast would like to stream again before previous resume was complete*/ + mBassClientService.handleUnicastSourceStreamStatusChange( + 0 /* STATUS_LOCAL_STREAM_REQUESTED */); + + /* Unicast finished streaming */ + mBassClientService.handleUnicastSourceStreamStatusChange( + 2 /* STATUS_LOCAL_STREAM_SUSPENDED */); + mInOrderMethodProxy + .verify(mMethodProxy) + .periodicAdvertisingManagerRegisterSync( + any(), any(), anyInt(), anyInt(), any(), any()); + onSyncEstablished(mSourceDevice, TEST_SYNC_HANDLE); // In case of add source to inactive + verifyAllGroupMembersGettingUpdateOrAddSource(createBroadcastMetadata(TEST_BROADCAST_ID)); + } + + @Test + @EnableFlags({ + Flags.FLAG_LEAUDIO_BROADCAST_RESYNC_HELPER, Flags.FLAG_LEAUDIO_BROADCAST_EXTRACT_PERIODIC_SCANNER_FROM_STATE_MACHINE }) public void hostIntentional_handleUnicastSourceStreamStatusChangeNoContext_withoutScanning() { diff --git a/android/app/tests/unit/src/com/android/bluetooth/btservice/BondStateMachineTest.java b/android/app/tests/unit/src/com/android/bluetooth/btservice/BondStateMachineTest.java index cc40c6af72..e274ac6eed 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/btservice/BondStateMachineTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/btservice/BondStateMachineTest.java @@ -218,7 +218,7 @@ public class BondStateMachineTest { RemoteDevices.DeviceProperties testDeviceProperties = mRemoteDevices.addDeviceProperties(TEST_BT_ADDR_BYTES); - testDeviceProperties.mUuids = TEST_UUIDS; + testDeviceProperties.mUuidsBrEdr = TEST_UUIDS; BluetoothDevice testDevice = testDeviceProperties.getDevice(); assertThat(testDevice).isNotNull(); @@ -228,7 +228,7 @@ public class BondStateMachineTest { bondingMsg.arg2 = AbstractionLayer.BT_STATUS_RMT_DEV_DOWN; mBondStateMachine.sendMessage(bondingMsg); - pendingDeviceProperties.mUuids = TEST_UUIDS; + pendingDeviceProperties.mUuidsBrEdr = TEST_UUIDS; Message uuidUpdateMsg = mBondStateMachine.obtainMessage(BondStateMachine.UUID_UPDATE); uuidUpdateMsg.obj = pendingDevice; @@ -634,7 +634,7 @@ public class BondStateMachineTest { } if (uuids != null) { // Add dummy UUID for the device. - mDeviceProperties.mUuids = TEST_UUIDS; + mDeviceProperties.mUuidsBrEdr = TEST_UUIDS; } testSendIntentCase( oldState, diff --git a/android/app/tests/unit/src/com/android/bluetooth/gatt/AdvertiseBinderTest.java b/android/app/tests/unit/src/com/android/bluetooth/gatt/AdvertiseBinderTest.java index 3a0abb2870..1eca1e2483 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/gatt/AdvertiseBinderTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/gatt/AdvertiseBinderTest.java @@ -16,6 +16,8 @@ package com.android.bluetooth.gatt; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -54,6 +56,13 @@ public class AdvertiseBinderTest { @Before public void setUp() { + doAnswer( + invocation -> { + ((Runnable) invocation.getArgument(0)).run(); + return null; + }) + .when(mAdvertiseManager) + .doOnAdvertiseThread(any()); mBinder = new AdvertiseBinder(mAdapterService, mAdvertiseManager); } diff --git a/android/app/tests/unit/src/com/android/bluetooth/gatt/AdvertiseManagerTest.java b/android/app/tests/unit/src/com/android/bluetooth/gatt/AdvertiseManagerTest.java index 6621a43331..4cd5423ce9 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/gatt/AdvertiseManagerTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/gatt/AdvertiseManagerTest.java @@ -27,15 +27,16 @@ import android.bluetooth.le.AdvertisingSetParameters; import android.bluetooth.le.IAdvertisingSetCallback; import android.bluetooth.le.PeriodicAdvertisingParameters; import android.os.IBinder; +import android.os.test.TestLooper; +import android.platform.test.flag.junit.FlagsParameterization; +import android.platform.test.flag.junit.SetFlagsRule; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; -import com.android.bluetooth.TestUtils; import com.android.bluetooth.btservice.AdapterService; +import com.android.bluetooth.flags.Flags; -import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -44,17 +45,21 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; +import platform.test.runner.parameterized.ParameterizedAndroidJunit4; +import platform.test.runner.parameterized.Parameters; + +import java.util.List; + /** Test cases for {@link AdvertiseManager}. */ @SmallTest -@RunWith(AndroidJUnit4.class) +@RunWith(ParameterizedAndroidJunit4.class) public class AdvertiseManagerTest { @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); + @Rule public final SetFlagsRule mSetFlagsRule; @Mock private AdapterService mAdapterService; - @Mock private GattService mService; - @Mock private AdvertiserMap mAdvertiserMap; @Mock private AdvertiseManagerNativeInterface mNativeInterface; @@ -66,10 +71,23 @@ public class AdvertiseManagerTest { private AdvertiseManager mAdvertiseManager; private int mAdvertiserId; + @Parameters(name = "{0}") + public static List<FlagsParameterization> getParams() { + return FlagsParameterization.allCombinationsOf(Flags.FLAG_ADVERTISE_THREAD); + } + + public AdvertiseManagerTest(FlagsParameterization flags) { + mSetFlagsRule = new SetFlagsRule(flags); + } + @Before public void setUp() throws Exception { - TestUtils.setAdapterService(mAdapterService); - mAdvertiseManager = new AdvertiseManager(mService, mNativeInterface, mAdvertiserMap); + mAdvertiseManager = + new AdvertiseManager( + mAdapterService, + new TestLooper().getLooper(), + mNativeInterface, + mAdvertiserMap); AdvertisingSetParameters parameters = new AdvertisingSetParameters.Builder().build(); AdvertiseData advertiseData = new AdvertiseData.Builder().build(); @@ -95,12 +113,7 @@ public class AdvertiseManagerTest { mCallback, InstrumentationRegistry.getTargetContext().getAttributionSource()); - mAdvertiserId = AdvertiseManager.sTempRegistrationId; - } - - @After - public void tearDown() throws Exception { - TestUtils.clearAdapterService(mAdapterService); + mAdvertiserId = mAdvertiseManager.mTempRegistrationId; } @Test diff --git a/android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetStateMachineTest.java b/android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetStateMachineTest.java index e594c924a8..0b42a50d92 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetStateMachineTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetStateMachineTest.java @@ -1793,28 +1793,6 @@ public class HeadsetStateMachineTest { } @Test - @EnableFlags({Flags.FLAG_HFP_ALLOW_VOLUME_CHANGE_WITHOUT_SCO}) - public void testVolumeChangeEvent_fromIntentWhenConnected() { - setUpConnectedState(); - int originalVolume = mHeadsetStateMachine.mSpeakerVolume; - mHeadsetStateMachine.mSpeakerVolume = 0; - int vol = 10; - - // Send INTENT_SCO_VOLUME_CHANGED message - Intent volumeChange = new Intent(AudioManager.ACTION_VOLUME_CHANGED); - volumeChange.putExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, vol); - - mHeadsetStateMachine.sendMessage( - HeadsetStateMachine.INTENT_SCO_VOLUME_CHANGED, volumeChange); - TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper()); - - // verify volume processed - verify(mNativeInterface).setVolume(mTestDevice, HeadsetHalConstants.VOLUME_TYPE_SPK, vol); - - mHeadsetStateMachine.mSpeakerVolume = originalVolume; - } - - @Test public void testVolumeChangeEvent_fromIntentWhenAudioOn() { setUpAudioOnState(); int originalVolume = mHeadsetStateMachine.mSpeakerVolume; diff --git a/android/app/tests/unit/src/com/android/bluetooth/le_scan/ScanControllerTest.java b/android/app/tests/unit/src/com/android/bluetooth/le_scan/ScanControllerTest.java index 2b19c1aa0b..ea051bc528 100644 --- a/android/app/tests/unit/src/com/android/bluetooth/le_scan/ScanControllerTest.java +++ b/android/app/tests/unit/src/com/android/bluetooth/le_scan/ScanControllerTest.java @@ -26,6 +26,7 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import android.bluetooth.BluetoothAdapter; @@ -47,7 +48,6 @@ import android.platform.test.flag.junit.SetFlagsRule; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import com.android.bluetooth.TestUtils; import com.android.bluetooth.btservice.AdapterService; @@ -55,6 +55,9 @@ import com.android.bluetooth.btservice.CompanionManager; import com.android.bluetooth.gatt.GattNativeInterface; import com.android.bluetooth.gatt.GattObjectsFactory; +import com.google.testing.junit.testparameterinjector.TestParameter; +import com.google.testing.junit.testparameterinjector.TestParameterInjector; + import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -73,7 +76,7 @@ import java.util.Set; /** Test cases for {@link ScanController}. */ @SmallTest -@RunWith(AndroidJUnit4.class) +@RunWith(TestParameterInjector.class) public class ScanControllerTest { @Rule public MockitoRule mockitoRule = MockitoJUnit.rule(); @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @@ -187,7 +190,8 @@ public class ScanControllerTest { } @Test - public void onBatchScanReportsInternal_deliverBatchScan() throws RemoteException { + public void onBatchScanReportsInternal_deliverBatchScan_full( + @TestParameter boolean expectResults) throws RemoteException { int status = 1; int scannerId = 2; int reportType = ScanManager.SCAN_RESULT_TYPE_FULL; @@ -200,28 +204,59 @@ public class ScanControllerTest { Set<ScanClient> scanClientSet = new HashSet<>(); ScanClient scanClient = new ScanClient(scannerId); scanClient.associatedDevices = new ArrayList<>(); - scanClient.associatedDevices.add("02:00:00:00:00:00"); scanClient.scannerId = scannerId; + if (expectResults) { + scanClient.hasScanWithoutLocationPermission = true; + } scanClientSet.add(scanClient); doReturn(scanClientSet).when(mScanManager).getFullBatchScanQueue(); doReturn(mApp).when(mScannerMap).getById(scanClient.scannerId); + IScannerCallback callback = mock(IScannerCallback.class); + mApp.mCallback = callback; mScanController.onBatchScanReportsInternal( status, scannerId, reportType, numRecords, recordData); verify(mScanManager).callbackDone(scannerId, status); + if (expectResults) { + verify(callback).onBatchScanResults(any()); + } else { + verify(callback, never()).onBatchScanResults(any()); + } + } - reportType = ScanManager.SCAN_RESULT_TYPE_TRUNCATED; - recordData = + @Test + public void onBatchScanReportsInternal_deliverBatchScan_truncated( + @TestParameter boolean expectResults) throws RemoteException { + int status = 1; + int scannerId = 2; + int reportType = ScanManager.SCAN_RESULT_TYPE_TRUNCATED; + int numRecords = 1; + byte[] recordData = new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x06, 0x04, 0x02, 0x02, 0x00, 0x00, 0x02 }; + + Set<ScanClient> scanClientSet = new HashSet<>(); + ScanClient scanClient = new ScanClient(scannerId); + scanClient.associatedDevices = new ArrayList<>(); + if (expectResults) { + scanClient.associatedDevices.add("02:00:00:00:00:00"); + } + scanClient.scannerId = scannerId; + scanClientSet.add(scanClient); doReturn(scanClientSet).when(mScanManager).getBatchScanQueue(); + doReturn(mApp).when(mScannerMap).getById(scanClient.scannerId); IScannerCallback callback = mock(IScannerCallback.class); mApp.mCallback = callback; mScanController.onBatchScanReportsInternal( status, scannerId, reportType, numRecords, recordData); - verify(callback).onBatchScanResults(any()); + verify(mScanManager).callbackDone(scannerId, status); + if (expectResults) { + verify(callback).onBatchScanResults(any()); + } else { + verify(callback, never()).onBatchScanResults(any()); + } } @Test diff --git a/android/pandora/server/src/A2dp.kt b/android/pandora/server/src/A2dp.kt index 6708dd1c46..0b5a0d6640 100644 --- a/android/pandora/server/src/A2dp.kt +++ b/android/pandora/server/src/A2dp.kt @@ -41,7 +41,6 @@ import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.cancel -import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.filter @@ -110,10 +109,6 @@ class A2dp(val context: Context) : A2DPImplBase(), Closeable { } } - // TODO: b/234891800, AVDTP start request sometimes never sent if playback starts too - // early. - delay(2000L) - val source = Source.newBuilder().setCookie(ByteString.copyFrom(device.getAddress(), "UTF-8")) OpenSourceResponse.newBuilder().setSource(source).build() @@ -147,10 +142,6 @@ class A2dp(val context: Context) : A2DPImplBase(), Closeable { } } - // TODO: b/234891800, AVDTP start request sometimes never sent if playback starts too - // early. - delay(2000L) - val source = Source.newBuilder().setCookie(ByteString.copyFrom(device.getAddress(), "UTF-8")) WaitSourceResponse.newBuilder().setSource(source).build() diff --git a/flags/Android.bp b/flags/Android.bp index 8a1dc66552..80a57891a5 100644 --- a/flags/Android.bp +++ b/flags/Android.bp @@ -31,6 +31,7 @@ aconfig_declarations { "hid.aconfig", "l2cap.aconfig", "le_advertising.aconfig", + "le_scanning.aconfig", "leaudio.aconfig", "mapclient.aconfig", "mcp.aconfig", diff --git a/flags/BUILD.gn b/flags/BUILD.gn index 9f96d85933..da5aa8621b 100644 --- a/flags/BUILD.gn +++ b/flags/BUILD.gn @@ -24,6 +24,7 @@ aconfig("bluetooth_flags_c_lib") { "hid.aconfig", "l2cap.aconfig", "le_advertising.aconfig", + "le_scanning.aconfig", "leaudio.aconfig", "mapclient.aconfig", "mcp.aconfig", diff --git a/flags/bta_dm.aconfig b/flags/bta_dm.aconfig index d91f3168b7..287a0f0187 100644 --- a/flags/bta_dm.aconfig +++ b/flags/bta_dm.aconfig @@ -9,13 +9,6 @@ flag { } flag { - name: "bta_dm_discover_both" - namespace: "bluetooth" - description: "perform both LE and Classic service discovery simulteanously on capable devices" - bug: "339217881" -} - -flag { name: "cancel_open_discovery_client" namespace: "bluetooth" description: "Cancel connection from discovery client correctly" diff --git a/flags/gap.aconfig b/flags/gap.aconfig index 8fe10911d5..5da5144aa1 100644 --- a/flags/gap.aconfig +++ b/flags/gap.aconfig @@ -281,3 +281,13 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "batch_scan_optimization" + namespace: "bluetooth" + description: "Optimized batch scan for less wakeups" + bug: "392132489" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/flags/hfp.aconfig b/flags/hfp.aconfig index 7b81b483b4..026c3b22b3 100644 --- a/flags/hfp.aconfig +++ b/flags/hfp.aconfig @@ -100,16 +100,6 @@ flag { } flag { - name: "hfp_allow_volume_change_without_sco" - namespace: "bluetooth" - description: "Allow Audio Fwk to change SCO volume when HFP profile is connected and SCO not connected" - bug: "362313390" - metadata { - purpose: PURPOSE_BUGFIX - } -} - -flag { name: "choose_wrong_hfp_codec_in_specific_config" namespace: "bluetooth" description: "Flag to fix codec selection in nego when the peer device only support NB and SWB." diff --git a/flags/le_scanning.aconfig b/flags/le_scanning.aconfig new file mode 100644 index 0000000000..0b4985e45e --- /dev/null +++ b/flags/le_scanning.aconfig @@ -0,0 +1,12 @@ +package: "com.android.bluetooth.flags" +container: "com.android.bt" + +flag { + name: "scan_results_in_main_thread" + namespace: "bluetooth" + description: "Use main thread for handling scan results" + bug: "392693506" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt index 5a506f21fb..60657262e3 100644 --- a/framework/api/system-current.txt +++ b/framework/api/system-current.txt @@ -53,7 +53,7 @@ package android.bluetooth { method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public void disableOptionalCodecs(@NonNull android.bluetooth.BluetoothDevice); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public void enableOptionalCodecs(@NonNull android.bluetooth.BluetoothDevice); method @Nullable @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public android.bluetooth.BufferConstraints getBufferConstraints(); - method @Nullable @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public android.bluetooth.BluetoothCodecStatus getCodecStatus(@NonNull android.bluetooth.BluetoothDevice); + method @Nullable @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}, conditional=true) public android.bluetooth.BluetoothCodecStatus getCodecStatus(@NonNull android.bluetooth.BluetoothDevice); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int getConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int getDynamicBufferSupport(); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int isOptionalCodecsEnabled(@NonNull android.bluetooth.BluetoothDevice); diff --git a/framework/java/android/bluetooth/BluetoothA2dp.java b/framework/java/android/bluetooth/BluetoothA2dp.java index 5bcd0789ab..54f2c702aa 100644 --- a/framework/java/android/bluetooth/BluetoothA2dp.java +++ b/framework/java/android/bluetooth/BluetoothA2dp.java @@ -724,20 +724,23 @@ public final class BluetoothA2dp implements BluetoothProfile { /** * Gets the current codec status (configuration and capability). * + * <p>This method requires the calling app to have the {@link + * android.Manifest.permission#BLUETOOTH_CONNECT} permission. Additionally, an app must either + * have the {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED} or be associated with the + * Companion Device manager (see {@link android.companion.CompanionDeviceManager#associate( + * AssociationRequest, android.companion.CompanionDeviceManager.Callback, Handler)}) + * * @param device the remote Bluetooth device. * @return the current codec status * @hide */ @SystemApi - @Nullable @RequiresLegacyBluetoothPermission @RequiresBluetoothConnectPermission @RequiresPermission( - allOf = { - BLUETOOTH_CONNECT, - BLUETOOTH_PRIVILEGED, - }) - public BluetoothCodecStatus getCodecStatus(@NonNull BluetoothDevice device) { + allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED}, + conditional = true) + public @Nullable BluetoothCodecStatus getCodecStatus(@NonNull BluetoothDevice device) { if (DBG) Log.d(TAG, "getCodecStatus(" + device + ")"); verifyDeviceNotNull(device, "getCodecStatus"); final IBluetoothA2dp service = getService(); diff --git a/framework/tests/bumble/src/android/bluetooth/DckL2capTest.kt b/framework/tests/bumble/src/android/bluetooth/DckL2capTest.kt index e658e8c645..ea41c55ee0 100644 --- a/framework/tests/bumble/src/android/bluetooth/DckL2capTest.kt +++ b/framework/tests/bumble/src/android/bluetooth/DckL2capTest.kt @@ -226,6 +226,51 @@ public class DckL2capTest() : Closeable { Log.d(TAG, "testReceive: done") } + @Test + @VirtualOnly + fun testReadReturnOnRemoteSocketDisconnect() { + Log.d(TAG, "testReadReturnonSocketDisconnect: Connect L2CAP") + var bluetoothSocket: BluetoothSocket? + val l2capServer = bluetoothAdapter.listenUsingInsecureL2capChannel() + val socketFlow = flow { emit(l2capServer.accept()) } + val connectResponse = createAndConnectL2capChannelWithBumble(l2capServer.psm) + runBlocking { + bluetoothSocket = socketFlow.first() + assertThat(connectResponse.hasChannel()).isTrue() + } + + val inputStream = bluetoothSocket!!.inputStream + + // block on read() on server thread + val readThread = Thread { + Log.d(TAG, "testReadReturnOnRemoteSocketDisconnect: Receive data on Android") + val ret = inputStream.read() + Log.d(TAG, "testReadReturnOnRemoteSocketDisconnect: read returns : " + ret) + Log.d( + TAG, + "testReadReturnOnRemoteSocketDisconnect: isConnected() : " + + bluetoothSocket!!.isConnected(), + ) + assertThat(ret).isEqualTo(-1) + assertThat(bluetoothSocket!!.isConnected()).isFalse() + } + readThread.start() + // check that socket is still connected + assertThat(bluetoothSocket!!.isConnected()).isTrue() + + // read() would be blocking till underlying l2cap is disconnected + Thread.sleep(1000 * 10) + Log.d(TAG, "testReadReturnOnRemoteSocketDisconnect: disconnect after 10 secs") + val disconnectRequest = + DisconnectRequest.newBuilder().setChannel(connectResponse.channel).build() + val disconnectResponse = mBumble.l2capBlocking().disconnect(disconnectRequest) + assertThat(disconnectResponse.hasSuccess()).isTrue() + inputStream.close() + bluetoothSocket?.close() + l2capServer.close() + Log.d(TAG, "testReadReturnOnRemoteSocketDisconnect: done") + } + private fun createAndConnectL2capChannelWithBumble(psm: Int): ConnectResponse { Log.d(TAG, "createAndConnectL2capChannelWithBumble") val remoteDevice = diff --git a/offload/hci/data.rs b/offload/hci/data.rs index 0e20e82029..bb4a452a59 100644 --- a/offload/hci/data.rs +++ b/offload/hci/data.rs @@ -120,7 +120,7 @@ impl<'a> IsoData<'a> { } } -impl<'a> Write for IsoData<'a> { +impl Write for IsoData<'_> { fn write(&self, w: &mut Writer) { let (pb_flag, hdr) = match self.sdu_fragment { IsoSduFragment::First { ref hdr, is_last: false } => (0b00, Some(hdr)), diff --git a/system/bta/dm/bta_dm_disc.cc b/system/bta/dm/bta_dm_disc.cc index e9c6ba5e77..c3977454f5 100644 --- a/system/bta/dm/bta_dm_disc.cc +++ b/system/bta/dm/bta_dm_disc.cc @@ -511,7 +511,7 @@ static void bta_dm_gatt_disc_complete(tCONN_ID conn_id, tGATT_STATUS status) { log::verbose("conn_id = {}, status = {}, sdp_pending = {}, le_pending = {}", conn_id, status, sdp_pending, le_pending); - if (com::android::bluetooth::flags::bta_dm_discover_both() && sdp_pending && !le_pending) { + if (sdp_pending && !le_pending) { /* LE Service discovery finished, and services were reported, but SDP is not * finished yet. gatt_close_timer closed the connection, and we received * this callback because of disconnection */ @@ -784,8 +784,7 @@ static void bta_dm_disc_sm_execute(tBTA_DM_DISC_EVT event, std::unique_ptr<tBTA_ "bad message type: {}", msg->index()); auto req = std::get<tBTA_DM_API_DISCOVER>(*msg); - if (com::android::bluetooth::flags::bta_dm_discover_both() && - is_same_device(req.bd_addr, bta_dm_discovery_cb.peer_bdaddr)) { + if (is_same_device(req.bd_addr, bta_dm_discovery_cb.peer_bdaddr)) { bta_dm_discover_services(std::get<tBTA_DM_API_DISCOVER>(*msg)); } else { bta_dm_queue_disc(std::get<tBTA_DM_API_DISCOVER>(*msg)); diff --git a/system/bta/hh/bta_hh_headtracker.cc b/system/bta/hh/bta_hh_headtracker.cc index c8b3e20f05..0282cb59d0 100644 --- a/system/bta/hh/bta_hh_headtracker.cc +++ b/system/bta/hh/bta_hh_headtracker.cc @@ -140,7 +140,10 @@ void bta_hh_headtracker_parse_service(tBTA_HH_DEV_CB* p_dev_cb, const gatt::Serv bool bta_hh_headtracker_supported(tBTA_HH_DEV_CB* p_dev_cb) { if (p_dev_cb->hid_srvc.headtracker_support == BTA_HH_UNKNOWN) { bluetooth::Uuid remote_uuids[BT_MAX_NUM_UUIDS] = {}; - bt_property_t remote_properties = {BT_PROPERTY_UUIDS, sizeof(remote_uuids), &remote_uuids}; + bt_property_t remote_properties = {com::android::bluetooth::flags::separate_service_storage() + ? BT_PROPERTY_UUIDS_LE + : BT_PROPERTY_UUIDS, + sizeof(remote_uuids), &remote_uuids}; const RawAddress& bd_addr = p_dev_cb->link_spec.addrt.bda; p_dev_cb->hid_srvc.headtracker_support = BTA_HH_UNAVAILABLE; diff --git a/system/bta/le_audio/state_machine.cc b/system/bta/le_audio/state_machine.cc index 8d8ca50ab0..cc0a79690c 100644 --- a/system/bta/le_audio/state_machine.cc +++ b/system/bta/le_audio/state_machine.cc @@ -1084,7 +1084,7 @@ public: /* Note, that this type is actually LONG WRITE. * Meaning all the Prepare Writes plus Execute is handled in the stack */ - write_type = GATT_WRITE_PREPARE; + write_type = GATT_WRITE; } BtaGattQueue::WriteCharacteristic(leAudioDevice->conn_id_, leAudioDevice->ctp_hdls_.val_hdl, diff --git a/system/bta/test/bta_disc_test.cc b/system/bta/test/bta_disc_test.cc index 58421f2962..3a1dbc9882 100644 --- a/system/bta/test/bta_disc_test.cc +++ b/system/bta/test/bta_disc_test.cc @@ -219,61 +219,7 @@ int gatt_service_cb_both_call_cnt = 0; /* This test exercises the usual service discovery flow when bonding to * dual-mode, CTKD capable device on LE transport. */ -TEST_F_WITH_FLAGS(BtaInitializedTest, bta_dm_disc_both_transports_flag_disabled, - REQUIRES_FLAGS_DISABLED(ACONFIG_FLAG(TEST_BT, bta_dm_discover_both))) { - bta_dm_disc_start(true); - - std::promise<void> gatt_triggered; - int gatt_call_cnt = 0; - base::RepeatingCallback<void(const RawAddress&)> gatt_performer = - base::BindLambdaForTesting([&](const RawAddress& /*bd_addr*/) { - gatt_call_cnt++; - gatt_triggered.set_value(); - }); - bta_dm_disc_override_gatt_performer_for_testing(gatt_performer); - - int sdp_call_cnt = 0; - base::RepeatingCallback<void(tBTA_DM_SDP_STATE*)> sdp_performer = - base::BindLambdaForTesting([&](tBTA_DM_SDP_STATE* /*sdp_state*/) { sdp_call_cnt++; }); - bta_dm_disc_override_sdp_performer_for_testing(sdp_performer); - - gatt_service_cb_both_call_cnt = 0; - service_cb_both_call_cnt = 0; - - bta_dm_disc_start_service_discovery( - {[](RawAddress, std::vector<bluetooth::Uuid>&, bool) {}, nullptr, - [](RawAddress /*addr*/, const std::vector<bluetooth::Uuid>&, tBTA_STATUS) { - service_cb_both_call_cnt++; - }}, - kRawAddress, BT_TRANSPORT_BR_EDR); - EXPECT_EQ(sdp_call_cnt, 1); - - bta_dm_disc_start_service_discovery( - {[](RawAddress, std::vector<bluetooth::Uuid>&, bool) { gatt_service_cb_both_call_cnt++; }, - nullptr, [](RawAddress /*addr*/, const std::vector<bluetooth::Uuid>&, tBTA_STATUS) {}}, - kRawAddress, BT_TRANSPORT_LE); - - // GATT discovery is queued, until SDP finishes - EXPECT_EQ(gatt_call_cnt, 0); - - bta_dm_sdp_finished(kRawAddress, BTA_SUCCESS, {}, {}); - EXPECT_EQ(service_cb_both_call_cnt, 1); - - // SDP finished, wait until GATT is triggered. - EXPECT_EQ(std::future_status::ready, - gatt_triggered.get_future().wait_for(std::chrono::seconds(1))); - bta_dm_gatt_finished(kRawAddress, BTA_SUCCESS); - EXPECT_EQ(gatt_service_cb_both_call_cnt, 1); - - bta_dm_disc_override_sdp_performer_for_testing({}); - bta_dm_disc_override_gatt_performer_for_testing({}); -} - -/* This test exercises the usual service discovery flow when bonding to - * dual-mode, CTKD capable device on LE transport. - */ -TEST_F_WITH_FLAGS(BtaInitializedTest, bta_dm_disc_both_transports_flag_enabled, - REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(TEST_BT, bta_dm_discover_both))) { +TEST_F(BtaInitializedTest, bta_dm_disc_both_transports) { bta_dm_disc_start(true); int gatt_call_cnt = 0; diff --git a/system/btif/include/btif_dm.h b/system/btif/include/btif_dm.h index b165baa441..9069c97b76 100644 --- a/system/btif/include/btif_dm.h +++ b/system/btif/include/btif_dm.h @@ -151,6 +151,7 @@ void btif_dm_get_ble_local_keys(tBTA_DM_BLE_LOCAL_KEY_MASK* p_key_mask, Octet16* tBTA_BLE_LOCAL_ID_KEYS* p_id_keys); void btif_update_remote_properties(const RawAddress& bd_addr, BD_NAME bd_name, DEV_CLASS dev_class, tBT_DEVICE_TYPE dev_type); +bool btif_is_interesting_le_service(const bluetooth::Uuid& uuid); bool check_cod_hid(const RawAddress& bd_addr); bool check_cod_hid_major(const RawAddress& bd_addr, uint32_t cod); diff --git a/system/btif/include/btif_storage.h b/system/btif/include/btif_storage.h index 0c1043faae..01432bade6 100644 --- a/system/btif/include/btif_storage.h +++ b/system/btif/include/btif_storage.h @@ -446,6 +446,7 @@ bt_status_t btif_storage_set_hid_connection_policy(const tAclLinkSpec& link_spec bt_status_t btif_storage_get_hid_connection_policy(const tAclLinkSpec& link_spec, bool* reconnect_allowed); +void btif_storage_migrate_services(); /****************************************************************************** * Exported for unit tests *****************************************************************************/ diff --git a/system/btif/src/btif_core.cc b/system/btif/src/btif_core.cc index f0d06373b6..8e481daed0 100644 --- a/system/btif/src/btif_core.cc +++ b/system/btif/src/btif_core.cc @@ -134,7 +134,12 @@ int btif_is_enabled(void) { return (!btif_is_dut_mode()) && (stack_manager_get_interface()->get_stack_is_running()); } -void btif_init_ok() { btif_dm_load_ble_local_keys(); } +void btif_init_ok() { + btif_dm_load_ble_local_keys(); + if (com::android::bluetooth::flags::separate_service_storage()) { + btif_storage_migrate_services(); + } +} /******************************************************************************* * @@ -290,7 +295,7 @@ void btif_dut_mode_send(uint16_t opcode, uint8_t* buf, uint8_t len) { ****************************************************************************/ static bt_status_t btif_in_get_adapter_properties(void) { - const static uint32_t NUM_ADAPTER_PROPERTIES = 5; + static const uint32_t NUM_ADAPTER_PROPERTIES = 5; bt_property_t properties[NUM_ADAPTER_PROPERTIES]; uint32_t num_props = 0; @@ -340,12 +345,13 @@ static bt_status_t btif_in_get_adapter_properties(void) { } static bt_status_t btif_in_get_remote_device_properties(RawAddress* bd_addr) { - bt_property_t remote_properties[8]; + bt_property_t remote_properties[9]; uint32_t num_props = 0; bt_bdname_t name, alias; uint32_t cod, devtype; Uuid remote_uuids[BT_MAX_NUM_UUIDS]; + Uuid remote_uuids_le[BT_MAX_NUM_UUIDS]; memset(remote_properties, 0, sizeof(remote_properties)); BTIF_STORAGE_FILL_PROPERTY(&remote_properties[num_props], BT_PROPERTY_BDNAME, sizeof(name), @@ -369,10 +375,17 @@ static bt_status_t btif_in_get_remote_device_properties(RawAddress* bd_addr) { num_props++; BTIF_STORAGE_FILL_PROPERTY(&remote_properties[num_props], BT_PROPERTY_UUIDS, sizeof(remote_uuids), - remote_uuids); + &remote_uuids); btif_storage_get_remote_device_property(bd_addr, &remote_properties[num_props]); num_props++; + if (com::android::bluetooth::flags::separate_service_storage()) { + BTIF_STORAGE_FILL_PROPERTY(&remote_properties[num_props], BT_PROPERTY_UUIDS_LE, + sizeof(remote_uuids_le), &remote_uuids_le); + btif_storage_get_remote_device_property(bd_addr, &remote_properties[num_props]); + num_props++; + } + GetInterfaceToProfiles()->events->invoke_remote_device_properties_cb( BT_STATUS_SUCCESS, *bd_addr, num_props, remote_properties); diff --git a/system/btif/src/btif_dm.cc b/system/btif/src/btif_dm.cc index a023c22970..6f83aee781 100644 --- a/system/btif/src/btif_dm.cc +++ b/system/btif/src/btif_dm.cc @@ -1517,15 +1517,16 @@ static void btif_dm_search_devices_evt(tBTA_DM_SEARCH_EVT event, tBTA_DM_SEARCH* } /* Returns true if |uuid| should be passed as device property */ -static bool btif_is_interesting_le_service(bluetooth::Uuid uuid) { +bool btif_is_interesting_le_service(const bluetooth::Uuid& uuid) { return uuid.As16Bit() == UUID_SERVCLASS_LE_HID || uuid == UUID_HEARING_AID || uuid == UUID_VC || uuid == UUID_CSIS || uuid == UUID_LE_AUDIO || uuid == UUID_LE_MIDI || uuid == UUID_HAS || uuid == UUID_BASS || uuid == UUID_BATTERY || uuid == ANDROID_HEADTRACKER_SERVICE_UUID; } -static bt_status_t btif_get_existing_uuids(RawAddress* bd_addr, Uuid* existing_uuids) { +static bt_status_t btif_get_existing_uuids(RawAddress* bd_addr, Uuid* existing_uuids, + bt_property_type_t property_type = BT_PROPERTY_UUIDS) { bt_property_t tmp_prop; - BTIF_STORAGE_FILL_PROPERTY(&tmp_prop, BT_PROPERTY_UUIDS, sizeof(*existing_uuids), existing_uuids); + BTIF_STORAGE_FILL_PROPERTY(&tmp_prop, property_type, sizeof(*existing_uuids), existing_uuids); return btif_storage_get_remote_device_property(bd_addr, &tmp_prop); } @@ -1537,9 +1538,10 @@ static bool btif_is_gatt_service_discovery_post_pairing(const RawAddress bd_addr (pairing_cb.gatt_over_le == btif_dm_pairing_cb_t::ServiceDiscoveryState::SCHEDULED); } -static void btif_merge_existing_uuids(RawAddress& addr, std::set<Uuid>* uuids) { +static void btif_merge_existing_uuids(RawAddress& addr, std::set<Uuid>* uuids, + bt_property_type_t property_type = BT_PROPERTY_UUIDS) { Uuid existing_uuids[BT_MAX_NUM_UUIDS] = {}; - bt_status_t lookup_result = btif_get_existing_uuids(&addr, existing_uuids); + bt_status_t lookup_result = btif_get_existing_uuids(&addr, existing_uuids, property_type); if (lookup_result == BT_STATUS_FAIL) { return; @@ -1550,18 +1552,14 @@ static void btif_merge_existing_uuids(RawAddress& addr, std::set<Uuid>* uuids) { if (btif_should_ignore_uuid(uuid)) { continue; } - if (btif_is_interesting_le_service(uuid)) { - log::info("interesting le service {} insert", uuid.ToString()); - uuids->insert(uuid); - } + + uuids->insert(uuid); } } static void btif_on_service_discovery_results(RawAddress bd_addr, const std::vector<bluetooth::Uuid>& uuids_param, tBTA_STATUS result) { - bt_property_t prop; - std::vector<uint8_t> property_value; std::set<Uuid> uuids; bool a2dp_sink_capable = false; @@ -1589,8 +1587,12 @@ static void btif_on_service_discovery_results(RawAddress bd_addr, pairing_cb.sdp_over_classic = btif_dm_pairing_cb_t::ServiceDiscoveryState::FINISHED; } - prop.type = BT_PROPERTY_UUIDS; - prop.len = 0; + std::vector<uint8_t> bredr_property_value; + std::vector<uint8_t> le_property_value; + bt_property_t uuid_props[2] = {}; + bt_property_t& bredr_prop = uuid_props[0]; + bt_property_t& le_prop = uuid_props[1]; + if ((result == BTA_SUCCESS) && !uuids_param.empty()) { log::info("New UUIDs for {}:", bd_addr); for (const auto& uuid : uuids_param) { @@ -1610,13 +1612,35 @@ static void btif_on_service_discovery_results(RawAddress bd_addr, for (auto& uuid : uuids) { auto uuid_128bit = uuid.To128BitBE(); - property_value.insert(property_value.end(), uuid_128bit.begin(), uuid_128bit.end()); + bredr_property_value.insert(bredr_property_value.end(), uuid_128bit.begin(), + uuid_128bit.end()); if (uuid == UUID_A2DP_SINK) { a2dp_sink_capable = true; } } - prop.val = (void*)property_value.data(); - prop.len = Uuid::kNumBytes128 * uuids.size(); + + bredr_prop = {BT_PROPERTY_UUIDS, static_cast<int>(Uuid::kNumBytes128 * uuids.size()), + (void*)bredr_property_value.data()}; + + if (com::android::bluetooth::flags::separate_service_storage()) { + bt_status_t ret = btif_storage_set_remote_device_property(&bd_addr, &bredr_prop); + ASSERTC(ret == BT_STATUS_SUCCESS, "storing remote classic services failed", ret); + + std::set<Uuid> le_uuids; + if (results_for_bonding_device) { + btif_merge_existing_uuids(pairing_cb.static_bdaddr, &le_uuids, BT_PROPERTY_UUIDS_LE); + btif_merge_existing_uuids(pairing_cb.bd_addr, &le_uuids, BT_PROPERTY_UUIDS_LE); + } else { + btif_merge_existing_uuids(bd_addr, &le_uuids, BT_PROPERTY_UUIDS_LE); + } + + for (auto& uuid : le_uuids) { + auto uuid_128bit = uuid.To128BitBE(); + le_property_value.insert(le_property_value.end(), uuid_128bit.begin(), uuid_128bit.end()); + } + le_prop = {BT_PROPERTY_UUIDS_LE, static_cast<int>(Uuid::kNumBytes128 * le_uuids.size()), + (void*)le_property_value.data()}; + } } bool skip_reporting_wait_for_le = false; @@ -1649,17 +1673,18 @@ static void btif_on_service_discovery_results(RawAddress bd_addr, log::info("SDP failed, send {} EIR UUIDs to unblock bonding {}", num_eir_uuids, bd_addr); for (auto eir_uuid : uuids_iter->second) { auto uuid_128bit = eir_uuid.To128BitBE(); - property_value.insert(property_value.end(), uuid_128bit.begin(), uuid_128bit.end()); + bredr_property_value.insert(bredr_property_value.end(), uuid_128bit.begin(), + uuid_128bit.end()); } eir_uuids_cache.erase(uuids_iter); } if (num_eir_uuids > 0) { - prop.val = (void*)property_value.data(); - prop.len = num_eir_uuids * Uuid::kNumBytes128; + bredr_prop.val = (void*)bredr_property_value.data(); + bredr_prop.len = num_eir_uuids * Uuid::kNumBytes128; } else { log::warn("SDP failed and we have no EIR UUIDs to report either"); - prop.val = &uuid; - prop.len = Uuid::kNumBytes128; + bredr_prop.val = &uuid; + bredr_prop.len = Uuid::kNumBytes128; } } @@ -1677,9 +1702,10 @@ static void btif_on_service_discovery_results(RawAddress bd_addr, uuids_param.size(), num_eir_uuids)); if (!uuids_param.empty() || num_eir_uuids != 0) { - /* Also write this to the NVRAM */ - const bt_status_t ret = btif_storage_set_remote_device_property(&bd_addr, &prop); - ASSERTC(ret == BT_STATUS_SUCCESS, "storing remote services failed", ret); + if (!com::android::bluetooth::flags::separate_service_storage()) { + const bt_status_t ret = btif_storage_set_remote_device_property(&bd_addr, &bredr_prop); + ASSERTC(ret == BT_STATUS_SUCCESS, "storing remote services failed", ret); + } if (skip_reporting_wait_for_le) { log::info( @@ -1694,16 +1720,13 @@ static void btif_on_service_discovery_results(RawAddress bd_addr, } /* Send the event to the BTIF */ - GetInterfaceToProfiles()->events->invoke_remote_device_properties_cb(BT_STATUS_SUCCESS, bd_addr, - 1, &prop); + GetInterfaceToProfiles()->events->invoke_remote_device_properties_cb( + BT_STATUS_SUCCESS, bd_addr, ARRAY_SIZE(uuid_props), uuid_props); } } static void btif_on_gatt_results(RawAddress bd_addr, std::vector<bluetooth::Uuid>& services, bool is_transport_le) { - std::vector<bt_property_t> prop; - std::vector<uint8_t> property_value; - std::set<Uuid> uuids; RawAddress static_addr_copy = pairing_cb.static_bdaddr; bool lea_supported = is_le_audio_capable_during_service_discovery(bd_addr); @@ -1739,6 +1762,7 @@ static void btif_on_gatt_results(RawAddress bd_addr, std::vector<bluetooth::Uuid BTM_LogHistory(kBtmLogTag, bd_addr, "Discovered GATT services using SDP transport"); } + std::set<Uuid> uuids; for (Uuid uuid : services) { if (btif_is_interesting_le_service(uuid)) { if (btif_should_ignore_uuid(uuid)) { @@ -1767,46 +1791,28 @@ static void btif_on_gatt_results(RawAddress bd_addr, std::vector<bluetooth::Uuid log::info("Will return Classic SDP results, if done, to unblock bonding"); } - Uuid existing_uuids[BT_MAX_NUM_UUIDS] = {}; - - // Look up UUIDs using pseudo address (either RPA or static address) - bt_status_t existing_lookup_result = btif_get_existing_uuids(&bd_addr, existing_uuids); - - if (existing_lookup_result != BT_STATUS_FAIL) { - log::info("Got some existing UUIDs by address {}", bd_addr); - - for (int i = 0; i < BT_MAX_NUM_UUIDS; i++) { - Uuid uuid = existing_uuids[i]; - if (uuid.IsEmpty()) { - continue; - } - uuids.insert(uuid); + if (!com::android::bluetooth::flags::separate_service_storage()) { + // Look up UUIDs using pseudo address (either RPA or static address) + btif_merge_existing_uuids(bd_addr, &uuids); + if (bd_addr != static_addr_copy) { + // Look up UUID using static address, if different than sudo address + btif_merge_existing_uuids(static_addr_copy, &uuids); } } - if (bd_addr != static_addr_copy) { - // Look up UUID using static address, if different than sudo address - existing_lookup_result = btif_get_existing_uuids(&static_addr_copy, existing_uuids); - if (existing_lookup_result != BT_STATUS_FAIL) { - log::info("Got some existing UUIDs by static address {}", static_addr_copy); - for (int i = 0; i < BT_MAX_NUM_UUIDS; i++) { - Uuid uuid = existing_uuids[i]; - if (uuid.IsEmpty()) { - continue; - } - uuids.insert(uuid); - } - } - } + std::vector<bt_property_t> prop; + std::vector<uint8_t> property_value; for (auto& uuid : uuids) { auto uuid_128bit = uuid.To128BitBE(); property_value.insert(property_value.end(), uuid_128bit.begin(), uuid_128bit.end()); } - prop.push_back(bt_property_t{BT_PROPERTY_UUIDS, - static_cast<int>(Uuid::kNumBytes128 * uuids.size()), - (void*)property_value.data()}); + prop.push_back(bt_property_t{ + (com::android::bluetooth::flags::separate_service_storage() && is_transport_le) + ? BT_PROPERTY_UUIDS_LE + : BT_PROPERTY_UUIDS, + static_cast<int>(Uuid::kNumBytes128 * uuids.size()), (void*)property_value.data()}); /* Also write this to the NVRAM */ bt_status_t ret = btif_storage_set_remote_device_property(&bd_addr, &prop[0]); @@ -1817,8 +1823,7 @@ static void btif_on_gatt_results(RawAddress bd_addr, std::vector<bluetooth::Uuid * send them with rest of SDP results in on_service_discovery_results */ return; } else { - if (pairing_cb.sdp_over_classic == btif_dm_pairing_cb_t::ServiceDiscoveryState::SCHEDULED && - com::android::bluetooth::flags::bta_dm_discover_both()) { + if (pairing_cb.sdp_over_classic == btif_dm_pairing_cb_t::ServiceDiscoveryState::SCHEDULED) { /* Don't report services yet, they will be reported together once SDP * finishes. */ log::info("will report services later, with SDP results {}", bd_addr); @@ -1826,6 +1831,32 @@ static void btif_on_gatt_results(RawAddress bd_addr, std::vector<bluetooth::Uuid } } + if (!com::android::bluetooth::flags::separate_service_storage()) { + /* Send the event to the BTIF */ + GetInterfaceToProfiles()->events->invoke_remote_device_properties_cb(BT_STATUS_SUCCESS, bd_addr, + prop.size(), prop.data()); + return; + } + + std::set<Uuid> bredr_uuids; + // Look up UUIDs using pseudo address (either RPA or static address) + btif_merge_existing_uuids(bd_addr, &bredr_uuids); + if (bd_addr != static_addr_copy) { + // Look up UUID using static address, if different than sudo address + btif_merge_existing_uuids(static_addr_copy, &bredr_uuids); + } + + std::vector<uint8_t> bredr_property_value; + + for (auto& uuid : bredr_uuids) { + auto uuid_128bit = uuid.To128BitBE(); + bredr_property_value.insert(bredr_property_value.end(), uuid_128bit.begin(), uuid_128bit.end()); + } + + prop.push_back(bt_property_t{BT_PROPERTY_UUIDS, + static_cast<int>(Uuid::kNumBytes128 * bredr_uuids.size()), + (void*)bredr_property_value.data()}); + /* Send the event to the BTIF */ GetInterfaceToProfiles()->events->invoke_remote_device_properties_cb(BT_STATUS_SUCCESS, bd_addr, prop.size(), prop.data()); diff --git a/system/btif/src/btif_storage.cc b/system/btif/src/btif_storage.cc index e51756483c..4704362f2c 100644 --- a/system/btif/src/btif_storage.cc +++ b/system/btif/src/btif_storage.cc @@ -47,6 +47,7 @@ #include "btif/include/btif_api.h" #include "btif/include/btif_config.h" +#include "btif/include/btif_dm.h" #include "btif/include/btif_util.h" #include "btif/include/core_callbacks.h" #include "btif/include/stack_manager_t.h" @@ -179,15 +180,18 @@ static bool prop2cfg(const RawAddress* remote_bd_addr, bt_property_t* prop) { case BT_PROPERTY_TYPE_OF_DEVICE: btif_config_set_int(bdstr, BTIF_STORAGE_KEY_DEV_TYPE, *reinterpret_cast<int*>(prop->val)); break; - case BT_PROPERTY_UUIDS: { + case BT_PROPERTY_UUIDS: + case BT_PROPERTY_UUIDS_LE: { std::string val; size_t cnt = (prop->len) / sizeof(Uuid); for (size_t i = 0; i < cnt; i++) { val += (reinterpret_cast<Uuid*>(prop->val) + i)->ToString() + " "; } - btif_config_set_str(bdstr, BTIF_STORAGE_KEY_REMOTE_SERVICE, val); - break; - } + std::string key = (prop->type == BT_PROPERTY_UUIDS_LE) ? BTIF_STORAGE_KEY_REMOTE_SERVICE_LE + : BTIF_STORAGE_KEY_REMOTE_SERVICE; + btif_config_set_str(bdstr, key, val); + } break; + case BT_PROPERTY_REMOTE_VERSION_INFO: { bt_remote_version_t* info = reinterpret_cast<bt_remote_version_t*>(prop->val); @@ -300,10 +304,15 @@ static bool cfg2prop(const RawAddress* remote_bd_addr, bt_property_t* prop) { reinterpret_cast<int*>(prop->val)); } break; - case BT_PROPERTY_UUIDS: { + case BT_PROPERTY_UUIDS: + case BT_PROPERTY_UUIDS_LE: { char value[1280]; int size = sizeof(value); - if (btif_config_get_str(bdstr, BTIF_STORAGE_KEY_REMOTE_SERVICE, value, &size)) { + + std::string key = (prop->type == BT_PROPERTY_UUIDS_LE) ? BTIF_STORAGE_KEY_REMOTE_SERVICE_LE + : BTIF_STORAGE_KEY_REMOTE_SERVICE; + + if (btif_config_get_str(bdstr, key, value, &size)) { Uuid* p_uuid = reinterpret_cast<Uuid*>(prop->val); size_t num_uuids = btif_split_uuids_string(value, p_uuid, BT_MAX_NUM_UUIDS); prop->len = num_uuids * sizeof(Uuid); @@ -938,13 +947,14 @@ bt_status_t btif_storage_load_bonded_devices(void) { uint32_t i = 0; bt_property_t adapter_props[6]; uint32_t num_props = 0; - bt_property_t remote_properties[10]; + bt_property_t remote_properties[11]; RawAddress addr; bt_bdname_t name, alias, model_name; bt_scan_mode_t mode; uint32_t disc_timeout; Uuid local_uuids[BT_MAX_NUM_UUIDS]; Uuid remote_uuids[BT_MAX_NUM_UUIDS]; + Uuid remote_uuids_le[BT_MAX_NUM_UUIDS]; bt_status_t status; remove_devices_with_sample_ltk(); @@ -1026,10 +1036,16 @@ bt_status_t btif_storage_load_bonded_devices(void) { sizeof(devtype), &remote_properties[num_props]); num_props++; - btif_storage_get_remote_prop(p_remote_addr, BT_PROPERTY_UUIDS, remote_uuids, + btif_storage_get_remote_prop(p_remote_addr, BT_PROPERTY_UUIDS, &remote_uuids, sizeof(remote_uuids), &remote_properties[num_props]); num_props++; + if (com::android::bluetooth::flags::separate_service_storage()) { + btif_storage_get_remote_prop(p_remote_addr, BT_PROPERTY_UUIDS_LE, &remote_uuids_le, + sizeof(remote_uuids_le), &remote_properties[num_props]); + num_props++; + } + // Floss needs appearance for metrics purposes uint16_t appearance = 0; if (btif_storage_get_remote_prop(p_remote_addr, BT_PROPERTY_APPEARANCE, &appearance, @@ -1438,6 +1454,48 @@ void btif_storage_remove_gatt_cl_db_hash(const RawAddress& bd_addr) { bd_addr)); } +// TODO(b/369381361) Remove this function after all devices are migrated +void btif_storage_migrate_services() { + for (const auto& mac_address : btif_config_get_paired_devices()) { + auto addr_str = mac_address.ToString(); + + int device_type = BT_DEVICE_TYPE_UNKNOWN; + btif_config_get_int(addr_str, BTIF_STORAGE_KEY_DEV_TYPE, &device_type); + + if ((device_type == BT_DEVICE_TYPE_BREDR) || + btif_config_exist(addr_str, BTIF_STORAGE_KEY_REMOTE_SERVICE_LE)) { + /* Classic only, or already migrated entries don't need migration */ + continue; + } + + bt_property_t remote_uuids_prop; + Uuid remote_uuids[BT_MAX_NUM_UUIDS]; + BTIF_STORAGE_FILL_PROPERTY(&remote_uuids_prop, BT_PROPERTY_UUIDS, sizeof(remote_uuids), + remote_uuids); + btif_storage_get_remote_device_property(&mac_address, &remote_uuids_prop); + + log::info("Will migrate Services => ServicesLe for {}", mac_address.ToStringForLogging()); + + std::vector<uint8_t> property_value; + for (auto& uuid : remote_uuids) { + if (!btif_is_interesting_le_service(uuid)) { + continue; + } + + log::info("interesting LE service: {}", uuid); + auto uuid_128bit = uuid.To128BitBE(); + property_value.insert(property_value.end(), uuid_128bit.begin(), uuid_128bit.end()); + } + + bt_property_t le_uuids_prop{BT_PROPERTY_UUIDS_LE, static_cast<int>(property_value.size()), + (void*)property_value.data()}; + + /* Write LE services to storage */ + btif_storage_set_remote_device_property(&mac_address, &le_uuids_prop); + log::info("Migration finished for {}", mac_address.ToStringForLogging()); + } +} + void btif_debug_linkkey_type_dump(int fd) { dprintf(fd, "\nLink Key Types:\n"); for (const auto& bd_addr : btif_config_get_paired_devices()) { diff --git a/system/btif/src/btif_util.cc b/system/btif/src/btif_util.cc index 85c0069eaa..c6cf544d4a 100644 --- a/system/btif/src/btif_util.cc +++ b/system/btif/src/btif_util.cc @@ -129,6 +129,7 @@ std::string dump_property_type(bt_property_type_t type) { CASE_RETURN_STRING(BT_PROPERTY_ADAPTER_DISCOVERABLE_TIMEOUT); CASE_RETURN_STRING(BT_PROPERTY_ADAPTER_BONDED_DEVICES); CASE_RETURN_STRING(BT_PROPERTY_REMOTE_FRIENDLY_NAME); + CASE_RETURN_STRING(BT_PROPERTY_UUIDS_LE); default: RETURN_UNKNOWN_TYPE_STRING(bt_property_type_t, type); } diff --git a/system/btif/test/btif_core_test.cc b/system/btif/test/btif_core_test.cc index 66881a92d8..1bffc9ce3a 100644 --- a/system/btif/test/btif_core_test.cc +++ b/system/btif/test/btif_core_test.cc @@ -320,6 +320,7 @@ TEST_F(BtifUtilsTest, dump_property_type) { "BT_PROPERTY_ADAPTER_DISCOVERABLE_TIMEOUT"), std::make_pair(BT_PROPERTY_ADAPTER_BONDED_DEVICES, "BT_PROPERTY_ADAPTER_BONDED_DEVICES"), std::make_pair(BT_PROPERTY_REMOTE_FRIENDLY_NAME, "BT_PROPERTY_REMOTE_FRIENDLY_NAME"), + std::make_pair(BT_PROPERTY_UUIDS_LE, "BT_PROPERTY_UUIDS_LE"), }; for (const auto& type : types) { EXPECT_TRUE(dump_property_type(type.first).starts_with(type.second)); diff --git a/system/gd/hci/le_advertising_manager.h b/system/gd/hci/le_advertising_manager.h index 55245a5cfa..fe614861e2 100644 --- a/system/gd/hci/le_advertising_manager.h +++ b/system/gd/hci/le_advertising_manager.h @@ -47,8 +47,8 @@ class AdvertisingConfig { public: std::vector<GapData> advertisement; std::vector<GapData> scan_response; - uint16_t interval_min; - uint16_t interval_max; + uint32_t interval_min; + uint32_t interval_max; AdvertisingType advertising_type; AdvertiserAddressType requested_advertiser_address_type; PeerAddressType peer_address_type; diff --git a/system/gd/storage/config_keys.h b/system/gd/storage/config_keys.h index 4629a494ba..d3659b6a96 100644 --- a/system/gd/storage/config_keys.h +++ b/system/gd/storage/config_keys.h @@ -111,6 +111,7 @@ #define BTIF_STORAGE_KEY_PIN_LENGTH "PinLength" #define BTIF_STORAGE_KEY_PRODUCT_ID "ProductId" #define BTIF_STORAGE_KEY_REMOTE_SERVICE "Service" +#define BTIF_STORAGE_KEY_REMOTE_SERVICE_LE "ServiceLe" #define BTIF_STORAGE_KEY_REMOTE_VER_MFCT "Manufacturer" #define BTIF_STORAGE_KEY_REMOTE_VER_SUBVER "LmpSubVer" #define BTIF_STORAGE_KEY_REMOTE_VER_VER "LmpVer" diff --git a/system/include/hardware/bluetooth.h b/system/include/hardware/bluetooth.h index a7a3bb0aa2..fa15353d9f 100644 --- a/system/include/hardware/bluetooth.h +++ b/system/include/hardware/bluetooth.h @@ -299,7 +299,7 @@ typedef enum { */ BT_PROPERTY_TYPE_OF_DEVICE, /** - * Description - Bluetooth Service Record + * Description - Bluetooth Service Record, UUIDs on BREDR transport * Access mode - Only GET. * Data type - bt_service_record_t */ @@ -427,6 +427,14 @@ typedef enum { */ BT_PROPERTY_LPP_OFFLOAD_FEATURES, + /** + * Description - Bluetooth Service 128-bit UUIDs on LE transport + * Access mode - Only GET. + * Data type - Array of bluetooth::Uuid (Array size inferred from property + * length). + */ + BT_PROPERTY_UUIDS_LE, + BT_PROPERTY_REMOTE_DEVICE_TIMESTAMP = 0xFF, } bt_property_type_t; diff --git a/system/test/headless/property.cc b/system/test/headless/property.cc index fe6e21f1ed..2b5df50ff2 100644 --- a/system/test/headless/property.cc +++ b/system/test/headless/property.cc @@ -99,6 +99,10 @@ std::map<::bt_property_type_t, return new headless::property::void_t(data, len, BT_PROPERTY_REMOTE_DEVICE_TIMESTAMP); }}, + {BT_PROPERTY_UUIDS_LE, + [](const uint8_t* data, const size_t len) -> headless::bt_property_t* { + return new headless::property::uuid_t(data, len); + }}, }; } // namespace diff --git a/system/test/headless/property.h b/system/test/headless/property.h index 6af17a244f..1113495f24 100644 --- a/system/test/headless/property.h +++ b/system/test/headless/property.h @@ -52,6 +52,7 @@ inline std::string bt_property_type_text(const ::bt_property_type_t type) { CASE_RETURN_TEXT(BT_PROPERTY_REMOTE_MODEL_NUM); CASE_RETURN_TEXT(BT_PROPERTY_REMOTE_DEVICE_TIMESTAMP); CASE_RETURN_TEXT(BT_PROPERTY_REMOTE_ADDR_TYPE); + CASE_RETURN_TEXT(BT_PROPERTY_UUIDS_LE); CASE_RETURN_TEXT(BT_PROPERTY_RESERVED_0x14); default: RETURN_UNKNOWN_TYPE_STRING(::bt_property_type_t, type); |