diff options
Diffstat (limited to 'framework/java')
56 files changed, 3397 insertions, 1242 deletions
diff --git a/framework/java/android/bluetooth/BluetoothA2dp.java b/framework/java/android/bluetooth/BluetoothA2dp.java index 16413e1a1d..65cdca9eb7 100644 --- a/framework/java/android/bluetooth/BluetoothA2dp.java +++ b/framework/java/android/bluetooth/BluetoothA2dp.java @@ -20,11 +20,17 @@ import android.Manifest; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresNoPermission; import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SystemApi; +import android.bluetooth.annotations.RequiresBluetoothConnectPermission; +import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission; +import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; import android.compat.annotation.UnsupportedAppUsage; +import android.content.Attributable; +import android.content.AttributionSource; import android.content.Context; import android.os.Binder; import android.os.Build; @@ -69,10 +75,10 @@ public final class BluetoothA2dp implements BluetoothProfile { * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}. - * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to - * receive. */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED"; @@ -90,15 +96,17 @@ public final class BluetoothA2dp implements BluetoothProfile { * * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of * {@link #STATE_PLAYING}, {@link #STATE_NOT_PLAYING}, - * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to - * receive. */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_PLAYING_STATE_CHANGED = "android.bluetooth.a2dp.profile.action.PLAYING_STATE_CHANGED"; /** @hide */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_AVRCP_CONNECTION_STATE_CHANGED = "android.bluetooth.a2dp.profile.action.AVRCP_CONNECTION_STATE_CHANGED"; @@ -112,11 +120,11 @@ public final class BluetoothA2dp implements BluetoothProfile { * be null if no device is active. </li> * </ul> * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to - * receive. - * * @hide */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) @UnsupportedAppUsage(trackingBug = 171933273) public static final String ACTION_ACTIVE_DEVICE_CHANGED = @@ -133,11 +141,11 @@ public final class BluetoothA2dp implements BluetoothProfile { * connected, otherwise it is not included.</li> * </ul> * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to - * receive. - * * @hide */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) @UnsupportedAppUsage(trackingBug = 181103983) public static final String ACTION_CODEC_CONFIG_CHANGED = @@ -258,7 +266,8 @@ public final class BluetoothA2dp implements BluetoothProfile { @SystemApi public static final int DYNAMIC_BUFFER_SUPPORT_A2DP_SOFTWARE_ENCODING = 2; - private BluetoothAdapter mAdapter; + private final BluetoothAdapter mAdapter; + private final AttributionSource mAttributionSource; private final BluetoothProfileConnector<IBluetoothA2dp> mProfileConnector = new BluetoothProfileConnector(this, BluetoothProfile.A2DP, "BluetoothA2dp", IBluetoothA2dp.class.getName()) { @@ -272,8 +281,10 @@ public final class BluetoothA2dp implements BluetoothProfile { * Create a BluetoothA2dp proxy object for interacting with the local * Bluetooth A2DP service. */ - /*package*/ BluetoothA2dp(Context context, ServiceListener listener) { - mAdapter = BluetoothAdapter.getDefaultAdapter(); + /* package */ BluetoothA2dp(Context context, ServiceListener listener, + BluetoothAdapter adapter) { + mAdapter = adapter; + mAttributionSource = adapter.getAttributionSource(); mProfileConnector.connect(context, listener); } @@ -307,7 +318,9 @@ public final class BluetoothA2dp implements BluetoothProfile { * @return false on immediate error, true otherwise * @hide */ - @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) @UnsupportedAppUsage public boolean connect(BluetoothDevice device) { if (DBG) log("connect(" + device + ")"); @@ -347,7 +360,9 @@ public final class BluetoothA2dp implements BluetoothProfile { * @return false on immediate error, true otherwise * @hide */ - @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) @UnsupportedAppUsage public boolean disconnect(BluetoothDevice device) { if (DBG) log("disconnect(" + device + ")"); @@ -368,12 +383,16 @@ public final class BluetoothA2dp implements BluetoothProfile { * {@inheritDoc} */ @Override + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public List<BluetoothDevice> getConnectedDevices() { if (VDBG) log("getConnectedDevices()"); try { final IBluetoothA2dp service = getService(); if (service != null && isEnabled()) { - return service.getConnectedDevices(); + return Attributable.setAttributionSource( + service.getConnectedDevicesWithAttribution(mAttributionSource), + mAttributionSource); } if (service == null) Log.w(TAG, "Proxy not attached to service"); return new ArrayList<BluetoothDevice>(); @@ -387,12 +406,17 @@ public final class BluetoothA2dp implements BluetoothProfile { * {@inheritDoc} */ @Override + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { if (VDBG) log("getDevicesMatchingStates()"); try { final IBluetoothA2dp service = getService(); if (service != null && isEnabled()) { - return service.getDevicesMatchingConnectionStates(states); + return Attributable.setAttributionSource( + service.getDevicesMatchingConnectionStatesWithAttribution(states, + mAttributionSource), + mAttributionSource); } if (service == null) Log.w(TAG, "Proxy not attached to service"); return new ArrayList<BluetoothDevice>(); @@ -406,6 +430,8 @@ public final class BluetoothA2dp implements BluetoothProfile { * {@inheritDoc} */ @Override + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public @BtProfileState int getConnectionState(BluetoothDevice device) { if (VDBG) log("getState(" + device + ")"); try { @@ -441,7 +467,9 @@ public final class BluetoothA2dp implements BluetoothProfile { * @return false on immediate error, true otherwise * @hide */ - @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) @UnsupportedAppUsage(trackingBug = 171933273) public boolean setActiveDevice(@Nullable BluetoothDevice device) { if (DBG) log("setActiveDevice(" + device + ")"); @@ -449,7 +477,7 @@ public final class BluetoothA2dp implements BluetoothProfile { final IBluetoothA2dp service = getService(); if (service != null && isEnabled() && ((device == null) || isValidDevice(device))) { - return service.setActiveDevice(device); + return service.setActiveDevice(device, mAttributionSource); } if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; @@ -468,13 +496,16 @@ public final class BluetoothA2dp implements BluetoothProfile { */ @UnsupportedAppUsage(trackingBug = 171933273) @Nullable - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public BluetoothDevice getActiveDevice() { if (VDBG) log("getActiveDevice()"); try { final IBluetoothA2dp service = getService(); if (service != null && isEnabled()) { - return service.getActiveDevice(); + return Attributable.setAttributionSource( + service.getActiveDevice(mAttributionSource), mAttributionSource); } if (service == null) Log.w(TAG, "Proxy not attached to service"); return null; @@ -495,7 +526,11 @@ public final class BluetoothA2dp implements BluetoothProfile { * @return true if priority is set, false on error * @hide */ - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public boolean setPriority(BluetoothDevice device, int priority) { if (DBG) log("setPriority(" + device + ", " + priority + ")"); return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority)); @@ -514,7 +549,11 @@ public final class BluetoothA2dp implements BluetoothProfile { * @hide */ @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public boolean setConnectionPolicy(@NonNull BluetoothDevice device, @ConnectionPolicy int connectionPolicy) { if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); @@ -526,7 +565,7 @@ public final class BluetoothA2dp implements BluetoothProfile { && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) { return false; } - return service.setConnectionPolicy(device, connectionPolicy); + return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource); } if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; @@ -546,7 +585,9 @@ public final class BluetoothA2dp implements BluetoothProfile { * @return priority of the device * @hide */ - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public int getPriority(BluetoothDevice device) { if (VDBG) log("getPriority(" + device + ")"); @@ -554,7 +595,8 @@ public final class BluetoothA2dp implements BluetoothProfile { final IBluetoothA2dp service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { - return BluetoothAdapter.connectionPolicyToPriority(service.getPriority(device)); + return BluetoothAdapter.connectionPolicyToPriority( + service.getPriority(device, mAttributionSource)); } if (service == null) Log.w(TAG, "Proxy not attached to service"); return BluetoothProfile.PRIORITY_OFF; @@ -576,14 +618,18 @@ public final class BluetoothA2dp implements BluetoothProfile { * @hide */ @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) { if (VDBG) log("getConnectionPolicy(" + device + ")"); try { final IBluetoothA2dp service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { - return service.getConnectionPolicy(device); + return service.getConnectionPolicy(device, mAttributionSource); } if (service == null) Log.w(TAG, "Proxy not attached to service"); return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; @@ -599,6 +645,7 @@ public final class BluetoothA2dp implements BluetoothProfile { * @return true if device supports absolute volume * @hide */ + @RequiresNoPermission public boolean isAvrcpAbsoluteVolumeSupported() { if (DBG) Log.d(TAG, "isAvrcpAbsoluteVolumeSupported"); try { @@ -620,12 +667,14 @@ public final class BluetoothA2dp implements BluetoothProfile { * @param volume Absolute volume to be set on AVRCP side * @hide */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void setAvrcpAbsoluteVolume(int volume) { if (DBG) Log.d(TAG, "setAvrcpAbsoluteVolume"); try { final IBluetoothA2dp service = getService(); if (service != null && isEnabled()) { - service.setAvrcpAbsoluteVolume(volume); + service.setAvrcpAbsoluteVolume(volume, mAttributionSource); } if (service == null) Log.w(TAG, "Proxy not attached to service"); } catch (RemoteException e) { @@ -636,16 +685,17 @@ public final class BluetoothA2dp implements BluetoothProfile { /** * Check if A2DP profile is streaming music. * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. - * * @param device BluetoothDevice device */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean isA2dpPlaying(BluetoothDevice device) { try { final IBluetoothA2dp service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { - return service.isA2dpPlaying(device); + return service.isA2dpPlaying(device, mAttributionSource); } if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; @@ -662,6 +712,8 @@ public final class BluetoothA2dp implements BluetoothProfile { * * @hide */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean shouldSendVolumeKeys(BluetoothDevice device) { if (isEnabled() && isValidDevice(device)) { ParcelUuid[] uuids = device.getUuids(); @@ -686,14 +738,16 @@ public final class BluetoothA2dp implements BluetoothProfile { */ @UnsupportedAppUsage(trackingBug = 181103983) @Nullable - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public BluetoothCodecStatus getCodecStatus(@NonNull BluetoothDevice device) { if (DBG) Log.d(TAG, "getCodecStatus(" + device + ")"); verifyDeviceNotNull(device, "getCodecStatus"); try { final IBluetoothA2dp service = getService(); if (service != null && isEnabled()) { - return service.getCodecStatus(device); + return service.getCodecStatus(device, mAttributionSource); } if (service == null) { Log.w(TAG, "Proxy not attached to service"); @@ -714,7 +768,9 @@ public final class BluetoothA2dp implements BluetoothProfile { * @hide */ @UnsupportedAppUsage(trackingBug = 181103983) - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void setCodecConfigPreference(@NonNull BluetoothDevice device, @NonNull BluetoothCodecConfig codecConfig) { if (DBG) Log.d(TAG, "setCodecConfigPreference(" + device + ")"); @@ -726,7 +782,7 @@ public final class BluetoothA2dp implements BluetoothProfile { try { final IBluetoothA2dp service = getService(); if (service != null && isEnabled()) { - service.setCodecConfigPreference(device, codecConfig); + service.setCodecConfigPreference(device, codecConfig, mAttributionSource); } if (service == null) Log.w(TAG, "Proxy not attached to service"); return; @@ -744,7 +800,9 @@ public final class BluetoothA2dp implements BluetoothProfile { * @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void enableOptionalCodecs(@NonNull BluetoothDevice device) { if (DBG) Log.d(TAG, "enableOptionalCodecs(" + device + ")"); verifyDeviceNotNull(device, "enableOptionalCodecs"); @@ -759,7 +817,9 @@ public final class BluetoothA2dp implements BluetoothProfile { * @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void disableOptionalCodecs(@NonNull BluetoothDevice device) { if (DBG) Log.d(TAG, "disableOptionalCodecs(" + device + ")"); verifyDeviceNotNull(device, "disableOptionalCodecs"); @@ -773,14 +833,15 @@ public final class BluetoothA2dp implements BluetoothProfile { * active A2DP Bluetooth device. * @param enable if true, enable the optional codecs, other disable them */ + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) private void enableDisableOptionalCodecs(BluetoothDevice device, boolean enable) { try { final IBluetoothA2dp service = getService(); if (service != null && isEnabled()) { if (enable) { - service.enableOptionalCodecs(device); + service.enableOptionalCodecs(device, mAttributionSource); } else { - service.disableOptionalCodecs(device); + service.disableOptionalCodecs(device, mAttributionSource); } } if (service == null) Log.w(TAG, "Proxy not attached to service"); @@ -800,14 +861,16 @@ public final class BluetoothA2dp implements BluetoothProfile { * @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) @OptionalCodecsSupportStatus public int isOptionalCodecsSupported(@NonNull BluetoothDevice device) { verifyDeviceNotNull(device, "isOptionalCodecsSupported"); try { final IBluetoothA2dp service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { - return service.supportsOptionalCodecs(device); + return service.supportsOptionalCodecs(device, mAttributionSource); } if (service == null) Log.w(TAG, "Proxy not attached to service"); return OPTIONAL_CODECS_SUPPORT_UNKNOWN; @@ -826,14 +889,16 @@ public final class BluetoothA2dp implements BluetoothProfile { * @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) @OptionalCodecsPreferenceStatus public int isOptionalCodecsEnabled(@NonNull BluetoothDevice device) { verifyDeviceNotNull(device, "isOptionalCodecsEnabled"); try { final IBluetoothA2dp service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { - return service.getOptionalCodecsEnabled(device); + return service.getOptionalCodecsEnabled(device, mAttributionSource); } if (service == null) Log.w(TAG, "Proxy not attached to service"); return OPTIONAL_CODECS_PREF_UNKNOWN; @@ -853,7 +918,9 @@ public final class BluetoothA2dp implements BluetoothProfile { * @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void setOptionalCodecsEnabled(@NonNull BluetoothDevice device, @OptionalCodecsPreferenceStatus int value) { verifyDeviceNotNull(device, "setOptionalCodecsEnabled"); @@ -867,7 +934,7 @@ public final class BluetoothA2dp implements BluetoothProfile { final IBluetoothA2dp service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { - service.setOptionalCodecsEnabled(device, value); + service.setOptionalCodecsEnabled(device, value, mAttributionSource); } if (service == null) Log.w(TAG, "Proxy not attached to service"); return; @@ -889,13 +956,17 @@ public final class BluetoothA2dp implements BluetoothProfile { * @hide */ @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public @Type int getDynamicBufferSupport() { if (VDBG) log("getDynamicBufferSupport()"); try { final IBluetoothA2dp service = getService(); if (service != null && isEnabled()) { - return service.getDynamicBufferSupport(); + return service.getDynamicBufferSupport(mAttributionSource); } if (service == null) Log.w(TAG, "Proxy not attached to service"); return DYNAMIC_BUFFER_SUPPORT_NONE; @@ -916,13 +987,17 @@ public final class BluetoothA2dp implements BluetoothProfile { * @hide */ @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public @Nullable BufferConstraints getBufferConstraints() { if (VDBG) log("getBufferConstraints()"); try { final IBluetoothA2dp service = getService(); if (service != null && isEnabled()) { - return service.getBufferConstraints(); + return service.getBufferConstraints(mAttributionSource); } if (service == null) Log.w(TAG, "Proxy not attached to service"); return null; @@ -942,14 +1017,18 @@ public final class BluetoothA2dp implements BluetoothProfile { * @hide */ @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public boolean setBufferLengthMillis(@BluetoothCodecConfig.SourceCodecType int codec, int value) { if (VDBG) log("setBufferLengthMillis(" + codec + ", " + value + ")"); try { final IBluetoothA2dp service = getService(); if (service != null && isEnabled()) { - return service.setBufferLengthMillis(codec, value); + return service.setBufferLengthMillis(codec, value, mAttributionSource); } if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; diff --git a/framework/java/android/bluetooth/BluetoothA2dpSink.java b/framework/java/android/bluetooth/BluetoothA2dpSink.java index 67f3d7b5d7..2dd63a0263 100755 --- a/framework/java/android/bluetooth/BluetoothA2dpSink.java +++ b/framework/java/android/bluetooth/BluetoothA2dpSink.java @@ -19,9 +19,16 @@ package android.bluetooth; import android.Manifest; import android.annotation.NonNull; import android.annotation.RequiresPermission; +import android.annotation.SdkConstant; import android.annotation.SuppressLint; import android.annotation.SystemApi; +import android.annotation.SdkConstant.SdkConstantType; +import android.bluetooth.annotations.RequiresBluetoothConnectPermission; +import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission; +import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; import android.compat.annotation.UnsupportedAppUsage; +import android.content.Attributable; +import android.content.AttributionSource; import android.content.Context; import android.os.Binder; import android.os.Build; @@ -67,11 +74,14 @@ public final class BluetoothA2dpSink implements BluetoothProfile { */ @SystemApi @SuppressLint("ActionValue") - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.a2dp-sink.profile.action.CONNECTION_STATE_CHANGED"; - private BluetoothAdapter mAdapter; + private final BluetoothAdapter mAdapter; + private final AttributionSource mAttributionSource; private final BluetoothProfileConnector<IBluetoothA2dpSink> mProfileConnector = new BluetoothProfileConnector(this, BluetoothProfile.A2DP_SINK, "BluetoothA2dpSink", IBluetoothA2dpSink.class.getName()) { @@ -85,8 +95,10 @@ public final class BluetoothA2dpSink implements BluetoothProfile { * Create a BluetoothA2dp proxy object for interacting with the local * Bluetooth A2DP service. */ - /*package*/ BluetoothA2dpSink(Context context, ServiceListener listener) { - mAdapter = BluetoothAdapter.getDefaultAdapter(); + /* package */ BluetoothA2dpSink(Context context, ServiceListener listener, + BluetoothAdapter adapter) { + mAdapter = adapter; + mAttributionSource = adapter.getAttributionSource(); mProfileConnector.connect(context, listener); } @@ -121,13 +133,17 @@ public final class BluetoothA2dpSink implements BluetoothProfile { * @return false on immediate error, true otherwise * @hide */ - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public boolean connect(BluetoothDevice device) { if (DBG) log("connect(" + device + ")"); final IBluetoothA2dpSink service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { try { - return service.connect(device); + return service.connect(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return false; @@ -160,13 +176,15 @@ public final class BluetoothA2dpSink implements BluetoothProfile { * @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean disconnect(BluetoothDevice device) { if (DBG) log("disconnect(" + device + ")"); final IBluetoothA2dpSink service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { try { - return service.disconnect(device); + return service.disconnect(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return false; @@ -182,12 +200,15 @@ public final class BluetoothA2dpSink implements BluetoothProfile { * @hide */ @Override + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public List<BluetoothDevice> getConnectedDevices() { if (VDBG) log("getConnectedDevices()"); final IBluetoothA2dpSink service = getService(); if (service != null && isEnabled()) { try { - return service.getConnectedDevices(); + return Attributable.setAttributionSource( + service.getConnectedDevices(mAttributionSource), mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return new ArrayList<BluetoothDevice>(); @@ -203,12 +224,16 @@ public final class BluetoothA2dpSink implements BluetoothProfile { * @hide */ @Override + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { if (VDBG) log("getDevicesMatchingStates()"); final IBluetoothA2dpSink service = getService(); if (service != null && isEnabled()) { try { - return service.getDevicesMatchingConnectionStates(states); + return Attributable.setAttributionSource( + service.getDevicesMatchingConnectionStates(states, mAttributionSource), + mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return new ArrayList<BluetoothDevice>(); @@ -224,12 +249,14 @@ public final class BluetoothA2dpSink implements BluetoothProfile { * @hide */ @Override + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getConnectionState(BluetoothDevice device) { if (VDBG) log("getState(" + device + ")"); final IBluetoothA2dpSink service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { try { - return service.getConnectionState(device); + return service.getConnectionState(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return BluetoothProfile.STATE_DISCONNECTED; @@ -243,8 +270,6 @@ public final class BluetoothA2dpSink implements BluetoothProfile { * Get the current audio configuration for the A2DP source device, * or null if the device has no audio configuration * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. - * * @param device Remote bluetooth device. * @return audio configuration for the device, or null * @@ -252,12 +277,15 @@ public final class BluetoothA2dpSink implements BluetoothProfile { * * @hide */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public BluetoothAudioConfig getAudioConfig(BluetoothDevice device) { if (VDBG) log("getAudioConfig(" + device + ")"); final IBluetoothA2dpSink service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { try { - return service.getAudioConfig(device); + return service.getAudioConfig(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return null; @@ -278,7 +306,11 @@ public final class BluetoothA2dpSink implements BluetoothProfile { * @return true if priority is set, false on error * @hide */ - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public boolean setPriority(BluetoothDevice device, int priority) { if (DBG) log("setPriority(" + device + ", " + priority + ")"); return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority)); @@ -297,7 +329,11 @@ public final class BluetoothA2dpSink implements BluetoothProfile { * @hide */ @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED + }) public boolean setConnectionPolicy(@NonNull BluetoothDevice device, @ConnectionPolicy int connectionPolicy) { if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); @@ -308,7 +344,7 @@ public final class BluetoothA2dpSink implements BluetoothProfile { return false; } try { - return service.setConnectionPolicy(device, connectionPolicy); + return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return false; @@ -328,7 +364,11 @@ public final class BluetoothA2dpSink implements BluetoothProfile { * @return priority of the device * @hide */ - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public int getPriority(BluetoothDevice device) { if (VDBG) log("getPriority(" + device + ")"); return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device)); @@ -346,13 +386,17 @@ public final class BluetoothA2dpSink implements BluetoothProfile { * @hide */ @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) { if (VDBG) log("getConnectionPolicy(" + device + ")"); final IBluetoothA2dpSink service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { try { - return service.getConnectionPolicy(device); + return service.getConnectionPolicy(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; @@ -371,12 +415,16 @@ public final class BluetoothA2dpSink implements BluetoothProfile { * @hide */ @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public boolean isAudioPlaying(@NonNull BluetoothDevice device) { final IBluetoothA2dpSink service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { try { - return service.isA2dpPlaying(device); + return service.isA2dpPlaying(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return false; diff --git a/framework/java/android/bluetooth/BluetoothAdapter.java b/framework/java/android/bluetooth/BluetoothAdapter.java index e305aa8e33..a9ddefbaea 100644 --- a/framework/java/android/bluetooth/BluetoothAdapter.java +++ b/framework/java/android/bluetooth/BluetoothAdapter.java @@ -19,19 +19,26 @@ package android.bluetooth; import static java.util.Objects.requireNonNull; -import android.Manifest; import android.annotation.CallbackExecutor; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresNoPermission; import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; +import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.app.ActivityThread; import android.app.PropertyInvalidatedCache; import android.bluetooth.BluetoothDevice.Transport; import android.bluetooth.BluetoothProfile.ConnectionPolicy; +import android.bluetooth.annotations.RequiresBluetoothAdvertisePermission; +import android.bluetooth.annotations.RequiresBluetoothConnectPermission; +import android.bluetooth.annotations.RequiresBluetoothLocationPermission; +import android.bluetooth.annotations.RequiresBluetoothScanPermission; +import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission; +import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; import android.bluetooth.le.BluetoothLeAdvertiser; import android.bluetooth.le.BluetoothLeScanner; import android.bluetooth.le.PeriodicAdvertisingManager; @@ -41,6 +48,8 @@ import android.bluetooth.le.ScanRecord; import android.bluetooth.le.ScanResult; import android.bluetooth.le.ScanSettings; import android.compat.annotation.UnsupportedAppUsage; +import android.content.Attributable; +import android.content.AttributionSource; import android.content.Context; import android.os.BatteryStats; import android.os.Binder; @@ -66,6 +75,7 @@ import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.UUID; import java.util.concurrent.Executor; @@ -96,11 +106,6 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; * Bluetooth LE devices with {@link #startLeScan(LeScanCallback callback)}. * </p> * <p>This class is thread safe.</p> - * <p class="note"><strong>Note:</strong> - * Most methods require the {@link android.Manifest.permission#BLUETOOTH} - * permission and some also require the - * {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission. - * </p> * <div class="special reference"> * <h3>Developer Guides</h3> * <p> @@ -142,8 +147,8 @@ public final class BluetoothAdapter { * <p>Always contains the extra fields {@link #EXTRA_STATE} and {@link * #EXTRA_PREVIOUS_STATE} containing the new and old states * respectively. - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive. */ + @RequiresLegacyBluetoothPermission @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_STATE_CHANGED = "android.bluetooth.adapter.action.STATE_CHANGED"; @@ -276,8 +281,10 @@ public final class BluetoothAdapter { * <p>Applications can also listen for {@link #ACTION_SCAN_MODE_CHANGED} * for global notification whenever the scan mode changes. For example, an * application can be notified when the device has ended discoverability. - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothAdvertisePermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_REQUEST_DISCOVERABLE = "android.bluetooth.adapter.action.REQUEST_DISCOVERABLE"; @@ -303,8 +310,10 @@ public final class BluetoothAdapter { * has rejected the request or an error has occurred. * <p>Applications can also listen for {@link #ACTION_STATE_CHANGED} * for global notification whenever Bluetooth is turned on or off. - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_REQUEST_ENABLE = "android.bluetooth.adapter.action.REQUEST_ENABLE"; @@ -323,10 +332,12 @@ public final class BluetoothAdapter { * has rejected the request or an error has occurred. * <p>Applications can also listen for {@link #ACTION_STATE_CHANGED} * for global notification whenever Bluetooth is turned on or off. - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} * * @hide */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_REQUEST_DISABLE = "android.bluetooth.adapter.action.REQUEST_DISABLE"; @@ -353,8 +364,10 @@ public final class BluetoothAdapter { * <p>Always contains the extra fields {@link #EXTRA_SCAN_MODE} and {@link * #EXTRA_PREVIOUS_SCAN_MODE} containing the new and old scan modes * respectively. - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothScanPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_SCAN_MODE_CHANGED = "android.bluetooth.adapter.action.SCAN_MODE_CHANGED"; @@ -506,15 +519,19 @@ public final class BluetoothAdapter { * progress, and existing connections will experience limited bandwidth * and high latency. Use {@link #cancelDiscovery()} to cancel an ongoing * discovery. - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive. */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothScanPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_DISCOVERY_STARTED = "android.bluetooth.adapter.action.DISCOVERY_STARTED"; /** * Broadcast Action: The local Bluetooth adapter has finished the device * discovery process. - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive. */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothScanPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_DISCOVERY_FINISHED = "android.bluetooth.adapter.action.DISCOVERY_FINISHED"; @@ -524,8 +541,10 @@ public final class BluetoothAdapter { * <p>This name is visible to remote Bluetooth devices. * <p>Always contains the extra field {@link #EXTRA_LOCAL_NAME} containing * the name. - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive. */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_LOCAL_NAME_CHANGED = "android.bluetooth.adapter.action.LOCAL_NAME_CHANGED"; /** @@ -557,9 +576,10 @@ public final class BluetoothAdapter { * {@link #EXTRA_CONNECTION_STATE} or {@link #EXTRA_PREVIOUS_CONNECTION_STATE} * can be any of {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}. - * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive. */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.adapter.action.CONNECTION_STATE_CHANGED"; @@ -585,6 +605,7 @@ public final class BluetoothAdapter { * * @hide */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) @SystemApi public static final String ACTION_BLE_STATE_CHANGED = "android.bluetooth.adapter.action.BLE_STATE_CHANGED"; @@ -599,6 +620,9 @@ public final class BluetoothAdapter { * * @hide */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_BLUETOOTH_ADDRESS_CHANGED = "android.bluetooth.adapter.action.BLUETOOTH_ADDRESS_CHANGED"; @@ -623,6 +647,9 @@ public final class BluetoothAdapter { * * @hide */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_BLE_ACL_CONNECTED = "android.bluetooth.adapter.action.BLE_ACL_CONNECTED"; @@ -637,6 +664,9 @@ public final class BluetoothAdapter { * * @hide */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_BLE_ACL_DISCONNECTED = "android.bluetooth.adapter.action.BLE_ACL_DISCONNECTED"; @@ -677,20 +707,21 @@ public final class BluetoothAdapter { */ private static BluetoothAdapter sAdapter; - private static BluetoothLeScanner sBluetoothLeScanner; - private static BluetoothLeAdvertiser sBluetoothLeAdvertiser; - private static PeriodicAdvertisingManager sPeriodicAdvertisingManager; + private BluetoothLeScanner mBluetoothLeScanner; + private BluetoothLeAdvertiser mBluetoothLeAdvertiser; + private PeriodicAdvertisingManager mPeriodicAdvertisingManager; private final IBluetoothManager mManagerService; + private final AttributionSource mAttributionSource; + @UnsupportedAppUsage private IBluetooth mService; - private Context mContext; private final ReentrantReadWriteLock mServiceLock = new ReentrantReadWriteLock(); private final Object mLock = new Object(); private final Map<LeScanCallback, ScanCallback> mLeScanClients; - private static final Map<BluetoothDevice, List<Pair<OnMetadataChangedListener, Executor>>> - sMetadataListeners = new HashMap<>(); + private final Map<BluetoothDevice, List<Pair<OnMetadataChangedListener, Executor>>> + mMetadataListeners = new HashMap<>(); private final Map<BluetoothConnectionCallback, Executor> mBluetoothConnectionCallbackExecutorMap = new HashMap<>(); @@ -698,14 +729,16 @@ public final class BluetoothAdapter { * Bluetooth metadata listener. Overrides the default BluetoothMetadataListener * implementation. */ - private static final IBluetoothMetadataListener sBluetoothMetadataListener = + @SuppressLint("AndroidFrameworkBluetoothPermission") + private final IBluetoothMetadataListener mBluetoothMetadataListener = new IBluetoothMetadataListener.Stub() { @Override public void onMetadataChanged(BluetoothDevice device, int key, byte[] value) { - synchronized (sMetadataListeners) { - if (sMetadataListeners.containsKey(device)) { + Attributable.setAttributionSource(device, mAttributionSource); + synchronized (mMetadataListeners) { + if (mMetadataListeners.containsKey(device)) { List<Pair<OnMetadataChangedListener, Executor>> list = - sMetadataListeners.get(device); + mMetadataListeners.get(device); for (Pair<OnMetadataChangedListener, Executor> pair : list) { OnMetadataChangedListener listener = pair.first; Executor executor = pair.second; @@ -721,32 +754,43 @@ public final class BluetoothAdapter { /** * Get a handle to the default local Bluetooth adapter. - * <p>Currently Android only supports one Bluetooth adapter, but the API - * could be extended to support more. This will always return the default - * adapter. + * <p> + * Currently Android only supports one Bluetooth adapter, but the API could + * be extended to support more. This will always return the default adapter. * </p> * - * @return the default local adapter, or null if Bluetooth is not supported on this hardware - * platform + * @return the default local adapter, or null if Bluetooth is not supported + * on this hardware platform + * @deprecated this method will continue to work, but developers are + * strongly encouraged to migrate to using + * {@link BluetoothManager#getAdapter()}, since that approach + * enables support for {@link Context#createAttributionContext}. */ + @Deprecated + @RequiresNoPermission public static synchronized BluetoothAdapter getDefaultAdapter() { if (sAdapter == null) { - IBinder b = ServiceManager.getService(BLUETOOTH_MANAGER_SERVICE); - if (b != null) { - IBluetoothManager managerService = IBluetoothManager.Stub.asInterface(b); - sAdapter = new BluetoothAdapter(managerService); - } else { - Log.e(TAG, "Bluetooth binder is null"); - } + sAdapter = createAdapter(BluetoothManager.resolveAttributionSource(null)); } return sAdapter; } + /** {@hide} */ + public static BluetoothAdapter createAdapter(AttributionSource attributionSource) { + IBinder binder = ServiceManager.getService(BLUETOOTH_MANAGER_SERVICE); + if (binder != null) { + return new BluetoothAdapter(IBluetoothManager.Stub.asInterface(binder), + attributionSource); + } else { + Log.e(TAG, "Bluetooth binder is null"); + return null; + } + } + /** * Use {@link #getDefaultAdapter} to get the BluetoothAdapter instance. */ - BluetoothAdapter(IBluetoothManager managerService) { - + BluetoothAdapter(IBluetoothManager managerService, AttributionSource attributionSource) { if (managerService == null) { throw new IllegalArgumentException("bluetooth manager service is null"); } @@ -758,7 +802,8 @@ public final class BluetoothAdapter { } finally { mServiceLock.writeLock().unlock(); } - mManagerService = managerService; + mManagerService = Objects.requireNonNull(managerService); + mAttributionSource = Objects.requireNonNull(attributionSource); mLeScanClients = new HashMap<LeScanCallback, ScanCallback>(); mToken = new Binder(); } @@ -775,8 +820,11 @@ public final class BluetoothAdapter { * @param address valid Bluetooth MAC address * @throws IllegalArgumentException if address is invalid */ + @RequiresNoPermission public BluetoothDevice getRemoteDevice(String address) { - return new BluetoothDevice(address); + final BluetoothDevice res = new BluetoothDevice(address); + res.setAttributionSource(mAttributionSource); + return res; } /** @@ -790,13 +838,16 @@ public final class BluetoothAdapter { * @param address Bluetooth MAC address (6 bytes) * @throws IllegalArgumentException if address is invalid */ + @RequiresNoPermission public BluetoothDevice getRemoteDevice(byte[] address) { if (address == null || address.length != 6) { throw new IllegalArgumentException("Bluetooth address must have 6 bytes"); } - return new BluetoothDevice( + final BluetoothDevice res = new BluetoothDevice( String.format(Locale.US, "%02X:%02X:%02X:%02X:%02X:%02X", address[0], address[1], address[2], address[3], address[4], address[5])); + res.setAttributionSource(mAttributionSource); + return res; } /** @@ -807,16 +858,17 @@ public final class BluetoothAdapter { * Use {@link #isMultipleAdvertisementSupported()} to check whether LE Advertising is supported * on this device before calling this method. */ + @RequiresNoPermission public BluetoothLeAdvertiser getBluetoothLeAdvertiser() { if (!getLeAccess()) { return null; } synchronized (mLock) { - if (sBluetoothLeAdvertiser == null) { - sBluetoothLeAdvertiser = new BluetoothLeAdvertiser(mManagerService); + if (mBluetoothLeAdvertiser == null) { + mBluetoothLeAdvertiser = new BluetoothLeAdvertiser(this); } + return mBluetoothLeAdvertiser; } - return sBluetoothLeAdvertiser; } /** @@ -829,6 +881,7 @@ public final class BluetoothAdapter { * * @hide */ + @RequiresNoPermission public PeriodicAdvertisingManager getPeriodicAdvertisingManager() { if (!getLeAccess()) { return null; @@ -839,27 +892,27 @@ public final class BluetoothAdapter { } synchronized (mLock) { - if (sPeriodicAdvertisingManager == null) { - sPeriodicAdvertisingManager = new PeriodicAdvertisingManager(mManagerService); + if (mPeriodicAdvertisingManager == null) { + mPeriodicAdvertisingManager = new PeriodicAdvertisingManager(this); } + return mPeriodicAdvertisingManager; } - return sPeriodicAdvertisingManager; } /** * Returns a {@link BluetoothLeScanner} object for Bluetooth LE scan operations. */ + @RequiresNoPermission public BluetoothLeScanner getBluetoothLeScanner() { if (!getLeAccess()) { return null; } synchronized (mLock) { - if (sBluetoothLeScanner == null) { - sBluetoothLeScanner = new BluetoothLeScanner(mManagerService, getOpPackageName(), - getAttributionTag()); + if (mBluetoothLeScanner == null) { + mBluetoothLeScanner = new BluetoothLeScanner(this); } + return mBluetoothLeScanner; } - return sBluetoothLeScanner; } /** @@ -869,7 +922,8 @@ public final class BluetoothAdapter { * * @return true if the local adapter is turned on */ - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresLegacyBluetoothPermission + @RequiresNoPermission public boolean isEnabled() { return getState() == BluetoothAdapter.STATE_ON; } @@ -883,6 +937,7 @@ public final class BluetoothAdapter { * @hide */ @SystemApi + @RequiresNoPermission public boolean isLeEnabled() { final int state = getLeState(); if (DBG) { @@ -920,13 +975,15 @@ public final class BluetoothAdapter { * @hide */ @SystemApi + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean disableBLE() { if (!isBleScanAlwaysAvailable()) { return false; } String packageName = ActivityThread.currentPackageName(); try { - return mManagerService.disableBle(packageName, mToken); + return mManagerService.disableBle(mAttributionSource, mToken); } catch (RemoteException e) { Log.e(TAG, "", e); } @@ -965,13 +1022,15 @@ public final class BluetoothAdapter { * @hide */ @SystemApi + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean enableBLE() { if (!isBleScanAlwaysAvailable()) { return false; } String packageName = ActivityThread.currentPackageName(); try { - return mManagerService.enableBle(packageName, mToken); + return mManagerService.enableBle(mAttributionSource, mToken); } catch (RemoteException e) { Log.e(TAG, "", e); } @@ -985,6 +1044,7 @@ public final class BluetoothAdapter { new PropertyInvalidatedCache<Void, Integer>( 8, BLUETOOTH_GET_STATE_CACHE_PROPERTY) { @Override + @SuppressLint("AndroidFrameworkRequiresPermission") protected Integer recompute(Void query) { try { return mService.getState(); @@ -995,6 +1055,7 @@ public final class BluetoothAdapter { }; /** @hide */ + @RequiresNoPermission public void disableBluetoothGetStateCache() { mBluetoothGetStateCache.disableLocal(); } @@ -1038,7 +1099,8 @@ public final class BluetoothAdapter { * * @return current state of Bluetooth adapter */ - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresLegacyBluetoothPermission + @RequiresNoPermission @AdapterState public int getState() { int state = getStateInternal(); @@ -1074,7 +1136,8 @@ public final class BluetoothAdapter { * @return current state of Bluetooth adapter * @hide */ - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresLegacyBluetoothPermission + @RequiresNoPermission @AdapterState @UnsupportedAppUsage(publicAlternatives = "Use {@link #getState()} instead to determine " + "whether you can use BLE & BT classic.") @@ -1121,7 +1184,9 @@ public final class BluetoothAdapter { * * @return true to indicate adapter startup has begun, or false on immediate error */ - @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean enable() { if (isEnabled()) { if (DBG) { @@ -1130,7 +1195,7 @@ public final class BluetoothAdapter { return true; } try { - return mManagerService.enable(ActivityThread.currentPackageName()); + return mManagerService.enable(mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); } @@ -1158,10 +1223,12 @@ public final class BluetoothAdapter { * * @return true to indicate adapter shutdown has begun, or false on immediate error */ - @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean disable() { try { - return mManagerService.disable(ActivityThread.currentPackageName(), true); + return mManagerService.disable(mAttributionSource, true); } catch (RemoteException e) { Log.e(TAG, "", e); } @@ -1171,17 +1238,17 @@ public final class BluetoothAdapter { /** * Turn off the local Bluetooth adapter and don't persist the setting. * - * <p>Requires the {@link android.Manifest.permission#BLUETOOTH_ADMIN} - * permission - * * @return true to indicate adapter shutdown has begun, or false on immediate error * @hide */ @UnsupportedAppUsage(trackingBug = 171933273) + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean disable(boolean persist) { try { - return mManagerService.disable(ActivityThread.currentPackageName(), persist); + return mManagerService.disable(mAttributionSource, persist); } catch (RemoteException e) { Log.e(TAG, "", e); } @@ -1194,10 +1261,15 @@ public final class BluetoothAdapter { * * @return Bluetooth hardware address as string */ - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.LOCAL_MAC_ADDRESS, + }) public String getAddress() { try { - return mManagerService.getAddress(); + return mManagerService.getAddress(mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); } @@ -1207,19 +1279,33 @@ public final class BluetoothAdapter { /** * Get the friendly Bluetooth name of the local Bluetooth adapter. * <p>This name is visible to remote Bluetooth devices. - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} * * @return the Bluetooth name, or null on error */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public String getName() { try { - return mManagerService.getName(); + return mManagerService.getName(mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); } return null; } + /** {@hide} */ + @RequiresBluetoothAdvertisePermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) + public int getNameLengthForAdvertise() { + try { + return mService.getNameLengthForAdvertise(mAttributionSource); + } catch (RemoteException e) { + Log.e(TAG, "", e); + } + return -1; + } + /** * Factory reset bluetooth settings. * @@ -1227,12 +1313,17 @@ public final class BluetoothAdapter { * @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public boolean factoryReset() { try { mServiceLock.readLock().lock(); - if (mService != null && mService.factoryReset() - && mManagerService != null && mManagerService.onFactoryReset()) { + if (mService != null && mService.factoryReset(mAttributionSource) + && mManagerService != null + && mManagerService.onFactoryReset(mAttributionSource)) { return true; } Log.e(TAG, "factoryReset(): Setting persist.bluetooth.factoryreset to retry later"); @@ -1252,7 +1343,9 @@ public final class BluetoothAdapter { * @hide */ @UnsupportedAppUsage - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public @Nullable ParcelUuid[] getUuids() { if (getState() != STATE_ON) { return null; @@ -1260,7 +1353,7 @@ public final class BluetoothAdapter { try { mServiceLock.readLock().lock(); if (mService != null) { - return mService.getUuids(); + return mService.getUuids(mAttributionSource); } } catch (RemoteException e) { Log.e(TAG, "", e); @@ -1284,7 +1377,9 @@ public final class BluetoothAdapter { * @param name a valid Bluetooth name * @return true if the name was set, false otherwise */ - @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean setName(String name) { if (getState() != STATE_ON) { return false; @@ -1292,7 +1387,7 @@ public final class BluetoothAdapter { try { mServiceLock.readLock().lock(); if (mService != null) { - return mService.setName(name); + return mService.setName(name, mAttributionSource); } } catch (RemoteException e) { Log.e(TAG, "", e); @@ -1310,7 +1405,9 @@ public final class BluetoothAdapter { * * @hide */ - @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public BluetoothClass getBluetoothClass() { if (getState() != STATE_ON) { return null; @@ -1318,7 +1415,7 @@ public final class BluetoothAdapter { try { mServiceLock.readLock().lock(); if (mService != null) { - return mService.getBluetoothClass(); + return mService.getBluetoothClass(mAttributionSource); } } catch (RemoteException e) { Log.e(TAG, "", e); @@ -1339,7 +1436,11 @@ public final class BluetoothAdapter { * * @hide */ - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public boolean setBluetoothClass(BluetoothClass bluetoothClass) { if (getState() != STATE_ON) { return false; @@ -1347,7 +1448,7 @@ public final class BluetoothAdapter { try { mServiceLock.readLock().lock(); if (mService != null) { - return mService.setBluetoothClass(bluetoothClass); + return mService.setBluetoothClass(bluetoothClass, mAttributionSource); } } catch (RemoteException e) { Log.e(TAG, "", e); @@ -1366,13 +1467,15 @@ public final class BluetoothAdapter { * * @hide */ - @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) @IoCapability public int getIoCapability() { if (getState() != STATE_ON) return BluetoothAdapter.IO_CAPABILITY_UNKNOWN; try { mServiceLock.readLock().lock(); - if (mService != null) return mService.getIoCapability(); + if (mService != null) return mService.getIoCapability(mAttributionSource); } catch (RemoteException e) { Log.e(TAG, e.getMessage(), e); } finally { @@ -1394,12 +1497,16 @@ public final class BluetoothAdapter { * * @hide */ - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public boolean setIoCapability(@IoCapability int capability) { if (getState() != STATE_ON) return false; try { mServiceLock.readLock().lock(); - if (mService != null) return mService.setIoCapability(capability); + if (mService != null) return mService.setIoCapability(capability, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, e.getMessage(), e); } finally { @@ -1417,13 +1524,15 @@ public final class BluetoothAdapter { * * @hide */ - @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) @IoCapability public int getLeIoCapability() { if (getState() != STATE_ON) return BluetoothAdapter.IO_CAPABILITY_UNKNOWN; try { mServiceLock.readLock().lock(); - if (mService != null) return mService.getLeIoCapability(); + if (mService != null) return mService.getLeIoCapability(mAttributionSource); } catch (RemoteException e) { Log.e(TAG, e.getMessage(), e); } finally { @@ -1445,12 +1554,16 @@ public final class BluetoothAdapter { * * @hide */ - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public boolean setLeIoCapability(@IoCapability int capability) { if (getState() != STATE_ON) return false; try { mServiceLock.readLock().lock(); - if (mService != null) return mService.setLeIoCapability(capability); + if (mService != null) return mService.setLeIoCapability(capability, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, e.getMessage(), e); } finally { @@ -1474,7 +1587,9 @@ public final class BluetoothAdapter { * * @return scan mode */ - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresLegacyBluetoothPermission + @RequiresBluetoothScanPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) @ScanMode public int getScanMode() { if (getState() != STATE_ON) { @@ -1483,7 +1598,7 @@ public final class BluetoothAdapter { try { mServiceLock.readLock().lock(); if (mService != null) { - return mService.getScanMode(); + return mService.getScanMode(mAttributionSource); } } catch (RemoteException e) { Log.e(TAG, "", e); @@ -1521,7 +1636,9 @@ public final class BluetoothAdapter { */ @UnsupportedAppUsage(publicAlternatives = "Use {@link #ACTION_REQUEST_DISCOVERABLE}, which " + "shows UI that confirms the user wants to go into discoverable mode.") - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresLegacyBluetoothPermission + @RequiresBluetoothScanPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public boolean setScanMode(@ScanMode int mode, long durationMillis) { if (getState() != STATE_ON) { return false; @@ -1530,7 +1647,7 @@ public final class BluetoothAdapter { mServiceLock.readLock().lock(); if (mService != null) { int durationSeconds = Math.toIntExact(durationMillis / 1000); - return mService.setScanMode(mode, durationSeconds); + return mService.setScanMode(mode, durationSeconds, mAttributionSource); } } catch (RemoteException e) { Log.e(TAG, "", e); @@ -1570,7 +1687,9 @@ public final class BluetoothAdapter { * @hide */ @UnsupportedAppUsage - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresLegacyBluetoothPermission + @RequiresBluetoothScanPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public boolean setScanMode(@ScanMode int mode) { if (getState() != STATE_ON) { return false; @@ -1578,7 +1697,7 @@ public final class BluetoothAdapter { try { mServiceLock.readLock().lock(); if (mService != null) { - return mService.setScanMode(mode, getDiscoverableTimeout()); + return mService.setScanMode(mode, getDiscoverableTimeout(), mAttributionSource); } } catch (RemoteException e) { Log.e(TAG, "", e); @@ -1590,6 +1709,8 @@ public final class BluetoothAdapter { /** @hide */ @UnsupportedAppUsage + @RequiresBluetoothScanPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public int getDiscoverableTimeout() { if (getState() != STATE_ON) { return -1; @@ -1597,7 +1718,7 @@ public final class BluetoothAdapter { try { mServiceLock.readLock().lock(); if (mService != null) { - return mService.getDiscoverableTimeout(); + return mService.getDiscoverableTimeout(mAttributionSource); } } catch (RemoteException e) { Log.e(TAG, "", e); @@ -1609,6 +1730,8 @@ public final class BluetoothAdapter { /** @hide */ @UnsupportedAppUsage + @RequiresBluetoothScanPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public void setDiscoverableTimeout(int timeout) { if (getState() != STATE_ON) { return; @@ -1616,7 +1739,7 @@ public final class BluetoothAdapter { try { mServiceLock.readLock().lock(); if (mService != null) { - mService.setDiscoverableTimeout(timeout); + mService.setDiscoverableTimeout(timeout, mAttributionSource); } } catch (RemoteException e) { Log.e(TAG, "", e); @@ -1634,12 +1757,16 @@ public final class BluetoothAdapter { * @hide */ @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public long getDiscoveryEndMillis() { try { mServiceLock.readLock().lock(); if (mService != null) { - return mService.getDiscoveryEndMillis(); + return mService.getDiscoveryEndMillis(mAttributionSource); } } catch (RemoteException e) { Log.e(TAG, "", e); @@ -1650,32 +1777,6 @@ public final class BluetoothAdapter { } /** - * Set the context for this BluetoothAdapter (only called from BluetoothManager) - * @hide - */ - public void setContext(Context context) { - mContext = context; - } - - private String getOpPackageName() { - // Workaround for legacy API for getting a BluetoothAdapter not - // passing a context - if (mContext != null) { - return mContext.getOpPackageName(); - } - return ActivityThread.currentOpPackageName(); - } - - private String getAttributionTag() { - // Workaround for legacy API for getting a BluetoothAdapter not - // passing a context - if (mContext != null) { - return mContext.getAttributionTag(); - } - return null; - } - - /** * Start the remote device discovery process. * <p>The discovery process usually involves an inquiry scan of about 12 * seconds, followed by a page scan of each new device to retrieve its @@ -1704,7 +1805,10 @@ public final class BluetoothAdapter { * * @return true on success, false on error */ - @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothScanPermission + @RequiresBluetoothLocationPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public boolean startDiscovery() { if (getState() != STATE_ON) { return false; @@ -1712,7 +1816,7 @@ public final class BluetoothAdapter { try { mServiceLock.readLock().lock(); if (mService != null) { - return mService.startDiscovery(getOpPackageName(), getAttributionTag()); + return mService.startDiscovery(mAttributionSource); } } catch (RemoteException e) { Log.e(TAG, "", e); @@ -1738,7 +1842,9 @@ public final class BluetoothAdapter { * * @return true on success, false on error */ - @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothScanPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public boolean cancelDiscovery() { if (getState() != STATE_ON) { return false; @@ -1746,7 +1852,7 @@ public final class BluetoothAdapter { try { mServiceLock.readLock().lock(); if (mService != null) { - return mService.cancelDiscovery(); + return mService.cancelDiscovery(mAttributionSource); } } catch (RemoteException e) { Log.e(TAG, "", e); @@ -1774,7 +1880,9 @@ public final class BluetoothAdapter { * * @return true if discovering */ - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresLegacyBluetoothPermission + @RequiresBluetoothScanPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public boolean isDiscovering() { if (getState() != STATE_ON) { return false; @@ -1782,7 +1890,7 @@ public final class BluetoothAdapter { try { mServiceLock.readLock().lock(); if (mService != null) { - return mService.isDiscovering(); + return mService.isDiscovering(mAttributionSource); } } catch (RemoteException e) { Log.e(TAG, "", e); @@ -1806,7 +1914,12 @@ public final class BluetoothAdapter { * @hide */ @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + android.Manifest.permission.MODIFY_PHONE_STATE, + }) public boolean removeActiveDevice(@ActiveDeviceUse int profiles) { if (profiles != ACTIVE_DEVICE_AUDIO && profiles != ACTIVE_DEVICE_PHONE_CALL && profiles != ACTIVE_DEVICE_ALL) { @@ -1820,7 +1933,7 @@ public final class BluetoothAdapter { mServiceLock.readLock().lock(); if (mService != null) { if (DBG) Log.d(TAG, "removeActiveDevice, profiles: " + profiles); - return mService.removeActiveDevice(profiles); + return mService.removeActiveDevice(profiles, mAttributionSource); } } catch (RemoteException e) { Log.e(TAG, "", e); @@ -1846,7 +1959,12 @@ public final class BluetoothAdapter { * @hide */ @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + android.Manifest.permission.MODIFY_PHONE_STATE, + }) public boolean setActiveDevice(@NonNull BluetoothDevice device, @ActiveDeviceUse int profiles) { if (device == null) { @@ -1867,7 +1985,7 @@ public final class BluetoothAdapter { if (DBG) { Log.d(TAG, "setActiveDevice, device: " + device + ", profiles: " + profiles); } - return mService.setActiveDevice(device, profiles); + return mService.setActiveDevice(device, profiles, mAttributionSource); } } catch (RemoteException e) { Log.e(TAG, "", e); @@ -1890,12 +2008,17 @@ public final class BluetoothAdapter { * * @hide */ - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + android.Manifest.permission.MODIFY_PHONE_STATE, + }) public boolean connectAllEnabledProfiles(@NonNull BluetoothDevice device) { try { mServiceLock.readLock().lock(); if (mService != null) { - return mService.connectAllEnabledProfiles(device); + return mService.connectAllEnabledProfiles(device, mAttributionSource); } } catch (RemoteException e) { Log.e(TAG, "", e); @@ -1918,12 +2041,16 @@ public final class BluetoothAdapter { * * @hide */ - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public boolean disconnectAllEnabledProfiles(@NonNull BluetoothDevice device) { try { mServiceLock.readLock().lock(); if (mService != null) { - return mService.disconnectAllEnabledProfiles(device); + return mService.disconnectAllEnabledProfiles(device, mAttributionSource); } } catch (RemoteException e) { Log.e(TAG, "", e); @@ -1939,6 +2066,8 @@ public final class BluetoothAdapter { * * @return true if Multiple Advertisement feature is supported */ + @RequiresLegacyBluetoothPermission + @RequiresNoPermission public boolean isMultipleAdvertisementSupported() { if (getState() != STATE_ON) { return false; @@ -1967,6 +2096,7 @@ public final class BluetoothAdapter { * @hide */ @SystemApi + @RequiresNoPermission public boolean isBleScanAlwaysAvailable() { try { return mManagerService.isBleScanAlwaysAvailable(); @@ -1982,6 +2112,7 @@ public final class BluetoothAdapter { new PropertyInvalidatedCache<Void, Boolean>( 8, BLUETOOTH_FILTERING_CACHE_PROPERTY) { @Override + @SuppressLint("AndroidFrameworkRequiresPermission") protected Boolean recompute(Void query) { try { mServiceLock.readLock().lock(); @@ -1999,6 +2130,7 @@ public final class BluetoothAdapter { }; /** @hide */ + @RequiresNoPermission public void disableIsOffloadedFilteringSupportedCache() { mBluetoothFilteringCache.disableLocal(); } @@ -2013,6 +2145,8 @@ public final class BluetoothAdapter { * * @return true if chipset supports on-chip filtering */ + @RequiresLegacyBluetoothPermission + @RequiresNoPermission public boolean isOffloadedFilteringSupported() { if (!getLeAccess()) { return false; @@ -2025,6 +2159,8 @@ public final class BluetoothAdapter { * * @return true if chipset supports on-chip scan batching */ + @RequiresLegacyBluetoothPermission + @RequiresNoPermission public boolean isOffloadedScanBatchingSupported() { if (!getLeAccess()) { return false; @@ -2047,6 +2183,8 @@ public final class BluetoothAdapter { * * @return true if chipset supports LE 2M PHY feature */ + @RequiresLegacyBluetoothPermission + @RequiresNoPermission public boolean isLe2MPhySupported() { if (!getLeAccess()) { return false; @@ -2069,6 +2207,8 @@ public final class BluetoothAdapter { * * @return true if chipset supports LE Coded PHY feature */ + @RequiresLegacyBluetoothPermission + @RequiresNoPermission public boolean isLeCodedPhySupported() { if (!getLeAccess()) { return false; @@ -2091,6 +2231,8 @@ public final class BluetoothAdapter { * * @return true if chipset supports LE Extended Advertising feature */ + @RequiresLegacyBluetoothPermission + @RequiresNoPermission public boolean isLeExtendedAdvertisingSupported() { if (!getLeAccess()) { return false; @@ -2113,6 +2255,8 @@ public final class BluetoothAdapter { * * @return true if chipset supports LE Periodic Advertising feature */ + @RequiresLegacyBluetoothPermission + @RequiresNoPermission public boolean isLePeriodicAdvertisingSupported() { if (!getLeAccess()) { return false; @@ -2136,6 +2280,8 @@ public final class BluetoothAdapter { * * @return the maximum LE advertising data length. */ + @RequiresLegacyBluetoothPermission + @RequiresNoPermission public int getLeMaximumAdvertisingDataLength() { if (!getLeAccess()) { return 0; @@ -2158,6 +2304,7 @@ public final class BluetoothAdapter { * * @return true if phone supports Hearing Aid Profile */ + @RequiresNoPermission private boolean isHearingAidProfileSupported() { try { return mManagerService.isHearingAidProfileSupported(); @@ -2173,12 +2320,14 @@ public final class BluetoothAdapter { * @return the maximum number of connected audio devices * @hide */ - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getMaxConnectedAudioDevices() { try { mServiceLock.readLock().lock(); if (mService != null) { - return mService.getMaxConnectedAudioDevices(); + return mService.getMaxConnectedAudioDevices(mAttributionSource); } } catch (RemoteException e) { Log.e(TAG, "failed to get getMaxConnectedAudioDevices, error: ", e); @@ -2194,6 +2343,8 @@ public final class BluetoothAdapter { * @return true if there are hw entries available for matching beacons * @hide */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean isHardwareTrackingFiltersAvailable() { if (!getLeAccess()) { return false; @@ -2204,7 +2355,7 @@ public final class BluetoothAdapter { // BLE is not supported return false; } - return (iGatt.numHwTrackFiltersAvailable() != 0); + return (iGatt.numHwTrackFiltersAvailable(mAttributionSource) != 0); } catch (RemoteException e) { Log.e(TAG, "", e); } @@ -2224,6 +2375,11 @@ public final class BluetoothAdapter { * instead. */ @Deprecated + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public BluetoothActivityEnergyInfo getControllerActivityEnergyInfo(int updateType) { SynchronousResultReceiver receiver = new SynchronousResultReceiver(); requestControllerActivityEnergyInfo(receiver); @@ -2249,11 +2405,16 @@ public final class BluetoothAdapter { * @param result The callback to which to send the activity info. * @hide */ + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public void requestControllerActivityEnergyInfo(ResultReceiver result) { try { mServiceLock.readLock().lock(); if (mService != null) { - mService.requestActivityInfo(result); + mService.requestActivityInfo(result, mAttributionSource); result = null; } } catch (RemoteException e) { @@ -2276,7 +2437,9 @@ public final class BluetoothAdapter { * * @hide */ - @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public @NonNull List<BluetoothDevice> getMostRecentlyConnectedDevices() { if (getState() != STATE_ON) { return new ArrayList<>(); @@ -2284,7 +2447,9 @@ public final class BluetoothAdapter { try { mServiceLock.readLock().lock(); if (mService != null) { - return mService.getMostRecentlyConnectedDevices(); + return Attributable.setAttributionSource( + mService.getMostRecentlyConnectedDevices(mAttributionSource), + mAttributionSource); } } catch (RemoteException e) { Log.e(TAG, "", e); @@ -2304,17 +2469,21 @@ public final class BluetoothAdapter { * * @return unmodifiable set of {@link BluetoothDevice}, or null on error */ - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public Set<BluetoothDevice> getBondedDevices() { if (getState() != STATE_ON) { - return toDeviceSet(new BluetoothDevice[0]); + return toDeviceSet(Arrays.asList()); } try { mServiceLock.readLock().lock(); if (mService != null) { - return toDeviceSet(mService.getBondedDevices()); + return toDeviceSet(Attributable.setAttributionSource( + Arrays.asList(mService.getBondedDevices(mAttributionSource)), + mAttributionSource)); } - return toDeviceSet(new BluetoothDevice[0]); + return toDeviceSet(Arrays.asList()); } catch (RemoteException e) { Log.e(TAG, "", e); } finally { @@ -2333,6 +2502,7 @@ public final class BluetoothAdapter { * BluetoothProfile}. * @hide */ + @RequiresNoPermission public @NonNull List<Integer> getSupportedProfiles() { final ArrayList<Integer> supportedProfiles = new ArrayList<Integer>(); @@ -2359,6 +2529,38 @@ public final class BluetoothAdapter { return supportedProfiles; } + private static final String BLUETOOTH_GET_ADAPTER_CONNECTION_STATE_CACHE_PROPERTY = + "cache_key.bluetooth.get_adapter_connection_state"; + private final PropertyInvalidatedCache<Void, Integer> + mBluetoothGetAdapterConnectionStateCache = + new PropertyInvalidatedCache<Void, Integer> ( + 8, BLUETOOTH_GET_ADAPTER_CONNECTION_STATE_CACHE_PROPERTY) { + /** + * This method must not be called when mService is null. + */ + @Override + @SuppressLint("AndroidFrameworkRequiresPermission") + protected Integer recompute(Void query) { + try { + return mService.getAdapterConnectionState(); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + }; + + /** @hide */ + @RequiresNoPermission + public void disableGetAdapterConnectionStateCache() { + mBluetoothGetAdapterConnectionStateCache.disableLocal(); + } + + /** @hide */ + public static void invalidateGetAdapterConnectionStateCache() { + PropertyInvalidatedCache.invalidateCache( + BLUETOOTH_GET_ADAPTER_CONNECTION_STATE_CACHE_PROPERTY); + } + /** * Get the current connection state of the local Bluetooth adapter. * This can be used to check whether the local Bluetooth adapter is connected @@ -2372,6 +2574,8 @@ public final class BluetoothAdapter { * @hide */ @UnsupportedAppUsage + @RequiresLegacyBluetoothPermission + @RequiresNoPermission public int getConnectionState() { if (getState() != STATE_ON) { return BluetoothAdapter.STATE_DISCONNECTED; @@ -2379,10 +2583,14 @@ public final class BluetoothAdapter { try { mServiceLock.readLock().lock(); if (mService != null) { - return mService.getAdapterConnectionState(); + return mBluetoothGetAdapterConnectionStateCache.query(null); + } + } catch (RuntimeException e) { + if (e.getCause() instanceof RemoteException) { + Log.e(TAG, "getConnectionState:", e.getCause()); + } else { + throw e; } - } catch (RemoteException e) { - Log.e(TAG, "getConnectionState:", e); } finally { mServiceLock.readLock().unlock(); } @@ -2396,6 +2604,7 @@ public final class BluetoothAdapter { new PropertyInvalidatedCache<Integer, Integer>( 8, BLUETOOTH_PROFILE_CACHE_PROPERTY) { @Override + @SuppressLint("AndroidFrameworkRequiresPermission") protected Integer recompute(Integer query) { try { mServiceLock.readLock().lock(); @@ -2417,6 +2626,7 @@ public final class BluetoothAdapter { }; /** @hide */ + @RequiresNoPermission public void disableGetProfileConnectionStateCache() { mGetProfileConnectionStateCache.disableLocal(); } @@ -2438,7 +2648,10 @@ public final class BluetoothAdapter { * {@link BluetoothProfile#STATE_CONNECTED}, * {@link BluetoothProfile#STATE_DISCONNECTING} */ - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) + @SuppressLint("AndroidFrameworkRequiresPermission") public int getProfileConnectionState(int profile) { if (getState() != STATE_ON) { return BluetoothProfile.STATE_DISCONNECTED; @@ -2453,7 +2666,6 @@ public final class BluetoothAdapter { * <p>Use {@link BluetoothServerSocket#accept} to retrieve incoming * connections from a listening {@link BluetoothServerSocket}. * <p>Valid RFCOMM channels are in range 1 to 30. - * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} * * @param channel RFCOMM channel to listen on * @return a listening RFCOMM BluetoothServerSocket @@ -2461,6 +2673,9 @@ public final class BluetoothAdapter { * permissions, or channel in use. * @hide */ + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public BluetoothServerSocket listenUsingRfcommOn(int channel) throws IOException { return listenUsingRfcommOn(channel, false, false); } @@ -2472,7 +2687,6 @@ public final class BluetoothAdapter { * <p>Use {@link BluetoothServerSocket#accept} to retrieve incoming * connections from a listening {@link BluetoothServerSocket}. * <p>Valid RFCOMM channels are in range 1 to 30. - * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} * <p>To auto assign a channel without creating a SDP record use * {@link #SOCKET_CHANNEL_AUTO_STATIC_NO_SDP} as channel number. * @@ -2486,6 +2700,9 @@ public final class BluetoothAdapter { * @hide */ @UnsupportedAppUsage + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public BluetoothServerSocket listenUsingRfcommOn(int channel, boolean mitm, boolean min16DigitPin) throws IOException { BluetoothServerSocket socket = @@ -2526,7 +2743,9 @@ public final class BluetoothAdapter { * @throws IOException on error, for example Bluetooth not available, or insufficient * permissions, or channel in use. */ - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public BluetoothServerSocket listenUsingRfcommWithServiceRecord(String name, UUID uuid) throws IOException { return createNewRfcommSocketAndRecord(name, uuid, true, true); @@ -2558,7 +2777,9 @@ public final class BluetoothAdapter { * @throws IOException on error, for example Bluetooth not available, or insufficient * permissions, or channel in use. */ - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public BluetoothServerSocket listenUsingInsecureRfcommWithServiceRecord(String name, UUID uuid) throws IOException { return createNewRfcommSocketAndRecord(name, uuid, false, false); @@ -2589,7 +2810,6 @@ public final class BluetoothAdapter { * closed, or if this application closes unexpectedly. * <p>Use {@link BluetoothDevice#createRfcommSocketToServiceRecord} to * connect to this socket from another device using the same {@link UUID}. - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} * * @param name service name for SDP record * @param uuid uuid for SDP record @@ -2599,12 +2819,16 @@ public final class BluetoothAdapter { * @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public BluetoothServerSocket listenUsingEncryptedRfcommWithServiceRecord(String name, UUID uuid) throws IOException { return createNewRfcommSocketAndRecord(name, uuid, false, true); } - + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) private BluetoothServerSocket createNewRfcommSocketAndRecord(String name, UUID uuid, boolean auth, boolean encrypt) throws IOException { BluetoothServerSocket socket; @@ -2630,6 +2854,8 @@ public final class BluetoothAdapter { * permissions. * @hide */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public BluetoothServerSocket listenUsingInsecureRfcommOn(int port) throws IOException { BluetoothServerSocket socket = new BluetoothServerSocket(BluetoothSocket.TYPE_RFCOMM, false, false, port); @@ -2661,6 +2887,8 @@ public final class BluetoothAdapter { * permissions. * @hide */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public BluetoothServerSocket listenUsingL2capOn(int port, boolean mitm, boolean min16DigitPin) throws IOException { BluetoothServerSocket socket = @@ -2693,11 +2921,12 @@ public final class BluetoothAdapter { * permissions. * @hide */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public BluetoothServerSocket listenUsingL2capOn(int port) throws IOException { return listenUsingL2capOn(port, false, false); } - /** * Construct an insecure L2CAP server socket. * Call #accept to retrieve connections to this socket. @@ -2710,6 +2939,8 @@ public final class BluetoothAdapter { * permissions. * @hide */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public BluetoothServerSocket listenUsingInsecureL2capOn(int port) throws IOException { Log.d(TAG, "listenUsingInsecureL2capOn: port=" + port); BluetoothServerSocket socket = @@ -2736,11 +2967,14 @@ public final class BluetoothAdapter { /** * Read the local Out of Band Pairing Data - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} * * @return Pair<byte[], byte[]> of Hash and Randomizer * @hide */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) + @SuppressLint("AndroidFrameworkRequiresPermission") public Pair<byte[], byte[]> readOutOfBandData() { return null; } @@ -2761,6 +2995,10 @@ public final class BluetoothAdapter { * BluetoothProfile#HEARING_AID} or {@link BluetoothProfile#GATT_SERVER}. * @return true on success, false on error */ + @SuppressLint({ + "AndroidFrameworkRequiresPermission", + "AndroidFrameworkBluetoothPermission" + }) public boolean getProfileProxy(Context context, BluetoothProfile.ServiceListener listener, int profile) { if (context == null || listener == null) { @@ -2768,53 +3006,57 @@ public final class BluetoothAdapter { } if (profile == BluetoothProfile.HEADSET) { - BluetoothHeadset headset = new BluetoothHeadset(context, listener); + BluetoothHeadset headset = new BluetoothHeadset(context, listener, this); return true; } else if (profile == BluetoothProfile.A2DP) { - BluetoothA2dp a2dp = new BluetoothA2dp(context, listener); + BluetoothA2dp a2dp = new BluetoothA2dp(context, listener, this); return true; } else if (profile == BluetoothProfile.A2DP_SINK) { - BluetoothA2dpSink a2dpSink = new BluetoothA2dpSink(context, listener); + BluetoothA2dpSink a2dpSink = new BluetoothA2dpSink(context, listener, this); return true; } else if (profile == BluetoothProfile.AVRCP_CONTROLLER) { - BluetoothAvrcpController avrcp = new BluetoothAvrcpController(context, listener); + BluetoothAvrcpController avrcp = new BluetoothAvrcpController(context, listener, this); return true; } else if (profile == BluetoothProfile.HID_HOST) { - BluetoothHidHost iDev = new BluetoothHidHost(context, listener); + BluetoothHidHost iDev = new BluetoothHidHost(context, listener, this); return true; } else if (profile == BluetoothProfile.PAN) { - BluetoothPan pan = new BluetoothPan(context, listener); + BluetoothPan pan = new BluetoothPan(context, listener, this); return true; } else if (profile == BluetoothProfile.PBAP) { - BluetoothPbap pbap = new BluetoothPbap(context, listener); + BluetoothPbap pbap = new BluetoothPbap(context, listener, this); return true; } else if (profile == BluetoothProfile.HEALTH) { Log.e(TAG, "getProfileProxy(): BluetoothHealth is deprecated"); return false; } else if (profile == BluetoothProfile.MAP) { - BluetoothMap map = new BluetoothMap(context, listener); + BluetoothMap map = new BluetoothMap(context, listener, this); return true; } else if (profile == BluetoothProfile.HEADSET_CLIENT) { - BluetoothHeadsetClient headsetClient = new BluetoothHeadsetClient(context, listener); + BluetoothHeadsetClient headsetClient = + new BluetoothHeadsetClient(context, listener, this); return true; } else if (profile == BluetoothProfile.SAP) { - BluetoothSap sap = new BluetoothSap(context, listener); + BluetoothSap sap = new BluetoothSap(context, listener, this); return true; } else if (profile == BluetoothProfile.PBAP_CLIENT) { - BluetoothPbapClient pbapClient = new BluetoothPbapClient(context, listener); + BluetoothPbapClient pbapClient = new BluetoothPbapClient(context, listener, this); return true; } else if (profile == BluetoothProfile.MAP_CLIENT) { - BluetoothMapClient mapClient = new BluetoothMapClient(context, listener); + BluetoothMapClient mapClient = new BluetoothMapClient(context, listener, this); return true; } else if (profile == BluetoothProfile.HID_DEVICE) { - BluetoothHidDevice hidDevice = new BluetoothHidDevice(context, listener); + BluetoothHidDevice hidDevice = new BluetoothHidDevice(context, listener, this); return true; } else if (profile == BluetoothProfile.HEARING_AID) { if (isHearingAidProfileSupported()) { - BluetoothHearingAid hearingAid = new BluetoothHearingAid(context, listener); + BluetoothHearingAid hearingAid = new BluetoothHearingAid(context, listener, this); return true; } return false; + } else if (profile == BluetoothProfile.LE_AUDIO) { + BluetoothLeAudio leAudio = new BluetoothLeAudio(context, listener, this); + return true; } else if (profile == BluetoothProfile.VOLUME_CONTROL) { BluetoothVolumeControl vcs = new BluetoothVolumeControl(context, listener, this); return true; @@ -2833,6 +3075,10 @@ public final class BluetoothAdapter { * @param profile * @param proxy Profile proxy object */ + @SuppressLint({ + "AndroidFrameworkRequiresPermission", + "AndroidFrameworkBluetoothPermission" + }) public void closeProfileProxy(int profile, BluetoothProfile proxy) { if (proxy == null) { return; @@ -2903,6 +3149,10 @@ public final class BluetoothAdapter { BluetoothHearingAid hearingAid = (BluetoothHearingAid) proxy; hearingAid.close(); break; + case BluetoothProfile.LE_AUDIO: + BluetoothLeAudio leAudio = (BluetoothLeAudio) proxy; + leAudio.close(); + break; case BluetoothProfile.VOLUME_CONTROL: BluetoothVolumeControl vcs = (BluetoothVolumeControl) proxy; vcs.close(); @@ -2910,8 +3160,10 @@ public final class BluetoothAdapter { } } + @SuppressLint("AndroidFrameworkBluetoothPermission") private final IBluetoothManagerCallback mManagerCallback = new IBluetoothManagerCallback.Stub() { + @SuppressLint("AndroidFrameworkRequiresPermission") public void onBluetoothServiceUp(IBluetooth bluetoothService) { if (DBG) { Log.d(TAG, "onBluetoothServiceUp: " + bluetoothService); @@ -2934,11 +3186,11 @@ public final class BluetoothAdapter { } } } - synchronized (sMetadataListeners) { - sMetadataListeners.forEach((device, pair) -> { + synchronized (mMetadataListeners) { + mMetadataListeners.forEach((device, pair) -> { try { - mService.registerMetadataListener(sBluetoothMetadataListener, - device); + mService.registerMetadataListener(mBluetoothMetadataListener, + device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "Failed to register metadata listener", e); } @@ -2947,7 +3199,8 @@ public final class BluetoothAdapter { synchronized (mBluetoothConnectionCallbackExecutorMap) { if (!mBluetoothConnectionCallbackExecutorMap.isEmpty()) { try { - mService.registerBluetoothConnectionCallback(mConnectionCallback); + mService.registerBluetoothConnectionCallback(mConnectionCallback, + mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "onBluetoothServiceUp: Failed to register bluetooth" + "connection callback", e); @@ -2967,11 +3220,11 @@ public final class BluetoothAdapter { if (mLeScanClients != null) { mLeScanClients.clear(); } - if (sBluetoothLeAdvertiser != null) { - sBluetoothLeAdvertiser.cleanup(); + if (mBluetoothLeAdvertiser != null) { + mBluetoothLeAdvertiser.cleanup(); } - if (sBluetoothLeScanner != null) { - sBluetoothLeScanner.cleanup(); + if (mBluetoothLeScanner != null) { + mBluetoothLeScanner.cleanup(); } } finally { mServiceLock.writeLock().unlock(); @@ -3006,7 +3259,9 @@ public final class BluetoothAdapter { * @hide */ @SystemApi - @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean enableNoAutoConnect() { if (isEnabled()) { if (DBG) { @@ -3015,7 +3270,7 @@ public final class BluetoothAdapter { return true; } try { - return mManagerService.enableNoAutoConnect(ActivityThread.currentPackageName()); + return mManagerService.enableNoAutoConnect(mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); } @@ -3024,41 +3279,16 @@ public final class BluetoothAdapter { /** @hide */ @Retention(RetentionPolicy.SOURCE) - @IntDef(prefix = { "OOB_ERROR_" }, value = { - OOB_ERROR_UNKNOWN, - OOB_ERROR_ANOTHER_ACTIVE_REQUEST, - OOB_ERROR_ADAPTER_DISABLED + @IntDef(value = { + BluetoothStatusCodes.ERROR_UNKNOWN, + BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED, + BluetoothStatusCodes.ERROR_ANOTHER_ACTIVE_OOB_REQUEST, }) public @interface OobError {} /** - * An unknown error has occurred in the controller, stack, or callback pipeline. - * - * @hide - */ - @SystemApi - public static final int OOB_ERROR_UNKNOWN = 0; - - /** - * If another application has already requested {@link OobData} then another fetch will be - * disallowed until the callback is removed. - * - * @hide - */ - @SystemApi - public static final int OOB_ERROR_ANOTHER_ACTIVE_REQUEST = 1; - - /** - * The adapter is currently disabled, please enable it. - * - * @hide - */ - @SystemApi - public static final int OOB_ERROR_ADAPTER_DISABLED = 2; - - /** * Provides callback methods for receiving {@link OobData} from the host stack, as well as an - * error interface in order to allow the caller to determine next steps based on the {@link + * error interface in order to allow the caller to determine next steps based on the {@code * ErrorCode}. * * @hide @@ -3076,7 +3306,7 @@ public final class BluetoothAdapter { /** * Provides feedback when things don't go as expected. * - * @param errorCode - the code descibing the type of error that occurred. + * @param errorCode - the code describing the type of error that occurred. */ void onError(@OobError int errorCode); } @@ -3159,7 +3389,11 @@ public final class BluetoothAdapter { * @hide */ @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public void generateLocalOobData(@Transport int transport, @NonNull @CallbackExecutor Executor executor, @NonNull OobDataCallback callback) { if (transport != BluetoothDevice.TRANSPORT_BREDR && transport @@ -3169,11 +3403,11 @@ public final class BluetoothAdapter { requireNonNull(callback); if (!isEnabled()) { Log.w(TAG, "generateLocalOobData(): Adapter isn't enabled!"); - callback.onError(OOB_ERROR_ADAPTER_DISABLED); + callback.onError(BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED); } else { try { mService.generateLocalOobData(transport, new WrappedOobDataCallback(callback, - executor)); + executor), mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); } @@ -3203,12 +3437,14 @@ public final class BluetoothAdapter { * reason. If Bluetooth is already on and if this function is called to turn * it on, the api will return true and a callback will be called. * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} - * * @param on True for on, false for off. * @param callback The callback to notify changes to the state. * @hide */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) + @SuppressLint("AndroidFrameworkRequiresPermission") public boolean changeApplicationBluetoothState(boolean on, BluetoothStateChangeCallback callback) { return false; @@ -3240,8 +3476,8 @@ public final class BluetoothAdapter { } } - private Set<BluetoothDevice> toDeviceSet(BluetoothDevice[] devices) { - Set<BluetoothDevice> deviceSet = new HashSet<BluetoothDevice>(Arrays.asList(devices)); + private Set<BluetoothDevice> toDeviceSet(List<BluetoothDevice> devices) { + Set<BluetoothDevice> deviceSet = new HashSet<BluetoothDevice>(devices); return Collections.unmodifiableSet(deviceSet); } @@ -3306,11 +3542,19 @@ public final class BluetoothAdapter { && (Integer.parseInt(address.split(":")[5], 16) & 0b11) == 0b11; } + /** {@hide} */ @UnsupportedAppUsage - /*package*/ IBluetoothManager getBluetoothManager() { + @RequiresNoPermission + public IBluetoothManager getBluetoothManager() { return mManagerService; } + /** {@hide} */ + @RequiresNoPermission + public AttributionSource getAttributionSource() { + return mAttributionSource; + } + private final ArrayList<IBluetoothManagerCallback> mProxyServiceStateCallbacks = new ArrayList<IBluetoothManagerCallback>(); @@ -3352,6 +3596,61 @@ public final class BluetoothAdapter { } /** + * Register a callback to receive events whenever the bluetooth stack goes down and back up, + * e.g. in the event the bluetooth is turned off/on via settings. + * + * If the bluetooth stack is currently up, there will not be an initial callback call. + * You can use the return value as an indication of this being the case. + * + * Callbacks will be delivered on a binder thread. + * + * @return whether bluetooth is already up currently + * + * @hide + */ + public boolean registerServiceLifecycleCallback(ServiceLifecycleCallback callback) { + return getBluetoothService(callback.mRemote) != null; + } + + /** + * Unregister a callback registered via {@link #registerServiceLifecycleCallback} + * + * @hide + */ + public void unregisterServiceLifecycleCallback(ServiceLifecycleCallback callback) { + removeServiceStateCallback(callback.mRemote); + } + + /** + * A callback for {@link #registerServiceLifecycleCallback} + * + * @hide + */ + public abstract static class ServiceLifecycleCallback { + + /** Called when the bluetooth stack is up */ + public abstract void onBluetoothServiceUp(); + + /** Called when the bluetooth stack is down */ + public abstract void onBluetoothServiceDown(); + + IBluetoothManagerCallback mRemote = new IBluetoothManagerCallback.Stub() { + @Override + public void onBluetoothServiceUp(IBluetooth bluetoothService) { + ServiceLifecycleCallback.this.onBluetoothServiceUp(); + } + + @Override + public void onBluetoothServiceDown() { + ServiceLifecycleCallback.this.onBluetoothServiceDown(); + } + + @Override + public void onBrEdrDown() {} + }; + } + + /** * Starts a scan for Bluetooth LE devices. * * <p>Results of the scan are reported using the @@ -3363,7 +3662,10 @@ public final class BluetoothAdapter { * instead. */ @Deprecated - @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothScanPermission + @RequiresBluetoothLocationPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public boolean startLeScan(LeScanCallback callback) { return startLeScan(null, callback); } @@ -3382,7 +3684,10 @@ public final class BluetoothAdapter { * instead. */ @Deprecated - @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothScanPermission + @RequiresBluetoothLocationPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public boolean startLeScan(final UUID[] serviceUuids, final LeScanCallback callback) { if (DBG) { Log.d(TAG, "startLeScan(): " + Arrays.toString(serviceUuids)); @@ -3416,6 +3721,7 @@ public final class BluetoothAdapter { return false; } + @SuppressLint("AndroidFrameworkBluetoothPermission") ScanCallback scanCallback = new ScanCallback() { @Override public void onScanResult(int callbackType, ScanResult result) { @@ -3479,7 +3785,9 @@ public final class BluetoothAdapter { * @deprecated Use {@link BluetoothLeScanner#stopScan(ScanCallback)} instead. */ @Deprecated - @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothScanPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public void stopLeScan(LeScanCallback callback) { if (DBG) { Log.d(TAG, "stopLeScan()"); @@ -3520,7 +3828,9 @@ public final class BluetoothAdapter { * @throws IOException on error, for example Bluetooth not available, or insufficient * permissions, or unable to start this CoC */ - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public @NonNull BluetoothServerSocket listenUsingL2capChannel() throws IOException { BluetoothServerSocket socket = @@ -3566,7 +3876,9 @@ public final class BluetoothAdapter { * @throws IOException on error, for example Bluetooth not available, or insufficient * permissions, or unable to start this CoC */ - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public @NonNull BluetoothServerSocket listenUsingInsecureL2capChannel() throws IOException { BluetoothServerSocket socket = @@ -3611,7 +3923,11 @@ public final class BluetoothAdapter { * @hide */ @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public boolean addOnMetadataChangedListener(@NonNull BluetoothDevice device, @NonNull Executor executor, @NonNull OnMetadataChangedListener listener) { if (DBG) Log.d(TAG, "addOnMetadataChangedListener()"); @@ -3631,13 +3947,13 @@ public final class BluetoothAdapter { throw new NullPointerException("executor is null"); } - synchronized (sMetadataListeners) { + synchronized (mMetadataListeners) { List<Pair<OnMetadataChangedListener, Executor>> listenerList = - sMetadataListeners.get(device); + mMetadataListeners.get(device); if (listenerList == null) { // Create new listener/executor list for registeration listenerList = new ArrayList<>(); - sMetadataListeners.put(device, listenerList); + mMetadataListeners.put(device, listenerList); } else { // Check whether this device was already registed by the lisenter if (listenerList.stream().anyMatch((pair) -> (pair.first.equals(listener)))) { @@ -3651,7 +3967,8 @@ public final class BluetoothAdapter { boolean ret = false; try { - ret = service.registerMetadataListener(sBluetoothMetadataListener, device); + ret = service.registerMetadataListener(mBluetoothMetadataListener, device, + mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "registerMetadataListener fail", e); } finally { @@ -3660,7 +3977,7 @@ public final class BluetoothAdapter { listenerList.remove(listenerPair); if (listenerList.isEmpty()) { // Remove the device if its listener list is empty - sMetadataListeners.remove(device); + mMetadataListeners.remove(device); } } } @@ -3684,7 +4001,11 @@ public final class BluetoothAdapter { * @hide */ @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public boolean removeOnMetadataChangedListener(@NonNull BluetoothDevice device, @NonNull OnMetadataChangedListener listener) { if (DBG) Log.d(TAG, "removeOnMetadataChangedListener()"); @@ -3695,24 +4016,24 @@ public final class BluetoothAdapter { throw new NullPointerException("listener is null"); } - synchronized (sMetadataListeners) { - if (!sMetadataListeners.containsKey(device)) { + synchronized (mMetadataListeners) { + if (!mMetadataListeners.containsKey(device)) { throw new IllegalArgumentException("device was not registered"); } // Remove issued listener from the registered device - sMetadataListeners.get(device).removeIf((pair) -> (pair.first.equals(listener))); + mMetadataListeners.get(device).removeIf((pair) -> (pair.first.equals(listener))); - if (sMetadataListeners.get(device).isEmpty()) { + if (mMetadataListeners.get(device).isEmpty()) { // Unregister to Bluetooth service if all listeners are removed from // the registered device - sMetadataListeners.remove(device); + mMetadataListeners.remove(device); final IBluetooth service = mService; if (service == null) { // Bluetooth is OFF, do nothing to Bluetooth service. return true; } try { - return service.unregisterMetadataListener(device); + return service.unregisterMetadataListener(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "unregisterMetadataListener fail", e); return false; @@ -3740,10 +4061,12 @@ public final class BluetoothAdapter { @Nullable byte[] value); } + @SuppressLint("AndroidFrameworkBluetoothPermission") private final IBluetoothConnectionCallback mConnectionCallback = new IBluetoothConnectionCallback.Stub() { @Override public void onDeviceConnected(BluetoothDevice device) { + Attributable.setAttributionSource(device, mAttributionSource); for (Map.Entry<BluetoothConnectionCallback, Executor> callbackExecutorEntry: mBluetoothConnectionCallbackExecutorMap.entrySet()) { BluetoothConnectionCallback callback = callbackExecutorEntry.getKey(); @@ -3754,6 +4077,7 @@ public final class BluetoothAdapter { @Override public void onDeviceDisconnected(BluetoothDevice device, int hciReason) { + Attributable.setAttributionSource(device, mAttributionSource); for (Map.Entry<BluetoothConnectionCallback, Executor> callbackExecutorEntry: mBluetoothConnectionCallbackExecutorMap.entrySet()) { BluetoothConnectionCallback callback = callbackExecutorEntry.getKey(); @@ -3773,6 +4097,11 @@ public final class BluetoothAdapter { * @throws IllegalArgumentException if the callback is already registered * @hide */ + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public boolean registerBluetoothConnectionCallback(@NonNull @CallbackExecutor Executor executor, @NonNull BluetoothConnectionCallback callback) { if (DBG) Log.d(TAG, "registerBluetoothConnectionCallback()"); @@ -3786,7 +4115,8 @@ public final class BluetoothAdapter { try { mServiceLock.readLock().lock(); if (mService != null) { - if (!mService.registerBluetoothConnectionCallback(mConnectionCallback)) { + if (!mService.registerBluetoothConnectionCallback(mConnectionCallback, + mAttributionSource)) { return false; } } @@ -3815,6 +4145,11 @@ public final class BluetoothAdapter { * @return true if the callback was unregistered successfully, false otherwise * @hide */ + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public boolean unregisterBluetoothConnectionCallback( @NonNull BluetoothConnectionCallback callback) { if (DBG) Log.d(TAG, "unregisterBluetoothConnectionCallback()"); @@ -3836,7 +4171,8 @@ public final class BluetoothAdapter { try { mServiceLock.readLock().lock(); if (mService != null) { - return mService.unregisterBluetoothConnectionCallback(mConnectionCallback); + return mService.unregisterBluetoothConnectionCallback(mConnectionCallback, + mAttributionSource); } } catch (RemoteException e) { Log.e(TAG, "", e); @@ -3872,141 +4208,45 @@ public final class BluetoothAdapter { */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = { "REASON_" }, value = { - REASON_UNKNOWN, - REASON_LOCAL_REQUEST, - REASON_REMOTE_REQUEST, - REASON_LOCAL_ERROR, - REASON_REMOTE_ERROR, - REASON_TIMEOUT, - REASON_SECURITY, - REASON_SYSTEM_POLICY, - REASON_RESOURCE_LIMIT_REACHED, - REASON_CONNECTION_EXISTS, - REASON_BAD_PARAMETERS}) + BluetoothStatusCodes.ERROR_UNKNOWN, + BluetoothStatusCodes.ERROR_DISCONNECT_REASON_LOCAL_REQUEST, + BluetoothStatusCodes.ERROR_DISCONNECT_REASON_REMOTE_REQUEST, + BluetoothStatusCodes.ERROR_DISCONNECT_REASON_LOCAL, + BluetoothStatusCodes.ERROR_DISCONNECT_REASON_REMOTE, + BluetoothStatusCodes.ERROR_DISCONNECT_REASON_TIMEOUT, + BluetoothStatusCodes.ERROR_DISCONNECT_REASON_SECURITY, + BluetoothStatusCodes.ERROR_DISCONNECT_REASON_SYSTEM_POLICY, + BluetoothStatusCodes.ERROR_DISCONNECT_REASON_RESOURCE_LIMIT_REACHED, + BluetoothStatusCodes.ERROR_DISCONNECT_REASON_CONNECTION_ALREADY_EXISTS, + BluetoothStatusCodes.ERROR_DISCONNECT_REASON_BAD_PARAMETERS}) public @interface DisconnectReason {} /** - * Indicates that the ACL disconnected due to an unknown reason. - */ - public static final int REASON_UNKNOWN = 0; - - /** - * Indicates that the ACL disconnected due to an explicit request from the local device. - * <p> - * Example cause: This is a normal disconnect reason, e.g., user/app initiates - * disconnection. - */ - public static final int REASON_LOCAL_REQUEST = 1; - - /** - * Indicates that the ACL disconnected due to an explicit request from the remote device. - * <p> - * Example cause: This is a normal disconnect reason, e.g., user/app initiates - * disconnection. - * <p> - * Example solution: The app can also prompt the user to check their remote device. - */ - public static final int REASON_REMOTE_REQUEST = 2; - - /** - * Generic disconnect reason indicating the ACL disconnected due to an error on the local - * device. - * <p> - * Example solution: Prompt the user to check their local device (e.g., phone, car - * headunit). - */ - public static final int REASON_LOCAL_ERROR = 3; - - /** - * Generic disconnect reason indicating the ACL disconnected due to an error on the remote - * device. - * <p> - * Example solution: Prompt the user to check their remote device (e.g., headset, car - * headunit, watch). - */ - public static final int REASON_REMOTE_ERROR = 4; - - /** - * Indicates that the ACL disconnected due to a timeout. - * <p> - * Example cause: remote device might be out of range. - * <p> - * Example solution: Prompt user to verify their remote device is on or in - * connection/pairing mode. - */ - public static final int REASON_TIMEOUT = 5; - - /** - * Indicates that the ACL disconnected due to link key issues. - * <p> - * Example cause: Devices are either unpaired or remote device is refusing our pairing - * request. - * <p> - * Example solution: Prompt user to unpair and pair again. - */ - public static final int REASON_SECURITY = 6; - - /** - * Indicates that the ACL disconnected due to the local device's system policy. - * <p> - * Example cause: privacy policy, power management policy, permissions, etc. - * <p> - * Example solution: Prompt the user to check settings, or check with their system - * administrator (e.g. some corp-managed devices do not allow OPP connection). - */ - public static final int REASON_SYSTEM_POLICY = 7; - - /** - * Indicates that the ACL disconnected due to resource constraints, either on the local - * device or the remote device. - * <p> - * Example cause: controller is busy, memory limit reached, maximum number of connections - * reached. - * <p> - * Example solution: The app should wait and try again. If still failing, prompt the user - * to disconnect some devices, or toggle Bluetooth on the local and/or the remote device. - */ - public static final int REASON_RESOURCE_LIMIT_REACHED = 8; - - /** - * Indicates that the ACL disconnected because another ACL connection already exists. - */ - public static final int REASON_CONNECTION_EXISTS = 9; - - /** - * Indicates that the ACL disconnected due to incorrect parameters passed in from the app. - * <p> - * Example solution: Change parameters and try again. If error persists, the app can report - * telemetry and/or log the error in a bugreport. - */ - public static final int REASON_BAD_PARAMETERS = 10; - - /** * Returns human-readable strings corresponding to {@link DisconnectReason}. */ public static String disconnectReasonText(@DisconnectReason int reason) { switch (reason) { - case REASON_UNKNOWN: + case BluetoothStatusCodes.ERROR_UNKNOWN: return "Reason unknown"; - case REASON_LOCAL_REQUEST: + case BluetoothStatusCodes.ERROR_DISCONNECT_REASON_LOCAL_REQUEST: return "Local request"; - case REASON_REMOTE_REQUEST: + case BluetoothStatusCodes.ERROR_DISCONNECT_REASON_REMOTE_REQUEST: return "Remote request"; - case REASON_LOCAL_ERROR: + case BluetoothStatusCodes.ERROR_DISCONNECT_REASON_LOCAL: return "Local error"; - case REASON_REMOTE_ERROR: + case BluetoothStatusCodes.ERROR_DISCONNECT_REASON_REMOTE: return "Remote error"; - case REASON_TIMEOUT: + case BluetoothStatusCodes.ERROR_DISCONNECT_REASON_TIMEOUT: return "Timeout"; - case REASON_SECURITY: + case BluetoothStatusCodes.ERROR_DISCONNECT_REASON_SECURITY: return "Security"; - case REASON_SYSTEM_POLICY: + case BluetoothStatusCodes.ERROR_DISCONNECT_REASON_SYSTEM_POLICY: return "System policy"; - case REASON_RESOURCE_LIMIT_REACHED: + case BluetoothStatusCodes.ERROR_DISCONNECT_REASON_RESOURCE_LIMIT_REACHED: return "Resource constrained"; - case REASON_CONNECTION_EXISTS: + case BluetoothStatusCodes.ERROR_DISCONNECT_REASON_CONNECTION_ALREADY_EXISTS: return "Connection already exists"; - case REASON_BAD_PARAMETERS: + case BluetoothStatusCodes.ERROR_DISCONNECT_REASON_BAD_PARAMETERS: return "Bad parameters"; default: return "Unrecognized disconnect reason: " + reason; diff --git a/framework/java/android/bluetooth/BluetoothAudioConfig.java b/framework/java/android/bluetooth/BluetoothAudioConfig.java index 9591a70b05..4c8b8c11fb 100644 --- a/framework/java/android/bluetooth/BluetoothAudioConfig.java +++ b/framework/java/android/bluetooth/BluetoothAudioConfig.java @@ -16,6 +16,7 @@ package android.bluetooth; +import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; @@ -39,7 +40,7 @@ public final class BluetoothAudioConfig implements Parcelable { } @Override - public boolean equals(Object o) { + public boolean equals(@Nullable Object o) { if (o instanceof BluetoothAudioConfig) { BluetoothAudioConfig bac = (BluetoothAudioConfig) o; return (bac.mSampleRate == mSampleRate && bac.mChannelConfig == mChannelConfig diff --git a/framework/java/android/bluetooth/BluetoothAvrcpController.java b/framework/java/android/bluetooth/BluetoothAvrcpController.java index 4e7e4415c5..d27c276480 100644 --- a/framework/java/android/bluetooth/BluetoothAvrcpController.java +++ b/framework/java/android/bluetooth/BluetoothAvrcpController.java @@ -16,6 +16,14 @@ package android.bluetooth; +import android.annotation.RequiresPermission; +import android.annotation.SdkConstant; +import android.annotation.SuppressLint; +import android.annotation.SdkConstant.SdkConstantType; +import android.bluetooth.annotations.RequiresBluetoothConnectPermission; +import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; +import android.content.Attributable; +import android.content.AttributionSource; import android.content.Context; import android.os.Binder; import android.os.IBinder; @@ -54,10 +62,11 @@ public final class BluetoothAvrcpController implements BluetoothProfile { * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}. - * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to - * receive. */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.avrcp-controller.profile.action.CONNECTION_STATE_CHANGED"; @@ -70,13 +79,17 @@ public final class BluetoothAvrcpController implements BluetoothProfile { * most recent player setting. </li> * </ul> */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_PLAYER_SETTING = "android.bluetooth.avrcp-controller.profile.action.PLAYER_SETTING"; public static final String EXTRA_PLAYER_SETTING = "android.bluetooth.avrcp-controller.profile.extra.PLAYER_SETTING"; - private BluetoothAdapter mAdapter; + private final BluetoothAdapter mAdapter; + private final AttributionSource mAttributionSource; private final BluetoothProfileConnector<IBluetoothAvrcpController> mProfileConnector = new BluetoothProfileConnector(this, BluetoothProfile.AVRCP_CONTROLLER, "BluetoothAvrcpController", IBluetoothAvrcpController.class.getName()) { @@ -91,8 +104,10 @@ public final class BluetoothAvrcpController implements BluetoothProfile { * Create a BluetoothAvrcpController proxy object for interacting with the local * Bluetooth AVRCP service. */ - /*package*/ BluetoothAvrcpController(Context context, ServiceListener listener) { - mAdapter = BluetoothAdapter.getDefaultAdapter(); + /* package */ BluetoothAvrcpController(Context context, ServiceListener listener, + BluetoothAdapter adapter) { + mAdapter = adapter; + mAttributionSource = adapter.getAttributionSource(); mProfileConnector.connect(context, listener); } @@ -113,13 +128,16 @@ public final class BluetoothAvrcpController implements BluetoothProfile { * {@inheritDoc} */ @Override + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public List<BluetoothDevice> getConnectedDevices() { if (VDBG) log("getConnectedDevices()"); final IBluetoothAvrcpController service = getService(); if (service != null && isEnabled()) { try { - return service.getConnectedDevices(); + return Attributable.setAttributionSource( + service.getConnectedDevices(mAttributionSource), mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return new ArrayList<BluetoothDevice>(); @@ -133,13 +151,17 @@ public final class BluetoothAvrcpController implements BluetoothProfile { * {@inheritDoc} */ @Override + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { if (VDBG) log("getDevicesMatchingStates()"); final IBluetoothAvrcpController service = getService(); if (service != null && isEnabled()) { try { - return service.getDevicesMatchingConnectionStates(states); + return Attributable.setAttributionSource( + service.getDevicesMatchingConnectionStates(states, mAttributionSource), + mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return new ArrayList<BluetoothDevice>(); @@ -153,13 +175,15 @@ public final class BluetoothAvrcpController implements BluetoothProfile { * {@inheritDoc} */ @Override + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getConnectionState(BluetoothDevice device) { if (VDBG) log("getState(" + device + ")"); final IBluetoothAvrcpController service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { try { - return service.getConnectionState(device); + return service.getConnectionState(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return BluetoothProfile.STATE_DISCONNECTED; @@ -174,6 +198,8 @@ public final class BluetoothAvrcpController implements BluetoothProfile { * * @return the {@link BluetoothAvrcpPlayerSettings} or {@link null} if there is an error. */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public BluetoothAvrcpPlayerSettings getPlayerSettings(BluetoothDevice device) { if (DBG) Log.d(TAG, "getPlayerSettings"); BluetoothAvrcpPlayerSettings settings = null; @@ -181,7 +207,7 @@ public final class BluetoothAvrcpController implements BluetoothProfile { getService(); if (service != null && isEnabled()) { try { - settings = service.getPlayerSettings(device); + settings = service.getPlayerSettings(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "Error talking to BT service in getMetadata() " + e); return null; @@ -194,13 +220,15 @@ public final class BluetoothAvrcpController implements BluetoothProfile { * Sets the player app setting for current player. * returns true in case setting is supported by remote, false otherwise */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean setPlayerApplicationSetting(BluetoothAvrcpPlayerSettings plAppSetting) { if (DBG) Log.d(TAG, "setPlayerApplicationSetting"); final IBluetoothAvrcpController service = getService(); if (service != null && isEnabled()) { try { - return service.setPlayerApplicationSetting(plAppSetting); + return service.setPlayerApplicationSetting(plAppSetting, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "Error talking to BT service in setPlayerApplicationSetting() " + e); return false; @@ -214,6 +242,8 @@ public final class BluetoothAvrcpController implements BluetoothProfile { * Send Group Navigation Command to Remote. * possible keycode values: next_grp, previous_grp defined above */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void sendGroupNavigationCmd(BluetoothDevice device, int keyCode, int keyState) { Log.d(TAG, "sendGroupNavigationCmd dev = " + device + " key " + keyCode + " State = " + keyState); @@ -221,7 +251,7 @@ public final class BluetoothAvrcpController implements BluetoothProfile { getService(); if (service != null && isEnabled()) { try { - service.sendGroupNavigationCmd(device, keyCode, keyState); + service.sendGroupNavigationCmd(device, keyCode, keyState, mAttributionSource); return; } catch (RemoteException e) { Log.e(TAG, "Error talking to BT service in sendGroupNavigationCmd()", e); diff --git a/framework/java/android/bluetooth/BluetoothClass.java b/framework/java/android/bluetooth/BluetoothClass.java index 1e92fc0568..7fe18a0704 100755 --- a/framework/java/android/bluetooth/BluetoothClass.java +++ b/framework/java/android/bluetooth/BluetoothClass.java @@ -16,6 +16,7 @@ package android.bluetooth; +import android.annotation.Nullable; import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; @@ -72,7 +73,7 @@ public final class BluetoothClass implements Parcelable { } @Override - public boolean equals(Object o) { + public boolean equals(@Nullable Object o) { if (o instanceof BluetoothClass) { return mClass == ((BluetoothClass) o).mClass; } diff --git a/framework/java/android/bluetooth/BluetoothCodecConfig.java b/framework/java/android/bluetooth/BluetoothCodecConfig.java index a52fc89179..1d0bf97c34 100644 --- a/framework/java/android/bluetooth/BluetoothCodecConfig.java +++ b/framework/java/android/bluetooth/BluetoothCodecConfig.java @@ -18,6 +18,7 @@ package android.bluetooth; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.os.Parcel; import android.os.Parcelable; @@ -208,7 +209,7 @@ public final class BluetoothCodecConfig implements Parcelable { } @Override - public boolean equals(Object o) { + public boolean equals(@Nullable Object o) { if (o instanceof BluetoothCodecConfig) { BluetoothCodecConfig other = (BluetoothCodecConfig) o; return (other.mCodecType == mCodecType diff --git a/framework/java/android/bluetooth/BluetoothCodecStatus.java b/framework/java/android/bluetooth/BluetoothCodecStatus.java index f43a9e8cab..7764ebeb2e 100644 --- a/framework/java/android/bluetooth/BluetoothCodecStatus.java +++ b/framework/java/android/bluetooth/BluetoothCodecStatus.java @@ -18,7 +18,6 @@ package android.bluetooth; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; -import android.os.Build; import android.os.Parcel; import android.os.Parcelable; @@ -40,7 +39,7 @@ public final class BluetoothCodecStatus implements Parcelable { * This extra represents the current codec status of the A2DP * profile. */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @UnsupportedAppUsage public static final String EXTRA_CODEC_STATUS = "android.bluetooth.extra.CODEC_STATUS"; @@ -57,7 +56,7 @@ public final class BluetoothCodecStatus implements Parcelable { } @Override - public boolean equals(Object o) { + public boolean equals(@Nullable Object o) { if (o instanceof BluetoothCodecStatus) { BluetoothCodecStatus other = (BluetoothCodecStatus) o; return (Objects.equals(other.mCodecConfig, mCodecConfig) @@ -199,7 +198,7 @@ public final class BluetoothCodecStatus implements Parcelable { * * @return the current codec configuration */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @UnsupportedAppUsage public @Nullable BluetoothCodecConfig getCodecConfig() { return mCodecConfig; } @@ -209,7 +208,7 @@ public final class BluetoothCodecStatus implements Parcelable { * * @return an array with the codecs local capabilities */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @UnsupportedAppUsage public @Nullable BluetoothCodecConfig[] getCodecsLocalCapabilities() { return mCodecsLocalCapabilities; } @@ -219,7 +218,7 @@ public final class BluetoothCodecStatus implements Parcelable { * * @return an array with the codecs selectable capabilities */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @UnsupportedAppUsage public @Nullable BluetoothCodecConfig[] getCodecsSelectableCapabilities() { return mCodecsSelectableCapabilities; } diff --git a/framework/java/android/bluetooth/BluetoothDevice.java b/framework/java/android/bluetooth/BluetoothDevice.java index 11b45e32c4..21ec91865d 100644 --- a/framework/java/android/bluetooth/BluetoothDevice.java +++ b/framework/java/android/bluetooth/BluetoothDevice.java @@ -16,7 +16,6 @@ package android.bluetooth; -import android.Manifest; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -26,7 +25,15 @@ import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.app.PropertyInvalidatedCache; +import android.bluetooth.annotations.RequiresBluetoothConnectPermission; +import android.bluetooth.annotations.RequiresBluetoothLocationPermission; +import android.bluetooth.annotations.RequiresBluetoothScanPermission; +import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission; +import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; +import android.companion.AssociationRequest; import android.compat.annotation.UnsupportedAppUsage; +import android.content.Attributable; +import android.content.AttributionSource; import android.content.Context; import android.os.Build; import android.os.Handler; @@ -65,9 +72,6 @@ import java.util.UUID; * {@link #createRfcommSocketToServiceRecord(UUID)} over Bluetooth BR/EDR or using * {@link #createL2capChannel(int)} over Bluetooth LE. * - * <p class="note"><strong>Note:</strong> - * Requires the {@link android.Manifest.permission#BLUETOOTH} permission. - * * <div class="special reference"> * <h3>Developer Guides</h3> * <p> @@ -80,7 +84,7 @@ import java.util.UUID; * {@see BluetoothAdapter} * {@see BluetoothSocket} */ -public final class BluetoothDevice implements Parcelable { +public final class BluetoothDevice implements Parcelable, Attributable { private static final String TAG = "BluetoothDevice"; private static final boolean DBG = false; @@ -107,10 +111,12 @@ public final class BluetoothDevice implements Parcelable { * <p>Always contains the extra fields {@link #EXTRA_DEVICE} and {@link * #EXTRA_CLASS}. Can contain the extra fields {@link #EXTRA_NAME} and/or * {@link #EXTRA_RSSI} if they are available. - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} and - * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION} to receive. */ // TODO: Change API to not broadcast RSSI if not available (incoming connection) + @RequiresLegacyBluetoothPermission + @RequiresBluetoothScanPermission + @RequiresBluetoothLocationPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_FOUND = "android.bluetooth.device.action.FOUND"; @@ -119,9 +125,11 @@ public final class BluetoothDevice implements Parcelable { * Broadcast Action: Bluetooth class of a remote device has changed. * <p>Always contains the extra fields {@link #EXTRA_DEVICE} and {@link * #EXTRA_CLASS}. - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive. * {@see BluetoothClass} */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_CLASS_CHANGED = "android.bluetooth.device.action.CLASS_CHANGED"; @@ -132,8 +140,10 @@ public final class BluetoothDevice implements Parcelable { * <p>Always contains the extra field {@link #EXTRA_DEVICE}. * <p>ACL connections are managed automatically by the Android Bluetooth * stack. - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive. */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_ACL_CONNECTED = "android.bluetooth.device.action.ACL_CONNECTED"; @@ -145,8 +155,10 @@ public final class BluetoothDevice implements Parcelable { * this intent as a hint to immediately terminate higher level connections * (RFCOMM, L2CAP, or profile connections) to the remote device. * <p>Always contains the extra field {@link #EXTRA_DEVICE}. - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive. */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_ACL_DISCONNECT_REQUESTED = "android.bluetooth.device.action.ACL_DISCONNECT_REQUESTED"; @@ -157,8 +169,10 @@ public final class BluetoothDevice implements Parcelable { * <p>Always contains the extra field {@link #EXTRA_DEVICE}. * <p>ACL connections are managed automatically by the Android Bluetooth * stack. - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive. */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_ACL_DISCONNECTED = "android.bluetooth.device.action.ACL_DISCONNECTED"; @@ -168,8 +182,10 @@ public final class BluetoothDevice implements Parcelable { * been retrieved for the first time, or changed since the last retrieval. * <p>Always contains the extra fields {@link #EXTRA_DEVICE} and {@link * #EXTRA_NAME}. - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive. */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_NAME_CHANGED = "android.bluetooth.device.action.NAME_CHANGED"; @@ -178,9 +194,11 @@ public final class BluetoothDevice implements Parcelable { * Broadcast Action: Indicates the alias of a remote device has been * changed. * <p>Always contains the extra field {@link #EXTRA_DEVICE}. - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive. */ @SuppressLint("ActionValue") + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_ALIAS_CHANGED = "android.bluetooth.device.action.ALIAS_CHANGED"; @@ -190,10 +208,12 @@ public final class BluetoothDevice implements Parcelable { * device. For example, if a device is bonded (paired). * <p>Always contains the extra fields {@link #EXTRA_DEVICE}, {@link * #EXTRA_BOND_STATE} and {@link #EXTRA_PREVIOUS_BOND_STATE}. - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive. */ // Note: When EXTRA_BOND_STATE is BOND_NONE then this will also // contain a hidden extra field EXTRA_REASON with the result code. + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_BOND_STATE_CHANGED = "android.bluetooth.device.action.BOND_STATE_CHANGED"; @@ -203,10 +223,12 @@ public final class BluetoothDevice implements Parcelable { * been retrieved for the first time, or changed since the last retrieval * <p>Always contains the extra fields {@link #EXTRA_DEVICE} and {@link * #EXTRA_BATTERY_LEVEL}. - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive. * * @hide */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_BATTERY_LEVEL_CHANGED = "android.bluetooth.device.action.BATTERY_LEVEL_CHANGED"; @@ -328,6 +350,26 @@ public final class BluetoothDevice implements Parcelable { public static final String EXTRA_PAIRING_KEY = "android.bluetooth.device.extra.PAIRING_KEY"; /** + * Used as an int extra field in {@link #ACTION_PAIRING_REQUEST} + * intents as the value of passkey. + * @hide + */ + public static final String EXTRA_PAIRING_INITIATOR = + "android.bluetooth.device.extra.PAIRING_INITIATOR"; + + /** + * Bluetooth pairing initiator, Foreground App + * @hide + */ + public static final int EXTRA_PAIRING_INITIATOR_FOREGROUND = 1; + + /** + * Bluetooth pairing initiator, Background + * @hide + */ + public static final int EXTRA_PAIRING_INITIATOR_BACKGROUND = 2; + + /** * Bluetooth device type, Unknown */ public static final int DEVICE_TYPE_UNKNOWN = 0; @@ -349,6 +391,8 @@ public final class BluetoothDevice implements Parcelable { /** @hide */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public static final String ACTION_SDP_RECORD = @@ -621,13 +665,17 @@ public final class BluetoothDevice implements Parcelable { * device are requested to be fetched using Service Discovery Protocol * <p> Always contains the extra field {@link #EXTRA_DEVICE} * <p> Always contains the extra field {@link #EXTRA_UUID} - * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} to receive. */ + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_UUID = "android.bluetooth.device.action.UUID"; /** @hide */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_MAS_INSTANCE = "android.bluetooth.device.action.MAS_INSTANCE"; @@ -636,40 +684,51 @@ public final class BluetoothDevice implements Parcelable { * Broadcast Action: Indicates a failure to retrieve the name of a remote * device. * <p>Always contains the extra field {@link #EXTRA_DEVICE}. - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive. * * @hide */ //TODO: is this actually useful? + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_NAME_FAILED = "android.bluetooth.device.action.NAME_FAILED"; /** * Broadcast Action: This intent is used to broadcast PAIRING REQUEST - * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} to - * receive. */ + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_PAIRING_REQUEST = "android.bluetooth.device.action.PAIRING_REQUEST"; /** @hide */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) @UnsupportedAppUsage public static final String ACTION_PAIRING_CANCEL = "android.bluetooth.device.action.PAIRING_CANCEL"; /** @hide */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_CONNECTION_ACCESS_REQUEST = "android.bluetooth.device.action.CONNECTION_ACCESS_REQUEST"; /** @hide */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_CONNECTION_ACCESS_REPLY = "android.bluetooth.device.action.CONNECTION_ACCESS_REPLY"; /** @hide */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_CONNECTION_ACCESS_CANCEL = "android.bluetooth.device.action.CONNECTION_ACCESS_CANCEL"; @@ -680,6 +739,8 @@ public final class BluetoothDevice implements Parcelable { * * @hide */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) @SystemApi public static final String ACTION_SILENCE_MODE_CHANGED = @@ -1045,6 +1106,8 @@ public final class BluetoothDevice implements Parcelable { private final String mAddress; @AddressType private final int mAddressType; + private AttributionSource mAttributionSource; + /*package*/ @UnsupportedAppUsage static IBluetooth getService() { @@ -1090,6 +1153,7 @@ public final class BluetoothDevice implements Parcelable { * and is validated in this constructor. * * @param address valid Bluetooth MAC address + * @param attributionSource attribution for permission-protected calls * @throws RuntimeException Bluetooth is not available on this platform * @throws IllegalArgumentException address is invalid * @hide @@ -1103,10 +1167,21 @@ public final class BluetoothDevice implements Parcelable { mAddress = address; mAddressType = ADDRESS_TYPE_PUBLIC; + mAttributionSource = BluetoothManager.resolveAttributionSource(null); + } + + /** {@hide} */ + public void setAttributionSource(@NonNull AttributionSource attributionSource) { + mAttributionSource = attributionSource; + } + + /** {@hide} */ + public void prepareToEnterProcess(@NonNull AttributionSource attributionSource) { + setAttributionSource(attributionSource); } @Override - public boolean equals(Object o) { + public boolean equals(@Nullable Object o) { if (o instanceof BluetoothDevice) { return mAddress.equals(((BluetoothDevice) o).getAddress()); } @@ -1165,6 +1240,18 @@ public final class BluetoothDevice implements Parcelable { } /** + * Returns the anonymized hardware address of this BluetoothDevice. The first three octets + * will be suppressed for anonymization. + * <p> For example, "XX:XX:XX:AA:BB:CC". + * + * @return Anonymized bluetooth hardware address as string + * @hide + */ + public String getAnonymizedAddress() { + return "XX:XX:XX" + getAddress().substring(8); + } + + /** * Get the friendly Bluetooth name of the remote device. * * <p>The local adapter will automatically retrieve remote names when @@ -1173,7 +1260,9 @@ public final class BluetoothDevice implements Parcelable { * * @return the Bluetooth name, or null if there was a problem. */ - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public String getName() { final IBluetooth service = sService; if (service == null) { @@ -1181,7 +1270,7 @@ public final class BluetoothDevice implements Parcelable { return null; } try { - String name = service.getRemoteName(this); + String name = service.getRemoteName(this, mAttributionSource); if (name != null) { // remove whitespace characters from the name return name @@ -1202,7 +1291,9 @@ public final class BluetoothDevice implements Parcelable { * @return the device type {@link #DEVICE_TYPE_CLASSIC}, {@link #DEVICE_TYPE_LE} {@link * #DEVICE_TYPE_DUAL}. {@link #DEVICE_TYPE_UNKNOWN} if it's not available */ - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getType() { final IBluetooth service = sService; if (service == null) { @@ -1210,7 +1301,7 @@ public final class BluetoothDevice implements Parcelable { return DEVICE_TYPE_UNKNOWN; } try { - return service.getRemoteType(this); + return service.getRemoteType(this, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); } @@ -1218,14 +1309,15 @@ public final class BluetoothDevice implements Parcelable { } /** - * Get the Bluetooth alias of the remote device. - * <p>Alias is the locally modified name of a remote device. + * Get the locally modifiable name (alias) of the remote Bluetooth device. * * @return the Bluetooth alias, the friendly device name if no alias, or * null if there was a problem */ @Nullable - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public String getAlias() { final IBluetooth service = sService; if (service == null) { @@ -1233,7 +1325,7 @@ public final class BluetoothDevice implements Parcelable { return null; } try { - String alias = service.getRemoteAlias(this); + String alias = service.getRemoteAliasWithAttribution(this, mAttributionSource); if (alias == null) { return getName(); } @@ -1244,30 +1336,53 @@ public final class BluetoothDevice implements Parcelable { return null; } - /** - * Set the Bluetooth alias of the remote device. - * <p>Alias is the locally modified name of a remote device. - * <p>This methoid overwrites the alias. The changed - * alias is saved in the local storage so that the change - * is preserved over power cycle. - * - * @return true on success, false on error - * @hide - */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @RequiresPermission(Manifest.permission.BLUETOOTH) - public boolean setAlias(@NonNull String alias) { + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(value = { + BluetoothStatusCodes.SUCCESS, + BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED, + BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ALLOWED, + BluetoothStatusCodes.ERROR_MISSING_BLUETOOTH_CONNECT_PERMISSION, + BluetoothStatusCodes.ERROR_DEVICE_NOT_BONDED + }) + public @interface SetAliasReturnValues{} + + /** + * Sets the locally modifiable name (alias) of the remote Bluetooth device. This method + * overwrites the previously stored alias. The new alias is saved in local + * storage so that the change is preserved over power cycles. + * + * <p>This method requires the calling app to be associated with Companion Device Manager (see + * {@link android.companion.CompanionDeviceManager#associate(AssociationRequest, + * android.companion.CompanionDeviceManager.Callback, Handler)}) and have the + * {@link android.Manifest.permission#BLUETOOTH_CONNECT} permission. Alternatively, if the + * caller has the {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED} permission, they can + * bypass the Companion Device Manager association requirement as well as other permission + * requirements. + * + * @param alias is the new locally modifiable name for the remote Bluetooth device which must + * be the empty string. If null, we clear the alias. + * @return whether the alias was successfully changed + * @throws IllegalArgumentException if the alias is the empty string + */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) + public @SetAliasReturnValues int setAlias(@Nullable String alias) { + if (alias != null && alias.isEmpty()) { + throw new IllegalArgumentException("alias cannot be the empty string"); + } final IBluetooth service = sService; if (service == null) { Log.e(TAG, "BT not enabled. Cannot set Remote Device name"); - return false; + return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED; } try { - return service.setRemoteAlias(this, alias); + return service.setRemoteAlias(this, alias, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); + throw e.rethrowFromSystemServer(); } - return false; } /** @@ -1279,7 +1394,9 @@ public final class BluetoothDevice implements Parcelable { * @hide */ @UnsupportedAppUsage - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getBatteryLevel() { final IBluetooth service = sService; if (service == null) { @@ -1287,7 +1404,7 @@ public final class BluetoothDevice implements Parcelable { return BATTERY_LEVEL_BLUETOOTH_OFF; } try { - return service.getBatteryLevel(this); + return service.getBatteryLevel(this, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); } @@ -1304,7 +1421,9 @@ public final class BluetoothDevice implements Parcelable { * * @return false on immediate error, true if bonding will begin */ - @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean createBond() { return createBond(TRANSPORT_AUTO); } @@ -1325,7 +1444,9 @@ public final class BluetoothDevice implements Parcelable { * @hide */ @UnsupportedAppUsage - @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean createBond(int transport) { return createBondInternal(transport, null, null); } @@ -1353,7 +1474,7 @@ public final class BluetoothDevice implements Parcelable { * @hide */ @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean createBondOutOfBand(int transport, @Nullable OobData remoteP192Data, @Nullable OobData remoteP256Data) { if (remoteP192Data == null && remoteP256Data == null) { @@ -1364,6 +1485,7 @@ public final class BluetoothDevice implements Parcelable { return createBondInternal(transport, remoteP192Data, remoteP256Data); } + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) private boolean createBondInternal(int transport, @Nullable OobData remoteP192Data, @Nullable OobData remoteP256Data) { final IBluetooth service = sService; @@ -1372,7 +1494,8 @@ public final class BluetoothDevice implements Parcelable { return false; } try { - return service.createBond(this, transport, remoteP192Data, remoteP256Data); + return service.createBond( + this, transport, remoteP192Data, remoteP256Data, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); } @@ -1387,7 +1510,9 @@ public final class BluetoothDevice implements Parcelable { * @hide */ @UnsupportedAppUsage - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean isBondingInitiatedLocally() { final IBluetooth service = sService; if (service == null) { @@ -1395,7 +1520,7 @@ public final class BluetoothDevice implements Parcelable { return false; } try { - return service.isBondingInitiatedLocally(this); + return service.isBondingInitiatedLocally(this, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); } @@ -1409,7 +1534,7 @@ public final class BluetoothDevice implements Parcelable { * @hide */ @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean cancelBondProcess() { final IBluetooth service = sService; if (service == null) { @@ -1420,7 +1545,7 @@ public final class BluetoothDevice implements Parcelable { Log.i(TAG, "cancelBondProcess() for device " + getAddress() + " called by pid: " + Process.myPid() + " tid: " + Process.myTid()); - return service.cancelBondProcess(this); + return service.cancelBondProcess(this, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); } @@ -1437,7 +1562,7 @@ public final class BluetoothDevice implements Parcelable { * @hide */ @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean removeBond() { final IBluetooth service = sService; if (service == null) { @@ -1448,7 +1573,7 @@ public final class BluetoothDevice implements Parcelable { Log.i(TAG, "removeBond() for device " + getAddress() + " called by pid: " + Process.myPid() + " tid: " + Process.myTid()); - return service.removeBond(this); + return service.removeBond(this, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); } @@ -1461,9 +1586,10 @@ public final class BluetoothDevice implements Parcelable { new PropertyInvalidatedCache<BluetoothDevice, Integer>( 8, BLUETOOTH_BONDING_CACHE_PROPERTY) { @Override + @SuppressLint("AndroidFrameworkRequiresPermission") protected Integer recompute(BluetoothDevice query) { try { - return sService.getBondState(query); + return sService.getBondState(query, mAttributionSource); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } @@ -1489,7 +1615,10 @@ public final class BluetoothDevice implements Parcelable { * * @return the bond state */ - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) + @SuppressLint("AndroidFrameworkRequiresPermission") public int getBondState() { final IBluetooth service = sService; if (service == null) { @@ -1509,13 +1638,43 @@ public final class BluetoothDevice implements Parcelable { } /** + * Checks whether this bluetooth device is associated with CDM and meets the criteria to skip + * the bluetooth pairing dialog because it has been already consented by the CDM prompt. + * + * @return true if we can bond without the dialog, false otherwise + * + * @hide + */ + @SystemApi + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) + public boolean canBondWithoutDialog() { + final IBluetooth service = sService; + if (service == null) { + Log.e(TAG, "BT not enabled. Cannot check if we can skip pairing dialog"); + return false; + } + try { + if (DBG) Log.d(TAG, "canBondWithoutDialog, device: " + this); + return service.canBondWithoutDialog(this, mAttributionSource); + } catch (RemoteException e) { + Log.e(TAG, "", e); + } + return false; + } + + /** * Returns whether there is an open connection to this device. * * @return True if there is at least one open connection to this device. * @hide */ @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean isConnected() { final IBluetooth service = sService; if (service == null) { @@ -1523,7 +1682,8 @@ public final class BluetoothDevice implements Parcelable { return false; } try { - return service.getConnectionState(this) != CONNECTION_STATE_DISCONNECTED; + return service.getConnectionStateWithAttribution(this, mAttributionSource) + != CONNECTION_STATE_DISCONNECTED; } catch (RemoteException e) { Log.e(TAG, "", e); return false; @@ -1538,7 +1698,9 @@ public final class BluetoothDevice implements Parcelable { * @hide */ @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean isEncrypted() { final IBluetooth service = sService; if (service == null) { @@ -1546,7 +1708,8 @@ public final class BluetoothDevice implements Parcelable { return false; } try { - return service.getConnectionState(this) > CONNECTION_STATE_CONNECTED; + return service.getConnectionStateWithAttribution(this, mAttributionSource) + > CONNECTION_STATE_CONNECTED; } catch (RemoteException e) { Log.e(TAG, "", e); return false; @@ -1558,7 +1721,9 @@ public final class BluetoothDevice implements Parcelable { * * @return Bluetooth class object, or null on error */ - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public BluetoothClass getBluetoothClass() { final IBluetooth service = sService; if (service == null) { @@ -1566,7 +1731,7 @@ public final class BluetoothDevice implements Parcelable { return null; } try { - int classInt = service.getRemoteClass(this); + int classInt = service.getRemoteClass(this, mAttributionSource); if (classInt == BluetoothClass.ERROR) return null; return new BluetoothClass(classInt); } catch (RemoteException e) { @@ -1585,7 +1750,9 @@ public final class BluetoothDevice implements Parcelable { * * @return the supported features (UUIDs) of the remote device, or null on error */ - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public ParcelUuid[] getUuids() { final IBluetooth service = sService; if (service == null || !isBluetoothEnabled()) { @@ -1593,7 +1760,7 @@ public final class BluetoothDevice implements Parcelable { return null; } try { - return service.getRemoteUuids(this); + return service.getRemoteUuids(this, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); } @@ -1613,7 +1780,9 @@ public final class BluetoothDevice implements Parcelable { * @return False if the check fails, True if the process of initiating an ACL connection * to the remote device was started or cached UUIDs will be broadcast. */ - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean fetchUuidsWithSdp() { final IBluetooth service = sService; if (service == null || !isBluetoothEnabled()) { @@ -1621,7 +1790,7 @@ public final class BluetoothDevice implements Parcelable { return false; } try { - return service.fetchRemoteUuids(this); + return service.fetchRemoteUuidsWithAttribution(this, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); } @@ -1639,8 +1808,7 @@ public final class BluetoothDevice implements Parcelable { * {@link #EXTRA_SDP_SEARCH_STATUS} different from 0. * Detailed status error codes can be found by members of the Bluetooth package in * the AbstractionLayer class. - * <p>Requires {@link android.Manifest.permission#BLUETOOTH}. - * The SDP record data will be stored in the intent as {@link #EXTRA_SDP_RECORD}. + * <p>The SDP record data will be stored in the intent as {@link #EXTRA_SDP_RECORD}. * The object type will match one of the SdpXxxRecord types, depending on the UUID searched * for. * @@ -1649,6 +1817,9 @@ public final class BluetoothDevice implements Parcelable { * was started. */ /** @hide */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean sdpSearch(ParcelUuid uuid) { final IBluetooth service = sService; if (service == null) { @@ -1656,7 +1827,7 @@ public final class BluetoothDevice implements Parcelable { return false; } try { - return service.sdpSearch(this, uuid); + return service.sdpSearch(this, uuid, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); } @@ -1665,10 +1836,12 @@ public final class BluetoothDevice implements Parcelable { /** * Set the pin during pairing when the pairing method is {@link #PAIRING_VARIANT_PIN} - * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}. * * @return true pin has been set false for error */ + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean setPin(byte[] pin) { final IBluetooth service = sService; if (service == null) { @@ -1676,7 +1849,7 @@ public final class BluetoothDevice implements Parcelable { return false; } try { - return service.setPin(this, true, pin.length, pin); + return service.setPin(this, true, pin.length, pin, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); } @@ -1690,7 +1863,9 @@ public final class BluetoothDevice implements Parcelable { * @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean setPin(@NonNull String pin) { byte[] pinBytes = convertPinToBytes(pin); if (pinBytes == null) { @@ -1704,7 +1879,10 @@ public final class BluetoothDevice implements Parcelable { * * @return true confirmation has been sent out false for error */ - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public boolean setPairingConfirmation(boolean confirm) { final IBluetooth service = sService; if (service == null) { @@ -1712,7 +1890,7 @@ public final class BluetoothDevice implements Parcelable { return false; } try { - return service.setPairingConfirmation(this, confirm); + return service.setPairingConfirmation(this, confirm, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); } @@ -1727,7 +1905,9 @@ public final class BluetoothDevice implements Parcelable { * @hide */ @UnsupportedAppUsage - @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean cancelPairing() { final IBluetooth service = sService; if (service == null) { @@ -1735,7 +1915,7 @@ public final class BluetoothDevice implements Parcelable { return false; } try { - return service.cancelBondProcess(this); + return service.cancelBondProcess(this, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); } @@ -1759,14 +1939,16 @@ public final class BluetoothDevice implements Parcelable { * @hide */ @UnsupportedAppUsage - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public @AccessPermission int getPhonebookAccessPermission() { final IBluetooth service = sService; if (service == null) { return ACCESS_UNKNOWN; } try { - return service.getPhonebookAccessPermission(this); + return service.getPhonebookAccessPermission(this, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); } @@ -1791,22 +1973,23 @@ public final class BluetoothDevice implements Parcelable { * If the {@link BluetoothDevice} is not connected with A2DP or HFP, it cannot * enter silence mode. * - * <p> Requires {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED}. - * * @param silence true to enter silence mode, false to exit * @return true on success, false on error. * @throws IllegalStateException if Bluetooth is not turned ON. * @hide */ @SystemApi - @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public boolean setSilenceMode(boolean silence) { final IBluetooth service = sService; if (service == null) { throw new IllegalStateException("Bluetooth is not turned ON"); } try { - return service.setSilenceMode(this, silence); + return service.setSilenceMode(this, silence, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "setSilenceMode fail", e); return false; @@ -1816,21 +1999,22 @@ public final class BluetoothDevice implements Parcelable { /** * Check whether the {@link BluetoothDevice} is in silence mode * - * <p> Requires {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED}. - * * @return true on device in silence mode, otherwise false. * @throws IllegalStateException if Bluetooth is not turned ON. * @hide */ @SystemApi - @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public boolean isInSilenceMode() { final IBluetooth service = sService; if (service == null) { throw new IllegalStateException("Bluetooth is not turned ON"); } try { - return service.getSilenceMode(this); + return service.getSilenceMode(this, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "isInSilenceMode fail", e); return false; @@ -1846,14 +2030,17 @@ public final class BluetoothDevice implements Parcelable { * @hide */ @SystemApi - @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public boolean setPhonebookAccessPermission(@AccessPermission int value) { final IBluetooth service = sService; if (service == null) { return false; } try { - return service.setPhonebookAccessPermission(this, value); + return service.setPhonebookAccessPermission(this, value, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); } @@ -1867,14 +2054,16 @@ public final class BluetoothDevice implements Parcelable { * @hide */ @UnsupportedAppUsage - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public @AccessPermission int getMessageAccessPermission() { final IBluetooth service = sService; if (service == null) { return ACCESS_UNKNOWN; } try { - return service.getMessageAccessPermission(this); + return service.getMessageAccessPermission(this, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); } @@ -1891,7 +2080,10 @@ public final class BluetoothDevice implements Parcelable { * @hide */ @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public boolean setMessageAccessPermission(@AccessPermission int value) { // Validates param value is one of the accepted constants if (value != ACCESS_ALLOWED && value != ACCESS_REJECTED && value != ACCESS_UNKNOWN) { @@ -1902,7 +2094,7 @@ public final class BluetoothDevice implements Parcelable { return false; } try { - return service.setMessageAccessPermission(this, value); + return service.setMessageAccessPermission(this, value, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); } @@ -1916,14 +2108,16 @@ public final class BluetoothDevice implements Parcelable { * @hide */ @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public @AccessPermission int getSimAccessPermission() { final IBluetooth service = sService; if (service == null) { return ACCESS_UNKNOWN; } try { - return service.getSimAccessPermission(this); + return service.getSimAccessPermission(this, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); } @@ -1940,14 +2134,17 @@ public final class BluetoothDevice implements Parcelable { * @hide */ @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public boolean setSimAccessPermission(int value) { final IBluetooth service = sService; if (service == null) { return false; } try { - return service.setSimAccessPermission(this, value); + return service.setSimAccessPermission(this, value, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); } @@ -1971,7 +2168,6 @@ public final class BluetoothDevice implements Parcelable { * <p>Use {@link BluetoothSocket#connect} to initiate the outgoing * connection. * <p>Valid RFCOMM channels are in range 1 to 30. - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} * * @param channel RFCOMM channel to connect to * @return a RFCOMM BluetoothServerSocket ready for an outgoing connection @@ -1980,6 +2176,10 @@ public final class BluetoothDevice implements Parcelable { * @hide */ @UnsupportedAppUsage + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) + @SuppressLint("AndroidFrameworkRequiresPermission") public BluetoothSocket createRfcommSocket(int channel) throws IOException { if (!isBluetoothEnabled()) { Log.e(TAG, "Bluetooth is not enabled"); @@ -2006,7 +2206,6 @@ public final class BluetoothDevice implements Parcelable { * <p>Use {@link BluetoothSocket#connect} to initiate the outgoing * connection. * <p>Valid L2CAP PSM channels are in range 1 to 2^16. - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} * * @param channel L2cap PSM/channel to connect to * @return a RFCOMM BluetoothServerSocket ready for an outgoing connection @@ -2014,6 +2213,10 @@ public final class BluetoothDevice implements Parcelable { * permissions * @hide */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) + @SuppressLint("AndroidFrameworkRequiresPermission") public BluetoothSocket createL2capSocket(int channel) throws IOException { return new BluetoothSocket(BluetoothSocket.TYPE_L2CAP, -1, true, true, this, channel, null); @@ -2027,7 +2230,6 @@ public final class BluetoothDevice implements Parcelable { * <p>Use {@link BluetoothSocket#connect} to initiate the outgoing * connection. * <p>Valid L2CAP PSM channels are in range 1 to 2^16. - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} * * @param channel L2cap PSM/channel to connect to * @return a RFCOMM BluetoothServerSocket ready for an outgoing connection @@ -2035,6 +2237,10 @@ public final class BluetoothDevice implements Parcelable { * permissions * @hide */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) + @SuppressLint("AndroidFrameworkRequiresPermission") public BluetoothSocket createInsecureL2capSocket(int channel) throws IOException { return new BluetoothSocket(BluetoothSocket.TYPE_L2CAP, -1, false, false, this, channel, null); @@ -2070,7 +2276,10 @@ public final class BluetoothDevice implements Parcelable { * @throws IOException on error, for example Bluetooth not available, or insufficient * permissions */ - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) + @SuppressLint("AndroidFrameworkRequiresPermission") public BluetoothSocket createRfcommSocketToServiceRecord(UUID uuid) throws IOException { if (!isBluetoothEnabled()) { Log.e(TAG, "Bluetooth is not enabled"); @@ -2108,7 +2317,10 @@ public final class BluetoothDevice implements Parcelable { * @throws IOException on error, for example Bluetooth not available, or insufficient * permissions */ - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) + @SuppressLint("AndroidFrameworkRequiresPermission") public BluetoothSocket createInsecureRfcommSocketToServiceRecord(UUID uuid) throws IOException { if (!isBluetoothEnabled()) { Log.e(TAG, "Bluetooth is not enabled"); @@ -2124,7 +2336,6 @@ public final class BluetoothDevice implements Parcelable { * Call #connect on the returned #BluetoothSocket to begin the connection. * The remote device will not be authenticated and communication on this * socket will not be encrypted. - * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} * * @param port remote port * @return An RFCOMM BluetoothSocket @@ -2134,6 +2345,10 @@ public final class BluetoothDevice implements Parcelable { */ @UnsupportedAppUsage(publicAlternatives = "Use " + "{@link #createInsecureRfcommSocketToServiceRecord} instead.") + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) + @SuppressLint("AndroidFrameworkRequiresPermission") public BluetoothSocket createInsecureRfcommSocket(int port) throws IOException { if (!isBluetoothEnabled()) { Log.e(TAG, "Bluetooth is not enabled"); @@ -2146,7 +2361,6 @@ public final class BluetoothDevice implements Parcelable { /** * Construct a SCO socket ready to start an outgoing connection. * Call #connect on the returned #BluetoothSocket to begin the connection. - * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} * * @return a SCO BluetoothSocket * @throws IOException on error, for example Bluetooth not available, or insufficient @@ -2154,6 +2368,10 @@ public final class BluetoothDevice implements Parcelable { * @hide */ @UnsupportedAppUsage + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) + @SuppressLint("AndroidFrameworkRequiresPermission") public BluetoothSocket createScoSocket() throws IOException { if (!isBluetoothEnabled()) { Log.e(TAG, "Bluetooth is not enabled"); @@ -2201,6 +2419,8 @@ public final class BluetoothDevice implements Parcelable { * automatically connect as soon as the remote device becomes available (true). * @throws IllegalArgumentException if callback is null */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public BluetoothGatt connectGatt(Context context, boolean autoConnect, BluetoothGattCallback callback) { return (connectGatt(context, autoConnect, callback, TRANSPORT_AUTO)); @@ -2221,6 +2441,8 @@ public final class BluetoothDevice implements Parcelable { * BluetoothDevice#TRANSPORT_LE} * @throws IllegalArgumentException if callback is null */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public BluetoothGatt connectGatt(Context context, boolean autoConnect, BluetoothGattCallback callback, int transport) { return (connectGatt(context, autoConnect, callback, transport, PHY_LE_1M_MASK)); @@ -2245,6 +2467,8 @@ public final class BluetoothDevice implements Parcelable { * is set to true. * @throws NullPointerException if callback is null */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public BluetoothGatt connectGatt(Context context, boolean autoConnect, BluetoothGattCallback callback, int transport, int phy) { return connectGatt(context, autoConnect, callback, transport, phy, null); @@ -2271,6 +2495,8 @@ public final class BluetoothDevice implements Parcelable { * an un-specified background thread. * @throws NullPointerException if callback is null */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public BluetoothGatt connectGatt(Context context, boolean autoConnect, BluetoothGattCallback callback, int transport, int phy, Handler handler) { @@ -2304,6 +2530,8 @@ public final class BluetoothDevice implements Parcelable { * @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public BluetoothGatt connectGatt(Context context, boolean autoConnect, BluetoothGattCallback callback, int transport, boolean opportunistic, int phy, Handler handler) { @@ -2321,7 +2549,8 @@ public final class BluetoothDevice implements Parcelable { // BLE is not supported return null; } - BluetoothGatt gatt = new BluetoothGatt(iGatt, this, transport, opportunistic, phy); + BluetoothGatt gatt = new BluetoothGatt( + iGatt, this, transport, opportunistic, phy, mAttributionSource); gatt.connect(autoConnect, callback, handler); return gatt; } catch (RemoteException e) { @@ -2348,7 +2577,10 @@ public final class BluetoothDevice implements Parcelable { * @throws IOException on error, for example Bluetooth not available, or insufficient * permissions */ - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) + @SuppressLint("AndroidFrameworkRequiresPermission") public @NonNull BluetoothSocket createL2capChannel(int psm) throws IOException { if (!isBluetoothEnabled()) { Log.e(TAG, "createL2capChannel: Bluetooth is not enabled"); @@ -2376,7 +2608,10 @@ public final class BluetoothDevice implements Parcelable { * @throws IOException on error, for example Bluetooth not available, or insufficient * permissions */ - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) + @SuppressLint("AndroidFrameworkRequiresPermission") public @NonNull BluetoothSocket createInsecureL2capChannel(int psm) throws IOException { if (!isBluetoothEnabled()) { Log.e(TAG, "createInsecureL2capChannel: Bluetooth is not enabled"); @@ -2404,7 +2639,10 @@ public final class BluetoothDevice implements Parcelable { * @hide */ @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public boolean setMetadata(@MetadataKey int key, @NonNull byte[] value) { final IBluetooth service = sService; if (service == null) { @@ -2416,7 +2654,7 @@ public final class BluetoothDevice implements Parcelable { + ", should not over " + METADATA_MAX_LENGTH); } try { - return service.setMetadata(this, key, value); + return service.setMetadata(this, key, value, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "setMetadata fail", e); return false; @@ -2432,7 +2670,10 @@ public final class BluetoothDevice implements Parcelable { */ @SystemApi @Nullable - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public byte[] getMetadata(@MetadataKey int key) { final IBluetooth service = sService; if (service == null) { @@ -2440,7 +2681,7 @@ public final class BluetoothDevice implements Parcelable { return null; } try { - return service.getMetadata(this, key); + return service.getMetadata(this, key, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "getMetadata fail", e); return null; diff --git a/framework/java/android/bluetooth/BluetoothDevicePicker.java b/framework/java/android/bluetooth/BluetoothDevicePicker.java index 09b0a80313..26e46573dd 100644 --- a/framework/java/android/bluetooth/BluetoothDevicePicker.java +++ b/framework/java/android/bluetooth/BluetoothDevicePicker.java @@ -16,8 +16,10 @@ package android.bluetooth; +import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; +import android.bluetooth.annotations.RequiresBluetoothConnectPermission; /** * A helper to show a system "Device Picker" activity to the user. @@ -39,6 +41,8 @@ public interface BluetoothDevicePicker { * Selected {@link BluetoothDevice} is returned in extra data named * {@link BluetoothDevice#EXTRA_DEVICE}. */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_DEVICE_SELECTED = "android.bluetooth.devicepicker.action.DEVICE_SELECTED"; @@ -54,6 +58,8 @@ public interface BluetoothDevicePicker { * - {@link #EXTRA_LAUNCH_CLASS} (string): where(which class) this intent * come from */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_LAUNCH = "android.bluetooth.devicepicker.action.LAUNCH"; diff --git a/framework/java/android/bluetooth/BluetoothGatt.java b/framework/java/android/bluetooth/BluetoothGatt.java index 1d08ddb65c..2f771e9d3b 100644 --- a/framework/java/android/bluetooth/BluetoothGatt.java +++ b/framework/java/android/bluetooth/BluetoothGatt.java @@ -16,7 +16,13 @@ package android.bluetooth; +import android.annotation.RequiresNoPermission; +import android.annotation.RequiresPermission; +import android.annotation.SuppressLint; +import android.bluetooth.annotations.RequiresBluetoothConnectPermission; +import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; import android.compat.annotation.UnsupportedAppUsage; +import android.content.AttributionSource; import android.os.Build; import android.os.Handler; import android.os.ParcelUuid; @@ -64,6 +70,7 @@ public final class BluetoothGatt implements BluetoothProfile { private int mTransport; private int mPhy; private boolean mOpportunistic; + private final AttributionSource mAttributionSource; private static final int AUTH_RETRY_STATE_IDLE = 0; private static final int AUTH_RETRY_STATE_NO_MITM = 1; @@ -150,6 +157,7 @@ public final class BluetoothGatt implements BluetoothProfile { /** * Bluetooth GATT callbacks. Overrides the default BluetoothGattCallback implementation. */ + @SuppressLint("AndroidFrameworkBluetoothPermission") private final IBluetoothGattCallback mBluetoothGattCallback = new IBluetoothGattCallback.Stub() { /** @@ -157,6 +165,7 @@ public final class BluetoothGatt implements BluetoothProfile { * @hide */ @Override + @SuppressLint("AndroidFrameworkRequiresPermission") public void onClientRegistered(int status, int clientIf) { if (DBG) { Log.d(TAG, "onClientRegistered() - status=" + status @@ -191,7 +200,7 @@ public final class BluetoothGatt implements BluetoothProfile { try { mService.clientConnect(mClientIf, mDevice.getAddress(), !mAutoConnect, mTransport, mOpportunistic, - mPhy); // autoConnect is inverse of "isDirect" + mPhy, mAttributionSource); // autoConnect is inverse of "isDirect" } catch (RemoteException e) { Log.e(TAG, "", e); } @@ -347,6 +356,7 @@ public final class BluetoothGatt implements BluetoothProfile { * @hide */ @Override + @SuppressLint("AndroidFrameworkRequiresPermission") public void onCharacteristicRead(String address, int status, int handle, byte[] value) { if (VDBG) { @@ -368,7 +378,8 @@ public final class BluetoothGatt implements BluetoothProfile { try { final int authReq = (mAuthRetryState == AUTH_RETRY_STATE_IDLE) ? AUTHENTICATION_NO_MITM : AUTHENTICATION_MITM; - mService.readCharacteristic(mClientIf, address, handle, authReq); + mService.readCharacteristic( + mClientIf, address, handle, authReq, mAttributionSource); mAuthRetryState++; return; } catch (RemoteException e) { @@ -404,6 +415,7 @@ public final class BluetoothGatt implements BluetoothProfile { * @hide */ @Override + @SuppressLint("AndroidFrameworkRequiresPermission") public void onCharacteristicWrite(String address, int status, int handle) { if (VDBG) { Log.d(TAG, "onCharacteristicWrite() - Device=" + address @@ -430,7 +442,7 @@ public final class BluetoothGatt implements BluetoothProfile { ? AUTHENTICATION_NO_MITM : AUTHENTICATION_MITM; mService.writeCharacteristic(mClientIf, address, handle, characteristic.getWriteType(), authReq, - characteristic.getValue()); + characteristic.getValue(), mAttributionSource); mAuthRetryState++; return; } catch (RemoteException e) { @@ -487,6 +499,7 @@ public final class BluetoothGatt implements BluetoothProfile { * @hide */ @Override + @SuppressLint("AndroidFrameworkRequiresPermission") public void onDescriptorRead(String address, int status, int handle, byte[] value) { if (VDBG) { Log.d(TAG, @@ -511,7 +524,8 @@ public final class BluetoothGatt implements BluetoothProfile { try { final int authReq = (mAuthRetryState == AUTH_RETRY_STATE_IDLE) ? AUTHENTICATION_NO_MITM : AUTHENTICATION_MITM; - mService.readDescriptor(mClientIf, address, handle, authReq); + mService.readDescriptor( + mClientIf, address, handle, authReq, mAttributionSource); mAuthRetryState++; return; } catch (RemoteException e) { @@ -538,6 +552,7 @@ public final class BluetoothGatt implements BluetoothProfile { * @hide */ @Override + @SuppressLint("AndroidFrameworkRequiresPermission") public void onDescriptorWrite(String address, int status, int handle) { if (VDBG) { Log.d(TAG, @@ -562,7 +577,7 @@ public final class BluetoothGatt implements BluetoothProfile { final int authReq = (mAuthRetryState == AUTH_RETRY_STATE_IDLE) ? AUTHENTICATION_NO_MITM : AUTHENTICATION_MITM; mService.writeDescriptor(mClientIf, address, handle, - authReq, descriptor.getValue()); + authReq, descriptor.getValue(), mAttributionSource); mAuthRetryState++; return; } catch (RemoteException e) { @@ -715,13 +730,14 @@ public final class BluetoothGatt implements BluetoothProfile { } }; - /*package*/ BluetoothGatt(IBluetoothGatt iGatt, BluetoothDevice device, - int transport, boolean opportunistic, int phy) { + /* package */ BluetoothGatt(IBluetoothGatt iGatt, BluetoothDevice device, int transport, + boolean opportunistic, int phy, AttributionSource attributionSource) { mService = iGatt; mDevice = device; mTransport = transport; mPhy = phy; mOpportunistic = opportunistic; + mAttributionSource = attributionSource; mServices = new ArrayList<BluetoothGattService>(); mConnState = CONN_STATE_IDLE; @@ -734,6 +750,8 @@ public final class BluetoothGatt implements BluetoothProfile { * Application should call this method as early as possible after it is done with * this GATT client. */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void close() { if (DBG) Log.d(TAG, "close()"); @@ -817,12 +835,13 @@ public final class BluetoothGatt implements BluetoothProfile { * <p>This is an asynchronous call. The callback {@link BluetoothGattCallback#onAppRegistered} * is used to notify success or failure if the function returns true. * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. - * * @param callback GATT callback handler that will receive asynchronous callbacks. * @return If true, the callback will be called to notify success or failure, false on immediate * error */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) private boolean registerApp(BluetoothGattCallback callback, Handler handler) { return registerApp(callback, handler, false); } @@ -833,14 +852,15 @@ public final class BluetoothGatt implements BluetoothProfile { * <p>This is an asynchronous call. The callback {@link BluetoothGattCallback#onAppRegistered} * is used to notify success or failure if the function returns true. * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. - * * @param callback GATT callback handler that will receive asynchronous callbacks. * @param eatt_support indicate to allow for eatt support * @return If true, the callback will be called to notify success or failure, false on immediate * error * @hide */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) private boolean registerApp(BluetoothGattCallback callback, Handler handler, boolean eatt_support) { if (DBG) Log.d(TAG, "registerApp()"); @@ -852,7 +872,8 @@ public final class BluetoothGatt implements BluetoothProfile { if (DBG) Log.d(TAG, "registerApp() - UUID=" + uuid); try { - mService.registerClient(new ParcelUuid(uuid), mBluetoothGattCallback, eatt_support); + mService.registerClient( + new ParcelUuid(uuid), mBluetoothGattCallback, eatt_support, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); return false; @@ -865,13 +886,15 @@ public final class BluetoothGatt implements BluetoothProfile { * Unregister the current application and callbacks. */ @UnsupportedAppUsage + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) private void unregisterApp() { if (DBG) Log.d(TAG, "unregisterApp() - mClientIf=" + mClientIf); if (mService == null || mClientIf == 0) return; try { mCallback = null; - mService.unregisterClient(mClientIf); + mService.unregisterClient(mClientIf, mAttributionSource); mClientIf = 0; } catch (RemoteException e) { Log.e(TAG, "", e); @@ -893,14 +916,15 @@ public final class BluetoothGatt implements BluetoothProfile { * subsequent connections to known devices should be invoked with the * autoConnect parameter set to true. * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. - * * @param device Remote device to connect to * @param autoConnect Whether to directly connect to the remote device (false) or to * automatically connect as soon as the remote device becomes available (true). * @return true, if the connection attempt was initiated successfully */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) /*package*/ boolean connect(Boolean autoConnect, BluetoothGattCallback callback, Handler handler) { if (DBG) { @@ -931,15 +955,16 @@ public final class BluetoothGatt implements BluetoothProfile { /** * Disconnects an established connection, or cancels a connection attempt * currently in progress. - * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void disconnect() { if (DBG) Log.d(TAG, "cancelOpen() - device: " + mDevice.getAddress()); if (mService == null || mClientIf == 0) return; try { - mService.clientDisconnect(mClientIf, mDevice.getAddress()); + mService.clientDisconnect(mClientIf, mDevice.getAddress(), mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); } @@ -954,10 +979,13 @@ public final class BluetoothGatt implements BluetoothProfile { * * @return true, if the connection attempt was initiated successfully */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean connect() { try { + // autoConnect is inverse of "isDirect" mService.clientConnect(mClientIf, mDevice.getAddress(), false, mTransport, - mOpportunistic, mPhy); // autoConnect is inverse of "isDirect" + mOpportunistic, mPhy, mAttributionSource); return true; } catch (RemoteException e) { Log.e(TAG, "", e); @@ -983,10 +1011,12 @@ public final class BluetoothGatt implements BluetoothProfile { * of {@link BluetoothDevice#PHY_OPTION_NO_PREFERRED}, {@link BluetoothDevice#PHY_OPTION_S2} or * {@link BluetoothDevice#PHY_OPTION_S8} */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void setPreferredPhy(int txPhy, int rxPhy, int phyOptions) { try { mService.clientSetPreferredPhy(mClientIf, mDevice.getAddress(), txPhy, rxPhy, - phyOptions); + phyOptions, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); } @@ -996,9 +1026,11 @@ public final class BluetoothGatt implements BluetoothProfile { * Read the current transmitter PHY and receiver PHY of the connection. The values are returned * in {@link BluetoothGattCallback#onPhyRead} */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void readPhy() { try { - mService.clientReadPhy(mClientIf, mDevice.getAddress()); + mService.clientReadPhy(mClientIf, mDevice.getAddress(), mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); } @@ -1009,6 +1041,7 @@ public final class BluetoothGatt implements BluetoothProfile { * * @return remote bluetooth device */ + @RequiresNoPermission public BluetoothDevice getDevice() { return mDevice; } @@ -1022,10 +1055,11 @@ public final class BluetoothGatt implements BluetoothProfile { * triggered. If the discovery was successful, the remote services can be * retrieved using the {@link #getServices} function. * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. - * * @return true, if the remote service discovery has been started */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean discoverServices() { if (DBG) Log.d(TAG, "discoverServices() - device: " + mDevice.getAddress()); if (mService == null || mClientIf == 0) return false; @@ -1033,7 +1067,7 @@ public final class BluetoothGatt implements BluetoothProfile { mServices.clear(); try { - mService.discoverServices(mClientIf, mDevice.getAddress()); + mService.discoverServices(mClientIf, mDevice.getAddress(), mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); return false; @@ -1047,11 +1081,12 @@ public final class BluetoothGatt implements BluetoothProfile { * It should never be used by real applications. The service is not searched * for characteristics and descriptors, or returned in any callback. * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. - * * @return true, if the remote service discovery has been started * @hide */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean discoverServiceByUuid(UUID uuid) { if (DBG) Log.d(TAG, "discoverServiceByUuid() - device: " + mDevice.getAddress()); if (mService == null || mClientIf == 0) return false; @@ -1059,7 +1094,8 @@ public final class BluetoothGatt implements BluetoothProfile { mServices.clear(); try { - mService.discoverServiceByUuid(mClientIf, mDevice.getAddress(), new ParcelUuid(uuid)); + mService.discoverServiceByUuid( + mClientIf, mDevice.getAddress(), new ParcelUuid(uuid), mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); return false; @@ -1073,11 +1109,11 @@ public final class BluetoothGatt implements BluetoothProfile { * <p>This function requires that service discovery has been completed * for the given device. * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. - * * @return List of services on the remote device. Returns an empty list if service discovery has * not yet been performed. */ + @RequiresLegacyBluetoothPermission + @RequiresNoPermission public List<BluetoothGattService> getServices() { List<BluetoothGattService> result = new ArrayList<BluetoothGattService>(); @@ -1101,12 +1137,12 @@ public final class BluetoothGatt implements BluetoothProfile { * <p>If multiple instances of the same service (as identified by UUID) * exist, the first instance of the service is returned. * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. - * * @param uuid UUID of the requested service * @return BluetoothGattService if supported, or null if the requested service is not offered by * the remote device. */ + @RequiresLegacyBluetoothPermission + @RequiresNoPermission public BluetoothGattService getService(UUID uuid) { for (BluetoothGattService service : mServices) { if (service.getDevice().equals(mDevice) && service.getUuid().equals(uuid)) { @@ -1124,11 +1160,12 @@ public final class BluetoothGatt implements BluetoothProfile { * is reported by the {@link BluetoothGattCallback#onCharacteristicRead} * callback. * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. - * * @param characteristic Characteristic to read from the remote device * @return true, if the read operation was initiated successfully */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean readCharacteristic(BluetoothGattCharacteristic characteristic) { if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_READ) == 0) { return false; @@ -1150,7 +1187,7 @@ public final class BluetoothGatt implements BluetoothProfile { try { mService.readCharacteristic(mClientIf, device.getAddress(), - characteristic.getInstanceId(), AUTHENTICATION_NONE); + characteristic.getInstanceId(), AUTHENTICATION_NONE, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); synchronized (mDeviceBusyLock) { @@ -1169,12 +1206,13 @@ public final class BluetoothGatt implements BluetoothProfile { * is reported by the {@link BluetoothGattCallback#onCharacteristicRead} * callback. * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. - * * @param uuid UUID of characteristic to read from the remote device * @return true, if the read operation was initiated successfully * @hide */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean readUsingCharacteristicUuid(UUID uuid, int startHandle, int endHandle) { if (VDBG) Log.d(TAG, "readUsingCharacteristicUuid() - uuid: " + uuid); if (mService == null || mClientIf == 0) return false; @@ -1186,7 +1224,8 @@ public final class BluetoothGatt implements BluetoothProfile { try { mService.readUsingCharacteristicUuid(mClientIf, mDevice.getAddress(), - new ParcelUuid(uuid), startHandle, endHandle, AUTHENTICATION_NONE); + new ParcelUuid(uuid), startHandle, endHandle, AUTHENTICATION_NONE, + mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); synchronized (mDeviceBusyLock) { @@ -1206,11 +1245,12 @@ public final class BluetoothGatt implements BluetoothProfile { * {@link BluetoothGattCallback#onCharacteristicWrite} callback is invoked, * reporting the result of the operation. * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. - * * @param characteristic Characteristic to write on the remote device * @return true, if the write operation was initiated successfully */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean writeCharacteristic(BluetoothGattCharacteristic characteristic) { if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_WRITE) == 0 && (characteristic.getProperties() @@ -1235,7 +1275,7 @@ public final class BluetoothGatt implements BluetoothProfile { try { mService.writeCharacteristic(mClientIf, device.getAddress(), characteristic.getInstanceId(), characteristic.getWriteType(), - AUTHENTICATION_NONE, characteristic.getValue()); + AUTHENTICATION_NONE, characteristic.getValue(), mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); synchronized (mDeviceBusyLock) { @@ -1254,11 +1294,12 @@ public final class BluetoothGatt implements BluetoothProfile { * {@link BluetoothGattCallback#onDescriptorRead} callback is * triggered, signaling the result of the operation. * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. - * * @param descriptor Descriptor value to read from the remote device * @return true, if the read operation was initiated successfully */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean readDescriptor(BluetoothGattDescriptor descriptor) { if (VDBG) Log.d(TAG, "readDescriptor() - uuid: " + descriptor.getUuid()); if (mService == null || mClientIf == 0) return false; @@ -1279,7 +1320,7 @@ public final class BluetoothGatt implements BluetoothProfile { try { mService.readDescriptor(mClientIf, device.getAddress(), - descriptor.getInstanceId(), AUTHENTICATION_NONE); + descriptor.getInstanceId(), AUTHENTICATION_NONE, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); synchronized (mDeviceBusyLock) { @@ -1297,11 +1338,12 @@ public final class BluetoothGatt implements BluetoothProfile { * <p>A {@link BluetoothGattCallback#onDescriptorWrite} callback is * triggered to report the result of the write operation. * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. - * * @param descriptor Descriptor to write to the associated remote device * @return true, if the write operation was initiated successfully */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean writeDescriptor(BluetoothGattDescriptor descriptor) { if (VDBG) Log.d(TAG, "writeDescriptor() - uuid: " + descriptor.getUuid()); if (mService == null || mClientIf == 0 || descriptor.getValue() == null) return false; @@ -1322,7 +1364,7 @@ public final class BluetoothGatt implements BluetoothProfile { try { mService.writeDescriptor(mClientIf, device.getAddress(), descriptor.getInstanceId(), - AUTHENTICATION_NONE, descriptor.getValue()); + AUTHENTICATION_NONE, descriptor.getValue(), mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); synchronized (mDeviceBusyLock) { @@ -1350,16 +1392,17 @@ public final class BluetoothGatt implements BluetoothProfile { * cancel the current transaction without committing any values on the * remote device. * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. - * * @return true, if the reliable write transaction has been initiated */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean beginReliableWrite() { if (VDBG) Log.d(TAG, "beginReliableWrite() - device: " + mDevice.getAddress()); if (mService == null || mClientIf == 0) return false; try { - mService.beginReliableWrite(mClientIf, mDevice.getAddress()); + mService.beginReliableWrite(mClientIf, mDevice.getAddress(), mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); return false; @@ -1377,10 +1420,11 @@ public final class BluetoothGatt implements BluetoothProfile { * <p>A {@link BluetoothGattCallback#onReliableWriteCompleted} callback is * invoked to indicate whether the transaction has been executed correctly. * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. - * * @return true, if the request to execute the transaction has been sent */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean executeReliableWrite() { if (VDBG) Log.d(TAG, "executeReliableWrite() - device: " + mDevice.getAddress()); if (mService == null || mClientIf == 0) return false; @@ -1391,7 +1435,7 @@ public final class BluetoothGatt implements BluetoothProfile { } try { - mService.endReliableWrite(mClientIf, mDevice.getAddress(), true); + mService.endReliableWrite(mClientIf, mDevice.getAddress(), true, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); synchronized (mDeviceBusyLock) { @@ -1408,15 +1452,16 @@ public final class BluetoothGatt implements BluetoothProfile { * * <p>Calling this function will discard all queued characteristic write * operations for a given remote device. - * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void abortReliableWrite() { if (VDBG) Log.d(TAG, "abortReliableWrite() - device: " + mDevice.getAddress()); if (mService == null || mClientIf == 0) return; try { - mService.endReliableWrite(mClientIf, mDevice.getAddress(), false); + mService.endReliableWrite(mClientIf, mDevice.getAddress(), false, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); } @@ -1426,6 +1471,8 @@ public final class BluetoothGatt implements BluetoothProfile { * @deprecated Use {@link #abortReliableWrite()} */ @Deprecated + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void abortReliableWrite(BluetoothDevice mDevice) { abortReliableWrite(); } @@ -1438,12 +1485,13 @@ public final class BluetoothGatt implements BluetoothProfile { * triggered if the remote device indicates that the given characteristic * has changed. * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. - * * @param characteristic The characteristic for which to enable notifications * @param enable Set to true to enable notifications/indications * @return true, if the requested notification status was set successfully */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean setCharacteristicNotification(BluetoothGattCharacteristic characteristic, boolean enable) { if (DBG) { @@ -1460,7 +1508,7 @@ public final class BluetoothGatt implements BluetoothProfile { try { mService.registerForNotification(mClientIf, device.getAddress(), - characteristic.getInstanceId(), enable); + characteristic.getInstanceId(), enable, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); return false; @@ -1476,12 +1524,14 @@ public final class BluetoothGatt implements BluetoothProfile { * @hide */ @UnsupportedAppUsage + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean refresh() { if (DBG) Log.d(TAG, "refresh() - device: " + mDevice.getAddress()); if (mService == null || mClientIf == 0) return false; try { - mService.refreshDevice(mClientIf, mDevice.getAddress()); + mService.refreshDevice(mClientIf, mDevice.getAddress(), mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); return false; @@ -1496,16 +1546,17 @@ public final class BluetoothGatt implements BluetoothProfile { * <p>The {@link BluetoothGattCallback#onReadRemoteRssi} callback will be * invoked when the RSSI value has been read. * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. - * * @return true, if the RSSI value has been requested successfully */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean readRemoteRssi() { if (DBG) Log.d(TAG, "readRssi() - device: " + mDevice.getAddress()); if (mService == null || mClientIf == 0) return false; try { - mService.readRemoteRssi(mClientIf, mDevice.getAddress()); + mService.readRemoteRssi(mClientIf, mDevice.getAddress(), mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); return false; @@ -1524,10 +1575,11 @@ public final class BluetoothGatt implements BluetoothProfile { * <p>A {@link BluetoothGattCallback#onMtuChanged} callback will indicate * whether this operation was successful. * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. - * * @return true, if the new MTU value has been requested successfully */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean requestMtu(int mtu) { if (DBG) { Log.d(TAG, "configureMTU() - device: " + mDevice.getAddress() @@ -1536,7 +1588,7 @@ public final class BluetoothGatt implements BluetoothProfile { if (mService == null || mClientIf == 0) return false; try { - mService.configureMTU(mClientIf, mDevice.getAddress(), mtu); + mService.configureMTU(mClientIf, mDevice.getAddress(), mtu, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); return false; @@ -1556,6 +1608,8 @@ public final class BluetoothGatt implements BluetoothProfile { * or {@link BluetoothGatt#CONNECTION_PRIORITY_LOW_POWER}. * @throws IllegalArgumentException If the parameters are outside of their specified range. */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean requestConnectionPriority(int connectionPriority) { if (connectionPriority < CONNECTION_PRIORITY_BALANCED || connectionPriority > CONNECTION_PRIORITY_LOW_POWER) { @@ -1566,7 +1620,8 @@ public final class BluetoothGatt implements BluetoothProfile { if (mService == null || mClientIf == 0) return false; try { - mService.connectionParameterUpdate(mClientIf, mDevice.getAddress(), connectionPriority); + mService.connectionParameterUpdate( + mClientIf, mDevice.getAddress(), connectionPriority, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); return false; @@ -1583,6 +1638,8 @@ public final class BluetoothGatt implements BluetoothProfile { * @return true, if the request is send to the Bluetooth stack. * @hide */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean requestLeConnectionUpdate(int minConnectionInterval, int maxConnectionInterval, int slaveLatency, int supervisionTimeout, int minConnectionEventLen, int maxConnectionEventLen) { @@ -1598,9 +1655,10 @@ public final class BluetoothGatt implements BluetoothProfile { try { mService.leConnectionUpdate(mClientIf, mDevice.getAddress(), - minConnectionInterval, maxConnectionInterval, - slaveLatency, supervisionTimeout, - minConnectionEventLen, maxConnectionEventLen); + minConnectionInterval, maxConnectionInterval, + slaveLatency, supervisionTimeout, + minConnectionEventLen, maxConnectionEventLen, + mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); return false; @@ -1616,6 +1674,7 @@ public final class BluetoothGatt implements BluetoothProfile { * @throws UnsupportedOperationException */ @Override + @RequiresNoPermission public int getConnectionState(BluetoothDevice device) { throw new UnsupportedOperationException("Use BluetoothManager#getConnectionState instead."); } @@ -1627,6 +1686,7 @@ public final class BluetoothGatt implements BluetoothProfile { * @throws UnsupportedOperationException */ @Override + @RequiresNoPermission public List<BluetoothDevice> getConnectedDevices() { throw new UnsupportedOperationException( "Use BluetoothManager#getConnectedDevices instead."); @@ -1640,6 +1700,7 @@ public final class BluetoothGatt implements BluetoothProfile { * @throws UnsupportedOperationException */ @Override + @RequiresNoPermission public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { throw new UnsupportedOperationException( "Use BluetoothManager#getDevicesMatchingConnectionStates instead."); diff --git a/framework/java/android/bluetooth/BluetoothGattCharacteristic.java b/framework/java/android/bluetooth/BluetoothGattCharacteristic.java index 8f1b59cf69..8a7d4baf5a 100644 --- a/framework/java/android/bluetooth/BluetoothGattCharacteristic.java +++ b/framework/java/android/bluetooth/BluetoothGattCharacteristic.java @@ -237,7 +237,6 @@ public class BluetoothGattCharacteristic implements Parcelable { /** * Create a new BluetoothGattCharacteristic. - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. * * @param uuid The UUID for this characteristic * @param properties Properties of this characteristic @@ -344,7 +343,6 @@ public class BluetoothGattCharacteristic implements Parcelable { /** * Adds a descriptor to this characteristic. - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. * * @param descriptor Descriptor to be added to this characteristic. * @return true, if the descriptor was added to the characteristic diff --git a/framework/java/android/bluetooth/BluetoothGattDescriptor.java b/framework/java/android/bluetooth/BluetoothGattDescriptor.java index 49ba281e2e..ed5ea08730 100644 --- a/framework/java/android/bluetooth/BluetoothGattDescriptor.java +++ b/framework/java/android/bluetooth/BluetoothGattDescriptor.java @@ -128,7 +128,6 @@ public class BluetoothGattDescriptor implements Parcelable { /** * Create a new BluetoothGattDescriptor. - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. * * @param uuid The UUID for this descriptor * @param permissions Permissions for this descriptor @@ -139,7 +138,6 @@ public class BluetoothGattDescriptor implements Parcelable { /** * Create a new BluetoothGattDescriptor. - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. * * @param characteristic The characteristic this descriptor belongs to * @param uuid The UUID for this descriptor @@ -228,8 +226,6 @@ public class BluetoothGattDescriptor implements Parcelable { * <p>If a remote device offers multiple descriptors with the same UUID, * the instance ID is used to distuinguish between descriptors. * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. - * * @return Instance ID of this descriptor * @hide */ diff --git a/framework/java/android/bluetooth/BluetoothGattServer.java b/framework/java/android/bluetooth/BluetoothGattServer.java index 088b0169b6..3e799defa5 100644 --- a/framework/java/android/bluetooth/BluetoothGattServer.java +++ b/framework/java/android/bluetooth/BluetoothGattServer.java @@ -16,6 +16,12 @@ package android.bluetooth; +import android.annotation.RequiresNoPermission; +import android.annotation.RequiresPermission; +import android.annotation.SuppressLint; +import android.bluetooth.annotations.RequiresBluetoothConnectPermission; +import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; +import android.content.AttributionSource; import android.os.ParcelUuid; import android.os.RemoteException; import android.util.Log; @@ -40,8 +46,10 @@ public final class BluetoothGattServer implements BluetoothProfile { private static final boolean DBG = true; private static final boolean VDBG = false; - private BluetoothAdapter mAdapter; - private IBluetoothGatt mService; + private final IBluetoothGatt mService; + private final BluetoothAdapter mAdapter; + private final AttributionSource mAttributionSource; + private BluetoothGattServerCallback mCallback; private Object mServerIfLock = new Object(); @@ -55,6 +63,7 @@ public final class BluetoothGattServer implements BluetoothProfile { /** * Bluetooth GATT interface callbacks */ + @SuppressLint("AndroidFrameworkBluetoothPermission") private final IBluetoothGattServerCallback mBluetoothGattServerCallback = new IBluetoothGattServerCallback.Stub() { /** @@ -376,9 +385,11 @@ public final class BluetoothGattServer implements BluetoothProfile { /** * Create a BluetoothGattServer proxy object. */ - /*package*/ BluetoothGattServer(IBluetoothGatt iGatt, int transport) { + /* package */ BluetoothGattServer(IBluetoothGatt iGatt, int transport, + BluetoothAdapter adapter) { mService = iGatt; - mAdapter = BluetoothAdapter.getDefaultAdapter(); + mAdapter = adapter; + mAttributionSource = adapter.getAttributionSource(); mCallback = null; mServerIf = 0; mTransport = transport; @@ -425,6 +436,8 @@ public final class BluetoothGattServer implements BluetoothProfile { * Application should call this method as early as possible after it is done with * this GATT server. */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void close() { if (DBG) Log.d(TAG, "close()"); unregisterCallback(); @@ -436,12 +449,13 @@ public final class BluetoothGattServer implements BluetoothProfile { * <p>This is an asynchronous call. The callback is used to notify * success or failure if the function returns true. * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. - * * @param callback GATT callback handler that will receive asynchronous callbacks. * @return true, the callback will be called to notify success or failure, false on immediate * error */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) /*package*/ boolean registerCallback(BluetoothGattServerCallback callback) { return registerCallback(callback, false); } @@ -452,14 +466,15 @@ public final class BluetoothGattServer implements BluetoothProfile { * <p>This is an asynchronous call. The callback is used to notify * success or failure if the function returns true. * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. - * * @param callback GATT callback handler that will receive asynchronous callbacks. * @param eatt_support indicates if server can use eatt * @return true, the callback will be called to notify success or failure, false on immediate * error * @hide */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) /*package*/ boolean registerCallback(BluetoothGattServerCallback callback, boolean eatt_support) { if (DBG) Log.d(TAG, "registerCallback()"); @@ -478,7 +493,8 @@ public final class BluetoothGattServer implements BluetoothProfile { mCallback = callback; try { - mService.registerServer(new ParcelUuid(uuid), mBluetoothGattServerCallback, eatt_support); + mService.registerServer(new ParcelUuid(uuid), mBluetoothGattServerCallback, + eatt_support, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); mCallback = null; @@ -504,13 +520,15 @@ public final class BluetoothGattServer implements BluetoothProfile { /** * Unregister the current application and callbacks. */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) private void unregisterCallback() { if (DBG) Log.d(TAG, "unregisterCallback() - mServerIf=" + mServerIf); if (mService == null || mServerIf == 0) return; try { mCallback = null; - mService.unregisterServer(mServerIf); + mService.unregisterServer(mServerIf, mAttributionSource); mServerIf = 0; } catch (RemoteException e) { Log.e(TAG, "", e); @@ -548,12 +566,13 @@ public final class BluetoothGattServer implements BluetoothProfile { * subsequent connections to known devices should be invoked with the * autoConnect parameter set to true. * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. - * * @param autoConnect Whether to directly connect to the remote device (false) or to * automatically connect as soon as the remote device becomes available (true). * @return true, if the connection attempt was initiated successfully */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean connect(BluetoothDevice device, boolean autoConnect) { if (DBG) { Log.d(TAG, @@ -563,7 +582,8 @@ public final class BluetoothGattServer implements BluetoothProfile { try { // autoConnect is inverse of "isDirect" - mService.serverConnect(mServerIf, device.getAddress(), !autoConnect, mTransport); + mService.serverConnect( + mServerIf, device.getAddress(), !autoConnect, mTransport, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); return false; @@ -576,16 +596,17 @@ public final class BluetoothGattServer implements BluetoothProfile { * Disconnects an established connection, or cancels a connection attempt * currently in progress. * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. - * * @param device Remote device */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void cancelConnection(BluetoothDevice device) { if (DBG) Log.d(TAG, "cancelConnection() - device: " + device.getAddress()); if (mService == null || mServerIf == 0) return; try { - mService.serverDisconnect(mServerIf, device.getAddress()); + mService.serverDisconnect(mServerIf, device.getAddress(), mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); } @@ -609,10 +630,12 @@ public final class BluetoothGattServer implements BluetoothProfile { * of {@link BluetoothDevice#PHY_OPTION_NO_PREFERRED}, {@link BluetoothDevice#PHY_OPTION_S2} or * {@link BluetoothDevice#PHY_OPTION_S8} */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void setPreferredPhy(BluetoothDevice device, int txPhy, int rxPhy, int phyOptions) { try { mService.serverSetPreferredPhy(mServerIf, device.getAddress(), txPhy, rxPhy, - phyOptions); + phyOptions, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); } @@ -624,9 +647,11 @@ public final class BluetoothGattServer implements BluetoothProfile { * * @param device The remote device to send this response to */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void readPhy(BluetoothDevice device) { try { - mService.serverReadPhy(mServerIf, device.getAddress()); + mService.serverReadPhy(mServerIf, device.getAddress(), mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); } @@ -645,14 +670,15 @@ public final class BluetoothGattServer implements BluetoothProfile { * <li>{@link BluetoothGattServerCallback#onDescriptorWriteRequest} * </ul> * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. - * * @param device The remote device to send this response to * @param requestId The ID of the request that was received with the callback * @param status The status of the request to be sent to the remote devices * @param offset Value offset for partial read/write response * @param value The value of the attribute that was read/written (optional) */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean sendResponse(BluetoothDevice device, int requestId, int status, int offset, byte[] value) { if (VDBG) Log.d(TAG, "sendResponse() - device: " + device.getAddress()); @@ -660,7 +686,7 @@ public final class BluetoothGattServer implements BluetoothProfile { try { mService.sendResponse(mServerIf, device.getAddress(), requestId, - status, offset, value); + status, offset, value, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); return false; @@ -677,8 +703,6 @@ public final class BluetoothGattServer implements BluetoothProfile { * for every client that requests notifications/indications by writing * to the "Client Configuration" descriptor for the given characteristic. * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. - * * @param device The remote device to receive the notification/indication * @param characteristic The local characteristic that has been updated * @param confirm true to request confirmation from the client (indication), false to send a @@ -686,6 +710,9 @@ public final class BluetoothGattServer implements BluetoothProfile { * @return true, if the notification has been triggered successfully * @throws IllegalArgumentException */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean notifyCharacteristicChanged(BluetoothDevice device, BluetoothGattCharacteristic characteristic, boolean confirm) { if (VDBG) Log.d(TAG, "notifyCharacteristicChanged() - device: " + device.getAddress()); @@ -702,7 +729,7 @@ public final class BluetoothGattServer implements BluetoothProfile { try { mService.sendNotification(mServerIf, device.getAddress(), characteristic.getInstanceId(), confirm, - characteristic.getValue()); + characteristic.getValue(), mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); return false; @@ -724,11 +751,12 @@ public final class BluetoothGattServer implements BluetoothProfile { * whether this service has been added successfully. Do not add another service * before this callback. * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. - * * @param service Service to be added to the list of services provided by this device. * @return true, if the request to add service has been initiated */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean addService(BluetoothGattService service) { if (DBG) Log.d(TAG, "addService() - service: " + service.getUuid()); if (mService == null || mServerIf == 0) return false; @@ -736,7 +764,7 @@ public final class BluetoothGattServer implements BluetoothProfile { mPendingService = service; try { - mService.addService(mServerIf, service); + mService.addService(mServerIf, service, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); return false; @@ -748,11 +776,12 @@ public final class BluetoothGattServer implements BluetoothProfile { /** * Removes a service from the list of services to be provided. * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. - * * @param service Service to be removed. * @return true, if the service has been removed */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean removeService(BluetoothGattService service) { if (DBG) Log.d(TAG, "removeService() - service: " + service.getUuid()); if (mService == null || mServerIf == 0) return false; @@ -762,7 +791,7 @@ public final class BluetoothGattServer implements BluetoothProfile { if (intService == null) return false; try { - mService.removeService(mServerIf, service.getInstanceId()); + mService.removeService(mServerIf, service.getInstanceId(), mAttributionSource); mServices.remove(intService); } catch (RemoteException e) { Log.e(TAG, "", e); @@ -774,14 +803,16 @@ public final class BluetoothGattServer implements BluetoothProfile { /** * Remove all services from the list of provided services. - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void clearServices() { if (DBG) Log.d(TAG, "clearServices()"); if (mService == null || mServerIf == 0) return; try { - mService.clearServices(mServerIf); + mService.clearServices(mServerIf, mAttributionSource); mServices.clear(); } catch (RemoteException e) { Log.e(TAG, "", e); @@ -794,10 +825,10 @@ public final class BluetoothGattServer implements BluetoothProfile { * <p>An application must call {@link #addService} to add a serice to the * list of services offered by this device. * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. - * * @return List of services. Returns an empty list if no services have been added yet. */ + @RequiresLegacyBluetoothPermission + @RequiresNoPermission public List<BluetoothGattService> getServices() { return mServices; } @@ -809,12 +840,12 @@ public final class BluetoothGattServer implements BluetoothProfile { * <p>If multiple instances of the same service (as identified by UUID) * exist, the first instance of the service is returned. * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. - * * @param uuid UUID of the requested service * @return BluetoothGattService if supported, or null if the requested service is not offered by * this device. */ + @RequiresLegacyBluetoothPermission + @RequiresNoPermission public BluetoothGattService getService(UUID uuid) { for (BluetoothGattService service : mServices) { if (service.getUuid().equals(uuid)) { @@ -833,6 +864,7 @@ public final class BluetoothGattServer implements BluetoothProfile { * @throws UnsupportedOperationException */ @Override + @RequiresNoPermission public int getConnectionState(BluetoothDevice device) { throw new UnsupportedOperationException("Use BluetoothManager#getConnectionState instead."); } @@ -844,6 +876,7 @@ public final class BluetoothGattServer implements BluetoothProfile { * @throws UnsupportedOperationException */ @Override + @RequiresNoPermission public List<BluetoothDevice> getConnectedDevices() { throw new UnsupportedOperationException( "Use BluetoothManager#getConnectedDevices instead."); @@ -857,6 +890,7 @@ public final class BluetoothGattServer implements BluetoothProfile { * @throws UnsupportedOperationException */ @Override + @RequiresNoPermission public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { throw new UnsupportedOperationException( "Use BluetoothManager#getDevicesMatchingConnectionStates instead."); diff --git a/framework/java/android/bluetooth/BluetoothGattService.java b/framework/java/android/bluetooth/BluetoothGattService.java index 23dc7c8308..f64d09fc30 100644 --- a/framework/java/android/bluetooth/BluetoothGattService.java +++ b/framework/java/android/bluetooth/BluetoothGattService.java @@ -15,6 +15,9 @@ */ package android.bluetooth; +import android.annotation.RequiresPermission; +import android.bluetooth.annotations.RequiresBluetoothConnectPermission; +import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; import android.os.Parcel; @@ -98,7 +101,6 @@ public class BluetoothGattService implements Parcelable { /** * Create a new BluetoothGattService. - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. * * @param uuid The UUID for this service * @param serviceType The type of this service, @@ -225,11 +227,11 @@ public class BluetoothGattService implements Parcelable { /** * Add an included service to this service. - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. * * @param service The service to be added * @return true, if the included service was added to the service */ + @RequiresLegacyBluetoothPermission public boolean addService(BluetoothGattService service) { mIncludedServices.add(service); return true; @@ -237,11 +239,11 @@ public class BluetoothGattService implements Parcelable { /** * Add a characteristic to this service. - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. * * @param characteristic The characteristics to be added * @return true, if the characteristic was added to the service */ + @RequiresLegacyBluetoothPermission public boolean addCharacteristic(BluetoothGattCharacteristic characteristic) { mCharacteristics.add(characteristic); characteristic.setService(this); diff --git a/framework/java/android/bluetooth/BluetoothHeadset.java b/framework/java/android/bluetooth/BluetoothHeadset.java index 632572dea3..b594ae3443 100644 --- a/framework/java/android/bluetooth/BluetoothHeadset.java +++ b/framework/java/android/bluetooth/BluetoothHeadset.java @@ -21,11 +21,18 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SdkConstant; +import android.annotation.SuppressLint; import android.annotation.SdkConstant.SdkConstantType; +import android.bluetooth.annotations.RequiresBluetoothConnectPermission; +import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission; +import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; +import android.content.Attributable; +import android.content.AttributionSource; import android.content.ComponentName; import android.content.Context; +import android.content.pm.PackageManager; import android.os.Binder; import android.os.Build; import android.os.Handler; @@ -33,6 +40,7 @@ import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.RemoteException; +import android.util.CloseGuard; import android.util.Log; import java.util.ArrayList; @@ -70,10 +78,10 @@ public final class BluetoothHeadset implements BluetoothProfile { * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}. - * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to - * receive. */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.headset.profile.action.CONNECTION_STATE_CHANGED"; @@ -90,10 +98,10 @@ public final class BluetoothHeadset implements BluetoothProfile { * </ul> * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of * {@link #STATE_AUDIO_CONNECTED}, {@link #STATE_AUDIO_DISCONNECTED}, - * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission - * to receive. */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_AUDIO_STATE_CHANGED = "android.bluetooth.headset.profile.action.AUDIO_STATE_CHANGED"; @@ -107,11 +115,11 @@ public final class BluetoothHeadset implements BluetoothProfile { * be null if no device is active. </li> * </ul> * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to - * receive. - * * @hide */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) @UnsupportedAppUsage(trackingBug = 171933273) public static final String ACTION_ACTIVE_DEVICE_CHANGED = @@ -147,9 +155,10 @@ public final class BluetoothHeadset implements BluetoothProfile { * <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE = AT_CMD_TYPE_SET </li> * <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS = foo, 3 </li> * </ul> - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission - * to receive. */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_VENDOR_SPECIFIC_HEADSET_EVENT = "android.bluetooth.headset.action.VENDOR_SPECIFIC_HEADSET_EVENT"; @@ -299,10 +308,13 @@ public final class BluetoothHeadset implements BluetoothProfile { * are given an assigned number. Below shows the assigned number of Indicator added so far * - Enhanced Safety - 1, Valid Values: 0 - Disabled, 1 - Enabled * - Battery Level - 2, Valid Values: 0~100 - Remaining level of Battery - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to receive. * * @hide */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_HF_INDICATORS_VALUE_CHANGED = "android.bluetooth.headset.action.HF_INDICATORS_VALUE_CHANGED"; @@ -328,11 +340,15 @@ public final class BluetoothHeadset implements BluetoothProfile { private static final int MESSAGE_HEADSET_SERVICE_CONNECTED = 100; private static final int MESSAGE_HEADSET_SERVICE_DISCONNECTED = 101; + private final CloseGuard mCloseGuard = new CloseGuard(); + private Context mContext; private ServiceListener mServiceListener; private volatile IBluetoothHeadset mService; - private BluetoothAdapter mAdapter; + private final BluetoothAdapter mAdapter; + private final AttributionSource mAttributionSource; + @SuppressLint("AndroidFrameworkBluetoothPermission") private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback = new IBluetoothStateChangeCallback.Stub() { public void onBluetoothStateChange(boolean up) { @@ -348,10 +364,20 @@ public final class BluetoothHeadset implements BluetoothProfile { /** * Create a BluetoothHeadset proxy object. */ - /*package*/ BluetoothHeadset(Context context, ServiceListener l) { + /* package */ BluetoothHeadset(Context context, ServiceListener l, BluetoothAdapter adapter) { mContext = context; mServiceListener = l; - mAdapter = BluetoothAdapter.getDefaultAdapter(); + mAdapter = adapter; + mAttributionSource = adapter.getAttributionSource(); + + // Preserve legacy compatibility where apps were depending on + // registerStateChangeCallback() performing a permissions check which + // has been relaxed in modern platform versions + if (context.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.R + && context.checkSelfPermission(android.Manifest.permission.BLUETOOTH) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Need BLUETOOTH permission"); + } IBluetoothManager mgr = mAdapter.getBluetoothManager(); if (mgr != null) { @@ -363,6 +389,7 @@ public final class BluetoothHeadset implements BluetoothProfile { } doBind(); + mCloseGuard.open("close"); } private boolean doBind() { @@ -416,6 +443,14 @@ public final class BluetoothHeadset implements BluetoothProfile { } mServiceListener = null; doUnbind(); + mCloseGuard.close(); + } + + /** {@hide} */ + @Override + protected void finalize() throws Throwable { + mCloseGuard.warnIfOpen(); + close(); } /** @@ -432,15 +467,17 @@ public final class BluetoothHeadset implements BluetoothProfile { * the state. Users can get the connection state of the profile * from this intent. * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} - * permission. - * * @param device Remote Bluetooth Device * @return false on immediate error, true otherwise * @hide */ @SystemApi - @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.MODIFY_PHONE_STATE, + }) public boolean connect(BluetoothDevice device) { if (DBG) log("connect(" + device + ")"); final IBluetoothHeadset service = mService; @@ -474,15 +511,14 @@ public final class BluetoothHeadset implements BluetoothProfile { * {@link #STATE_DISCONNECTING} can be used to distinguish between the * two scenarios. * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} - * permission. - * * @param device Remote Bluetooth Device * @return false on immediate error, true otherwise * @hide */ @SystemApi - @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean disconnect(BluetoothDevice device) { if (DBG) log("disconnect(" + device + ")"); final IBluetoothHeadset service = mService; @@ -502,12 +538,16 @@ public final class BluetoothHeadset implements BluetoothProfile { * {@inheritDoc} */ @Override + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public List<BluetoothDevice> getConnectedDevices() { if (VDBG) log("getConnectedDevices()"); final IBluetoothHeadset service = mService; if (service != null && isEnabled()) { try { - return service.getConnectedDevices(); + return Attributable.setAttributionSource( + service.getConnectedDevicesWithAttribution(mAttributionSource), + mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return new ArrayList<BluetoothDevice>(); @@ -521,12 +561,16 @@ public final class BluetoothHeadset implements BluetoothProfile { * {@inheritDoc} */ @Override + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { if (VDBG) log("getDevicesMatchingStates()"); final IBluetoothHeadset service = mService; if (service != null && isEnabled()) { try { - return service.getDevicesMatchingConnectionStates(states); + return Attributable.setAttributionSource( + service.getDevicesMatchingConnectionStates(states, mAttributionSource), + mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return new ArrayList<BluetoothDevice>(); @@ -540,6 +584,8 @@ public final class BluetoothHeadset implements BluetoothProfile { * {@inheritDoc} */ @Override + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getConnectionState(BluetoothDevice device) { if (VDBG) log("getConnectionState(" + device + ")"); final IBluetoothHeadset service = mService; @@ -571,7 +617,12 @@ public final class BluetoothHeadset implements BluetoothProfile { */ @Deprecated @SystemApi - @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.MODIFY_PHONE_STATE, + }) public boolean setPriority(BluetoothDevice device, int priority) { if (DBG) log("setPriority(" + device + ", " + priority + ")"); final IBluetoothHeadset service = mService; @@ -582,7 +633,8 @@ public final class BluetoothHeadset implements BluetoothProfile { } try { return service.setPriority( - device, BluetoothAdapter.priorityToConnectionPolicy(priority)); + device, BluetoothAdapter.priorityToConnectionPolicy(priority), + mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return false; @@ -605,7 +657,12 @@ public final class BluetoothHeadset implements BluetoothProfile { * @hide */ @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + android.Manifest.permission.MODIFY_PHONE_STATE, + }) public boolean setConnectionPolicy(@NonNull BluetoothDevice device, @ConnectionPolicy int connectionPolicy) { if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); @@ -616,7 +673,7 @@ public final class BluetoothHeadset implements BluetoothProfile { return false; } try { - return service.setConnectionPolicy(device, connectionPolicy); + return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return false; @@ -638,13 +695,16 @@ public final class BluetoothHeadset implements BluetoothProfile { * @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getPriority(BluetoothDevice device) { if (VDBG) log("getPriority(" + device + ")"); final IBluetoothHeadset service = mService; if (service != null && isEnabled() && isValidDevice(device)) { try { - return BluetoothAdapter.connectionPolicyToPriority(service.getPriority(device)); + return BluetoothAdapter.connectionPolicyToPriority( + service.getPriority(device, mAttributionSource)); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return BluetoothProfile.PRIORITY_OFF; @@ -666,13 +726,17 @@ public final class BluetoothHeadset implements BluetoothProfile { * @hide */ @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) { if (VDBG) log("getConnectionPolicy(" + device + ")"); final IBluetoothHeadset service = mService; if (service != null && isEnabled() && isValidDevice(device)) { try { - return service.getConnectionPolicy(device); + return service.getConnectionPolicy(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; @@ -688,13 +752,15 @@ public final class BluetoothHeadset implements BluetoothProfile { * @param device Bluetooth device * @return true if echo cancellation and/or noise reduction is supported, false otherwise */ - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean isNoiseReductionSupported(@NonNull BluetoothDevice device) { if (DBG) log("isNoiseReductionSupported()"); final IBluetoothHeadset service = mService; if (service != null && isEnabled() && isValidDevice(device)) { try { - return service.isNoiseReductionSupported(device); + return service.isNoiseReductionSupported(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); } @@ -709,13 +775,15 @@ public final class BluetoothHeadset implements BluetoothProfile { * @param device Bluetooth device * @return true if voice recognition is supported, false otherwise */ - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean isVoiceRecognitionSupported(@NonNull BluetoothDevice device) { if (DBG) log("isVoiceRecognitionSupported()"); final IBluetoothHeadset service = mService; if (service != null && isEnabled() && isValidDevice(device)) { try { - return service.isVoiceRecognitionSupported(device); + return service.isVoiceRecognitionSupported(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); } @@ -738,19 +806,23 @@ public final class BluetoothHeadset implements BluetoothProfile { * audio connection is established and to {@link #STATE_AUDIO_DISCONNECTED} * in case of failure to establish the audio connection. * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. - * * @param device Bluetooth headset * @return false if there is no headset connected, or the connected headset doesn't support * voice recognition, or voice recognition is already started, or audio channel is occupied, * or on error, true otherwise */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.MODIFY_PHONE_STATE, + }) public boolean startVoiceRecognition(BluetoothDevice device) { if (DBG) log("startVoiceRecognition()"); final IBluetoothHeadset service = mService; if (service != null && isEnabled() && isValidDevice(device)) { try { - return service.startVoiceRecognition(device); + return service.startVoiceRecognition(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); } @@ -767,18 +839,19 @@ public final class BluetoothHeadset implements BluetoothProfile { * If this function returns true, this intent will be broadcasted with * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_DISCONNECTED}. * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. - * * @param device Bluetooth headset * @return false if there is no headset connected, or voice recognition has not started, * or voice recognition has ended on this headset, or on error, true otherwise */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean stopVoiceRecognition(BluetoothDevice device) { if (DBG) log("stopVoiceRecognition()"); final IBluetoothHeadset service = mService; if (service != null && isEnabled() && isValidDevice(device)) { try { - return service.stopVoiceRecognition(device); + return service.stopVoiceRecognition(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); } @@ -790,17 +863,18 @@ public final class BluetoothHeadset implements BluetoothProfile { /** * Check if Bluetooth SCO audio is connected. * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. - * * @param device Bluetooth headset * @return true if SCO is connected, false otherwise or on error */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean isAudioConnected(BluetoothDevice device) { if (VDBG) log("isAudioConnected()"); final IBluetoothHeadset service = mService; if (service != null && isEnabled() && isValidDevice(device)) { try { - return service.isAudioConnected(device); + return service.isAudioConnected(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); } @@ -827,12 +901,14 @@ public final class BluetoothHeadset implements BluetoothProfile { * @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getAudioState(BluetoothDevice device) { if (VDBG) log("getAudioState"); final IBluetoothHeadset service = mService; if (service != null && !isDisabled()) { try { - return service.getAudioState(device); + return service.getAudioState(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -853,12 +929,14 @@ public final class BluetoothHeadset implements BluetoothProfile { * @param allowed {@code true} if the profile can reroute audio, {@code false} otherwise. * @hide */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void setAudioRouteAllowed(boolean allowed) { if (VDBG) log("setAudioRouteAllowed"); final IBluetoothHeadset service = mService; if (service != null && isEnabled()) { try { - service.setAudioRouteAllowed(allowed); + service.setAudioRouteAllowed(allowed, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -874,12 +952,14 @@ public final class BluetoothHeadset implements BluetoothProfile { * * @hide */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean getAudioRouteAllowed() { if (VDBG) log("getAudioRouteAllowed"); final IBluetoothHeadset service = mService; if (service != null && isEnabled()) { try { - return service.getAudioRouteAllowed(); + return service.getAudioRouteAllowed(mAttributionSource); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -897,12 +977,14 @@ public final class BluetoothHeadset implements BluetoothProfile { * False to use SCO audio in normal manner * @hide */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void setForceScoAudio(boolean forced) { if (VDBG) log("setForceScoAudio " + String.valueOf(forced)); final IBluetoothHeadset service = mService; if (service != null && isEnabled()) { try { - service.setForceScoAudio(forced); + service.setForceScoAudio(forced, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -915,18 +997,19 @@ public final class BluetoothHeadset implements BluetoothProfile { /** * Check if at least one headset's SCO audio is connected or connecting * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. - * * @return true if at least one device's SCO audio is connected or connecting, false otherwise * or on error * @hide */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean isAudioOn() { if (VDBG) log("isAudioOn()"); final IBluetoothHeadset service = mService; if (service != null && isEnabled()) { try { - return service.isAudioOn(); + return service.isAudioOn(mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); } @@ -955,11 +1038,13 @@ public final class BluetoothHeadset implements BluetoothProfile { * @hide */ @UnsupportedAppUsage + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean connectAudio() { final IBluetoothHeadset service = mService; if (service != null && isEnabled()) { try { - return service.connectAudio(); + return service.connectAudio(mAttributionSource); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -982,11 +1067,13 @@ public final class BluetoothHeadset implements BluetoothProfile { * @hide */ @UnsupportedAppUsage + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean disconnectAudio() { final IBluetoothHeadset service = mService; if (service != null && isEnabled()) { try { - return service.disconnectAudio(); + return service.disconnectAudio(mAttributionSource); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -1018,14 +1105,19 @@ public final class BluetoothHeadset implements BluetoothProfile { * - binder is dead or Bluetooth is disabled or other error * @hide */ - @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.MODIFY_PHONE_STATE, + }) @UnsupportedAppUsage public boolean startScoUsingVirtualVoiceCall() { if (DBG) log("startScoUsingVirtualVoiceCall()"); final IBluetoothHeadset service = mService; if (service != null && isEnabled()) { try { - return service.startScoUsingVirtualVoiceCall(); + return service.startScoUsingVirtualVoiceCall(mAttributionSource); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -1048,14 +1140,19 @@ public final class BluetoothHeadset implements BluetoothProfile { * - binder is dead or Bluetooth is disabled or other error * @hide */ - @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.MODIFY_PHONE_STATE, + }) @UnsupportedAppUsage public boolean stopScoUsingVirtualVoiceCall() { if (DBG) log("stopScoUsingVirtualVoiceCall()"); final IBluetoothHeadset service = mService; if (service != null && isEnabled()) { try { - return service.stopScoUsingVirtualVoiceCall(); + return service.stopScoUsingVirtualVoiceCall(mAttributionSource); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -1075,12 +1172,18 @@ public final class BluetoothHeadset implements BluetoothProfile { * @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.MODIFY_PHONE_STATE, + }) public void phoneStateChanged(int numActive, int numHeld, int callState, String number, int type, String name) { final IBluetoothHeadset service = mService; if (service != null && isEnabled()) { try { - service.phoneStateChanged(numActive, numHeld, callState, number, type, name); + service.phoneStateChanged(numActive, numHeld, callState, number, type, name, + mAttributionSource); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -1095,12 +1198,18 @@ public final class BluetoothHeadset implements BluetoothProfile { * * @hide */ + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.MODIFY_PHONE_STATE, + }) public void clccResponse(int index, int direction, int status, int mode, boolean mpty, String number, int type) { final IBluetoothHeadset service = mService; if (service != null && isEnabled()) { try { - service.clccResponse(index, direction, status, mode, mpty, number, type); + service.clccResponse(index, direction, status, mode, mpty, number, type, + mAttributionSource); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -1119,8 +1228,6 @@ public final class BluetoothHeadset implements BluetoothProfile { * * <p>Currently only {@link #VENDOR_RESULT_CODE_COMMAND_ANDROID} is allowed as {@code command}. * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. - * * @param device Bluetooth headset. * @param command A vendor-specific command. * @param arg The argument that will be attached to the command. @@ -1128,6 +1235,9 @@ public final class BluetoothHeadset implements BluetoothProfile { * vendor-specific unsolicited result code, or on error. {@code true} otherwise. * @throws IllegalArgumentException if {@code command} is {@code null}. */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean sendVendorSpecificResultCode(BluetoothDevice device, String command, String arg) { if (DBG) { @@ -1139,7 +1249,8 @@ public final class BluetoothHeadset implements BluetoothProfile { final IBluetoothHeadset service = mService; if (service != null && isEnabled() && isValidDevice(device)) { try { - return service.sendVendorSpecificResultCode(device, command, arg); + return service.sendVendorSpecificResultCode(device, command, arg, + mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); } @@ -1164,15 +1275,17 @@ public final class BluetoothHeadset implements BluetoothProfile { * {@link #ACTION_ACTIVE_DEVICE_CHANGED} intent will be broadcasted * with the active device. * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} - * permission. - * * @param device Remote Bluetooth Device, could be null if phone call audio should not be * streamed to a headset * @return false on immediate error, true otherwise * @hide */ - @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.MODIFY_PHONE_STATE, + }) @UnsupportedAppUsage(trackingBug = 171933273) public boolean setActiveDevice(@Nullable BluetoothDevice device) { if (DBG) { @@ -1181,7 +1294,7 @@ public final class BluetoothHeadset implements BluetoothProfile { final IBluetoothHeadset service = mService; if (service != null && isEnabled() && (device == null || isValidDevice(device))) { try { - return service.setActiveDevice(device); + return service.setActiveDevice(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); } @@ -1201,7 +1314,9 @@ public final class BluetoothHeadset implements BluetoothProfile { */ @UnsupportedAppUsage(trackingBug = 171933273) @Nullable - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public BluetoothDevice getActiveDevice() { if (VDBG) { Log.d(TAG, "getActiveDevice"); @@ -1209,7 +1324,8 @@ public final class BluetoothHeadset implements BluetoothProfile { final IBluetoothHeadset service = mService; if (service != null && isEnabled()) { try { - return service.getActiveDevice(); + return Attributable.setAttributionSource( + service.getActiveDevice(mAttributionSource), mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); } @@ -1227,7 +1343,9 @@ public final class BluetoothHeadset implements BluetoothProfile { * @return true if in-band ringing is enabled, false if in-band ringing is disabled * @hide */ - @RequiresPermission(android.Manifest.permission.BLUETOOTH) + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean isInbandRingingEnabled() { if (DBG) { log("isInbandRingingEnabled()"); @@ -1235,7 +1353,7 @@ public final class BluetoothHeadset implements BluetoothProfile { final IBluetoothHeadset service = mService; if (service != null && isEnabled()) { try { - return service.isInbandRingingEnabled(); + return service.isInbandRingingEnabled(mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); } @@ -1257,6 +1375,7 @@ public final class BluetoothHeadset implements BluetoothProfile { com.android.internal.R.bool.config_bluetooth_hfp_inband_ringing_support); } + @SuppressLint("AndroidFrameworkBluetoothPermission") private final IBluetoothProfileServiceConnection mConnection = new IBluetoothProfileServiceConnection.Stub() { @Override @@ -1293,6 +1412,7 @@ public final class BluetoothHeadset implements BluetoothProfile { Log.d(TAG, msg); } + @SuppressLint("AndroidFrameworkBluetoothPermission") private final Handler mHandler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { diff --git a/framework/java/android/bluetooth/BluetoothHeadsetClient.java b/framework/java/android/bluetooth/BluetoothHeadsetClient.java index e5b2a1e23c..83108d22e0 100644 --- a/framework/java/android/bluetooth/BluetoothHeadsetClient.java +++ b/framework/java/android/bluetooth/BluetoothHeadsetClient.java @@ -16,10 +16,15 @@ package android.bluetooth; -import android.Manifest; import android.annotation.NonNull; import android.annotation.RequiresPermission; +import android.annotation.SdkConstant; +import android.annotation.SdkConstant.SdkConstantType; +import android.bluetooth.annotations.RequiresBluetoothConnectPermission; +import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; import android.compat.annotation.UnsupportedAppUsage; +import android.content.Attributable; +import android.content.AttributionSource; import android.content.Context; import android.os.Binder; import android.os.Build; @@ -71,6 +76,9 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * booleans with value <code>true</code>, * and not supported ones are <strong>not</strong> being sent at all.</p> */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.headsetclient.profile.action.CONNECTION_STATE_CHANGED"; @@ -89,6 +97,9 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * it also includes {@link #EXTRA_AUDIO_WBS} * indicating wide band speech support.</p> */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_AUDIO_STATE_CHANGED = "android.bluetooth.headsetclient.profile.action.AUDIO_STATE_CHANGED"; @@ -105,6 +116,9 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * {@link #EXTRA_VOICE_RECOGNITION}, * {@link #EXTRA_IN_BAND_RING}</p> */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_AG_EVENT = "android.bluetooth.headsetclient.profile.action.AG_EVENT"; @@ -116,6 +130,9 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * with value of {@link BluetoothHeadsetClientCall} instance, * representing actual call state.</p> */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_CALL_CHANGED = "android.bluetooth.headsetclient.profile.action.AG_CALL_CHANGED"; @@ -126,6 +143,9 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * like <code>ACTION_AG_EVENT</code> with <code>EXTRA_VOICE_RECOGNITION</code> value * when for example user started voice recognition from HF unit. */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_RESULT = "android.bluetooth.headsetclient.profile.action.RESULT"; @@ -137,6 +157,9 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * Vendor event can be a response to an vendor specific command or unsolicited. * */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_VENDOR_SPECIFIC_HEADSETCLIENT_EVENT = "android.bluetooth.headsetclient.profile.action.VENDOR_SPECIFIC_EVENT"; @@ -148,6 +171,9 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * {@link #EXTRA_NUMBER}, * with a <code>String</code> value representing phone number.</p> */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_LAST_VTAG = "android.bluetooth.headsetclient.profile.action.LAST_VTAG"; @@ -400,7 +426,8 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { public static final int CALL_ACCEPT_HOLD = 1; public static final int CALL_ACCEPT_TERMINATE = 2; - private BluetoothAdapter mAdapter; + private final BluetoothAdapter mAdapter; + private final AttributionSource mAttributionSource; private final BluetoothProfileConnector<IBluetoothHeadsetClient> mProfileConnector = new BluetoothProfileConnector(this, BluetoothProfile.HEADSET_CLIENT, "BluetoothHeadsetClient", IBluetoothHeadsetClient.class.getName()) { @@ -413,8 +440,10 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { /** * Create a BluetoothHeadsetClient proxy object. */ - /*package*/ BluetoothHeadsetClient(Context context, ServiceListener listener) { - mAdapter = BluetoothAdapter.getDefaultAdapter(); + /* package */ BluetoothHeadsetClient(Context context, ServiceListener listener, + BluetoothAdapter adapter) { + mAdapter = adapter; + mAttributionSource = adapter.getAttributionSource(); mProfileConnector.connect(context, listener); } @@ -447,13 +476,15 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean connect(BluetoothDevice device) { if (DBG) log("connect(" + device + ")"); final IBluetoothHeadsetClient service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { try { - return service.connect(device); + return service.connect(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return false; @@ -473,13 +504,15 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean disconnect(BluetoothDevice device) { if (DBG) log("disconnect(" + device + ")"); final IBluetoothHeadsetClient service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { try { - return service.disconnect(device); + return service.disconnect(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return false; @@ -495,13 +528,16 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * @return list of connected devices; empty list if nothing is connected. */ @Override + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public List<BluetoothDevice> getConnectedDevices() { if (VDBG) log("getConnectedDevices()"); final IBluetoothHeadsetClient service = getService(); if (service != null && isEnabled()) { try { - return service.getConnectedDevices(); + return Attributable.setAttributionSource( + service.getConnectedDevices(mAttributionSource), mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return new ArrayList<BluetoothDevice>(); @@ -519,13 +555,17 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * list if nothing matches the <code>states</code> */ @Override + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { if (VDBG) log("getDevicesMatchingStates()"); final IBluetoothHeadsetClient service = getService(); if (service != null && isEnabled()) { try { - return service.getDevicesMatchingConnectionStates(states); + return Attributable.setAttributionSource( + service.getDevicesMatchingConnectionStates(states, mAttributionSource), + mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return new ArrayList<BluetoothDevice>(); @@ -542,13 +582,15 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * @return the state of connection of the device */ @Override + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getConnectionState(BluetoothDevice device) { if (VDBG) log("getConnectionState(" + device + ")"); final IBluetoothHeadsetClient service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { try { - return service.getConnectionState(device); + return service.getConnectionState(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return BluetoothProfile.STATE_DISCONNECTED; @@ -569,7 +611,8 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * @return true if priority is set, false on error * @hide */ - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean setPriority(BluetoothDevice device, int priority) { if (DBG) log("setPriority(" + device + ", " + priority + ")"); return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority)); @@ -587,7 +630,8 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * @return true if connectionPolicy is set, false on error * @hide */ - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean setConnectionPolicy(@NonNull BluetoothDevice device, @ConnectionPolicy int connectionPolicy) { if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); @@ -599,7 +643,7 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { return false; } try { - return service.setConnectionPolicy(device, connectionPolicy); + return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return false; @@ -619,7 +663,9 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * @return priority of the device * @hide */ - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getPriority(BluetoothDevice device) { if (VDBG) log("getPriority(" + device + ")"); return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device)); @@ -636,14 +682,16 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * @return connection policy of the device * @hide */ - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) { if (VDBG) log("getConnectionPolicy(" + device + ")"); final IBluetoothHeadsetClient service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { try { - return service.getConnectionPolicy(device); + return service.getConnectionPolicy(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; @@ -664,13 +712,15 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * #EXTRA_AG_FEATURE_VOICE_RECOGNITION}. This method invocation will fail silently when feature * is not supported.</p> */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean startVoiceRecognition(BluetoothDevice device) { if (DBG) log("startVoiceRecognition()"); final IBluetoothHeadsetClient service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { try { - return service.startVoiceRecognition(device); + return service.startVoiceRecognition(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); } @@ -688,6 +738,8 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * @return <code>true</code> if command has been issued successfully; <code>false</code> * otherwise. */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean sendVendorAtCommand(BluetoothDevice device, int vendorId, String atCommand) { if (DBG) log("sendVendorSpecificCommand()"); @@ -695,7 +747,7 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { getService(); if (service != null && isEnabled() && isValidDevice(device)) { try { - return service.sendVendorAtCommand(device, vendorId, atCommand); + return service.sendVendorAtCommand(device, vendorId, atCommand, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); } @@ -715,13 +767,15 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * #EXTRA_AG_FEATURE_VOICE_RECOGNITION}. This method invocation will fail silently when feature * is not supported.</p> */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean stopVoiceRecognition(BluetoothDevice device) { if (DBG) log("stopVoiceRecognition()"); final IBluetoothHeadsetClient service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { try { - return service.stopVoiceRecognition(device); + return service.stopVoiceRecognition(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); } @@ -736,13 +790,16 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * @param device remote device * @return list of calls; empty list if none call exists */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public List<BluetoothHeadsetClientCall> getCurrentCalls(BluetoothDevice device) { if (DBG) log("getCurrentCalls()"); final IBluetoothHeadsetClient service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { try { - return service.getCurrentCalls(device); + return Attributable.setAttributionSource( + service.getCurrentCalls(device, mAttributionSource), mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); } @@ -757,13 +814,15 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * @param device remote device * @return bundle of AG indicators; null if device is not in CONNECTED state */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public Bundle getCurrentAgEvents(BluetoothDevice device) { if (DBG) log("getCurrentCalls()"); final IBluetoothHeadsetClient service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { try { - return service.getCurrentAgEvents(device); + return service.getCurrentAgEvents(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); } @@ -782,13 +841,15 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean acceptCall(BluetoothDevice device, int flag) { if (DBG) log("acceptCall()"); final IBluetoothHeadsetClient service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { try { - return service.acceptCall(device, flag); + return service.acceptCall(device, flag, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); } @@ -804,13 +865,15 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * @return <code>true</code> if command has been issued successfully; <code>false</code> * otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent. */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean holdCall(BluetoothDevice device) { if (DBG) log("holdCall()"); final IBluetoothHeadsetClient service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { try { - return service.holdCall(device); + return service.holdCall(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); } @@ -831,13 +894,15 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * supported.</p> */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean rejectCall(BluetoothDevice device) { if (DBG) log("rejectCall()"); final IBluetoothHeadsetClient service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { try { - return service.rejectCall(device); + return service.rejectCall(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); } @@ -862,13 +927,15 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * #EXTRA_AG_FEATURE_ECC}. This method invocation will fail silently when feature is not * supported.</p> */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean terminateCall(BluetoothDevice device, BluetoothHeadsetClientCall call) { if (DBG) log("terminateCall()"); final IBluetoothHeadsetClient service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { try { - return service.terminateCall(device, call); + return service.terminateCall(device, call, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); } @@ -891,13 +958,15 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * #EXTRA_AG_FEATURE_ECC}. This method invocation will fail silently when feature is not * supported.</p> */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean enterPrivateMode(BluetoothDevice device, int index) { if (DBG) log("enterPrivateMode()"); final IBluetoothHeadsetClient service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { try { - return service.enterPrivateMode(device, index); + return service.enterPrivateMode(device, index, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); } @@ -919,13 +988,15 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * #EXTRA_AG_FEATURE_MERGE_AND_DETACH}. This method invocation will fail silently when feature * is not supported.</p> */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean explicitCallTransfer(BluetoothDevice device) { if (DBG) log("explicitCallTransfer()"); final IBluetoothHeadsetClient service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { try { - return service.explicitCallTransfer(device); + return service.explicitCallTransfer(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); } @@ -943,13 +1014,16 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * successfully; <code>{@link null}</code> otherwise; upon completion HFP sends {@link * #ACTION_CALL_CHANGED} intent in case of success; {@link #ACTION_RESULT} is sent otherwise; */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public BluetoothHeadsetClientCall dial(BluetoothDevice device, String number) { if (DBG) log("dial()"); final IBluetoothHeadsetClient service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { try { - return service.dial(device, number); + return Attributable.setAttributionSource( + service.dial(device, number, mAttributionSource), mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); } @@ -968,13 +1042,15 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * @return <code>true</code> if command has been issued successfully; <code>false</code> * otherwise; upon completion HFP sends {@link #ACTION_RESULT} intent; */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean sendDTMF(BluetoothDevice device, byte code) { if (DBG) log("sendDTMF()"); final IBluetoothHeadsetClient service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { try { - return service.sendDTMF(device, code); + return service.sendDTMF(device, code, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); } @@ -995,13 +1071,15 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * #EXTRA_AG_FEATURE_ATTACH_NUMBER_TO_VT}. This method invocation will fail silently when * feature is not supported.</p> */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean getLastVoiceTagNumber(BluetoothDevice device) { if (DBG) log("getLastVoiceTagNumber()"); final IBluetoothHeadsetClient service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { try { - return service.getLastVoiceTagNumber(device); + return service.getLastVoiceTagNumber(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); } @@ -1016,13 +1094,15 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * Note: This is an internal function and shouldn't be exposed */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getAudioState(BluetoothDevice device) { if (VDBG) log("getAudioState"); final IBluetoothHeadsetClient service = getService(); if (service != null && isEnabled()) { try { - return service.getAudioState(device); + return service.getAudioState(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -1040,13 +1120,15 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * @param allowed if routing is allowed to the device Note: This is an internal function and * shouldn't be exposed */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void setAudioRouteAllowed(BluetoothDevice device, boolean allowed) { if (VDBG) log("setAudioRouteAllowed"); final IBluetoothHeadsetClient service = getService(); if (service != null && isEnabled()) { try { - service.setAudioRouteAllowed(device, allowed); + service.setAudioRouteAllowed(device, allowed, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -1063,13 +1145,15 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * @return whether the command succeeded Note: This is an internal function and shouldn't be * exposed */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean getAudioRouteAllowed(BluetoothDevice device) { if (VDBG) log("getAudioRouteAllowed"); final IBluetoothHeadsetClient service = getService(); if (service != null && isEnabled()) { try { - return service.getAudioRouteAllowed(device); + return service.getAudioRouteAllowed(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -1089,12 +1173,14 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * @return <code>true</code> if command has been issued successfully; <code>false</code> * otherwise; upon completion HFP sends {@link #ACTION_AUDIO_STATE_CHANGED} intent; */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean connectAudio(BluetoothDevice device) { final IBluetoothHeadsetClient service = getService(); if (service != null && isEnabled()) { try { - return service.connectAudio(device); + return service.connectAudio(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -1114,12 +1200,14 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * @return <code>true</code> if command has been issued successfully; <code>false</code> * otherwise; upon completion HFP sends {@link #ACTION_AUDIO_STATE_CHANGED} intent; */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean disconnectAudio(BluetoothDevice device) { final IBluetoothHeadsetClient service = getService(); if (service != null && isEnabled()) { try { - return service.disconnectAudio(device); + return service.disconnectAudio(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -1136,12 +1224,14 @@ public final class BluetoothHeadsetClient implements BluetoothProfile { * @param device remote device * @return bundle of AG features; null if no service or AG not connected */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public Bundle getCurrentAgFeatures(BluetoothDevice device) { final IBluetoothHeadsetClient service = getService(); if (service != null && isEnabled()) { try { - return service.getCurrentAgFeatures(device); + return service.getCurrentAgFeatures(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, e.toString()); } diff --git a/framework/java/android/bluetooth/BluetoothHeadsetClientCall.java b/framework/java/android/bluetooth/BluetoothHeadsetClientCall.java index 219d1596fb..3f1ef84612 100644 --- a/framework/java/android/bluetooth/BluetoothHeadsetClientCall.java +++ b/framework/java/android/bluetooth/BluetoothHeadsetClientCall.java @@ -16,7 +16,10 @@ package android.bluetooth; +import android.annotation.NonNull; import android.compat.annotation.UnsupportedAppUsage; +import android.content.Attributable; +import android.content.AttributionSource; import android.os.Build; import android.os.Parcel; import android.os.Parcelable; @@ -30,7 +33,7 @@ import java.util.UUID; * * @hide */ -public final class BluetoothHeadsetClientCall implements Parcelable { +public final class BluetoothHeadsetClientCall implements Parcelable, Attributable { /* Call state */ /** @@ -98,6 +101,11 @@ public final class BluetoothHeadsetClientCall implements Parcelable { mCreationElapsedMilli = SystemClock.elapsedRealtime(); } + /** {@hide} */ + public void setAttributionSource(@NonNull AttributionSource attributionSource) { + Attributable.setAttributionSource(mDevice, attributionSource); + } + /** * Sets call's state. * diff --git a/framework/java/android/bluetooth/BluetoothHealth.java b/framework/java/android/bluetooth/BluetoothHealth.java index 5fd60e0016..65f68a943e 100644 --- a/framework/java/android/bluetooth/BluetoothHealth.java +++ b/framework/java/android/bluetooth/BluetoothHealth.java @@ -16,6 +16,10 @@ package android.bluetooth; +import android.annotation.RequiresPermission; +import android.annotation.SuppressLint; +import android.bluetooth.annotations.RequiresBluetoothConnectPermission; +import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; import android.os.ParcelFileDescriptor; import android.util.Log; @@ -111,8 +115,6 @@ public final class BluetoothHealth implements BluetoothProfile { * which will act as the {@link #SOURCE_ROLE}. This is an asynchronous call and so * the callback is used to notify success or failure if the function returns true. * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. - * * @param name The friendly name associated with the application or configuration. * @param dataType The dataType of the Source role of Health Profile to which the sink wants to * connect to. @@ -126,6 +128,10 @@ public final class BluetoothHealth implements BluetoothProfile { * {@link BluetoothDevice#createL2capChannel(int)} */ @Deprecated + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) + @SuppressLint("AndroidFrameworkRequiresPermission") public boolean registerSinkAppConfiguration(String name, int dataType, BluetoothHealthCallback callback) { Log.e(TAG, "registerSinkAppConfiguration(): BluetoothHealth is deprecated"); @@ -136,8 +142,6 @@ public final class BluetoothHealth implements BluetoothProfile { * Unregister an application configuration that has been registered using * {@link #registerSinkAppConfiguration} * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. - * * @param config The health app configuration * @return Success or failure. * @@ -147,6 +151,10 @@ public final class BluetoothHealth implements BluetoothProfile { * {@link BluetoothDevice#createL2capChannel(int)} */ @Deprecated + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) + @SuppressLint("AndroidFrameworkRequiresPermission") public boolean unregisterAppConfiguration(BluetoothHealthAppConfiguration config) { Log.e(TAG, "unregisterAppConfiguration(): BluetoothHealth is deprecated"); return false; @@ -157,8 +165,6 @@ public final class BluetoothHealth implements BluetoothProfile { * This is an asynchronous call. If this function returns true, the callback * associated with the application configuration will be called. * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. - * * @param device The remote Bluetooth device. * @param config The application configuration which has been registered using {@link * #registerSinkAppConfiguration(String, int, BluetoothHealthCallback) } @@ -170,6 +176,10 @@ public final class BluetoothHealth implements BluetoothProfile { * {@link BluetoothDevice#createL2capChannel(int)} */ @Deprecated + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) + @SuppressLint("AndroidFrameworkRequiresPermission") public boolean connectChannelToSource(BluetoothDevice device, BluetoothHealthAppConfiguration config) { Log.e(TAG, "connectChannelToSource(): BluetoothHealth is deprecated"); @@ -181,8 +191,6 @@ public final class BluetoothHealth implements BluetoothProfile { * This is an asynchronous call. If this function returns true, the callback * associated with the application configuration will be called. * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. - * * @param device The remote Bluetooth device. * @param config The application configuration which has been registered using {@link * #registerSinkAppConfiguration(String, int, BluetoothHealthCallback) } @@ -195,6 +203,10 @@ public final class BluetoothHealth implements BluetoothProfile { * {@link BluetoothDevice#createL2capChannel(int)} */ @Deprecated + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) + @SuppressLint("AndroidFrameworkRequiresPermission") public boolean disconnectChannel(BluetoothDevice device, BluetoothHealthAppConfiguration config, int channelId) { Log.e(TAG, "disconnectChannel(): BluetoothHealth is deprecated"); @@ -205,8 +217,6 @@ public final class BluetoothHealth implements BluetoothProfile { * Get the file descriptor of the main channel associated with the remote device * and application configuration. * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. - * * <p> Its the responsibility of the caller to close the ParcelFileDescriptor * when done. * @@ -220,6 +230,10 @@ public final class BluetoothHealth implements BluetoothProfile { * {@link BluetoothDevice#createL2capChannel(int)} */ @Deprecated + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) + @SuppressLint("AndroidFrameworkRequiresPermission") public ParcelFileDescriptor getMainChannelFd(BluetoothDevice device, BluetoothHealthAppConfiguration config) { Log.e(TAG, "getMainChannelFd(): BluetoothHealth is deprecated"); @@ -229,8 +243,6 @@ public final class BluetoothHealth implements BluetoothProfile { /** * Get the current connection state of the profile. * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. - * * This is not specific to any application configuration but represents the connection * state of the local Bluetooth adapter with the remote device. This can be used * by applications like status bar which would just like to know the state of the @@ -241,6 +253,10 @@ public final class BluetoothHealth implements BluetoothProfile { * #STATE_CONNECTING}, {@link #STATE_DISCONNECTED}, {@link #STATE_DISCONNECTING} */ @Override + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) + @SuppressLint("AndroidFrameworkRequiresPermission") public int getConnectionState(BluetoothDevice device) { Log.e(TAG, "getConnectionState(): BluetoothHealth is deprecated"); return STATE_DISCONNECTED; @@ -251,8 +267,6 @@ public final class BluetoothHealth implements BluetoothProfile { * * <p> Return the set of devices which are in state {@link #STATE_CONNECTED} * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. - * * This is not specific to any application configuration but represents the connection * state of the local Bluetooth adapter for this profile. This can be used * by applications like status bar which would just like to know the state of the @@ -261,6 +275,10 @@ public final class BluetoothHealth implements BluetoothProfile { * @return List of devices. The list will be empty on error. */ @Override + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) + @SuppressLint("AndroidFrameworkRequiresPermission") public List<BluetoothDevice> getConnectedDevices() { Log.e(TAG, "getConnectedDevices(): BluetoothHealth is deprecated"); return new ArrayList<>(); @@ -273,8 +291,7 @@ public final class BluetoothHealth implements BluetoothProfile { * <p> If none of the devices match any of the given states, * an empty list will be returned. * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. - * This is not specific to any application configuration but represents the connection + * <p>This is not specific to any application configuration but represents the connection * state of the local Bluetooth adapter for this profile. This can be used * by applications like status bar which would just like to know the state of the * local adapter. @@ -284,6 +301,10 @@ public final class BluetoothHealth implements BluetoothProfile { * @return List of devices. The list will be empty on error. */ @Override + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) + @SuppressLint("AndroidFrameworkRequiresPermission") public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { Log.e(TAG, "getDevicesMatchingConnectionStates(): BluetoothHealth is deprecated"); return new ArrayList<>(); diff --git a/framework/java/android/bluetooth/BluetoothHearingAid.java b/framework/java/android/bluetooth/BluetoothHearingAid.java index ff78825e0f..183f4d55bd 100644 --- a/framework/java/android/bluetooth/BluetoothHearingAid.java +++ b/framework/java/android/bluetooth/BluetoothHearingAid.java @@ -21,9 +21,15 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SdkConstant; +import android.annotation.SuppressLint; import android.annotation.SdkConstant.SdkConstantType; +import android.bluetooth.annotations.RequiresBluetoothConnectPermission; +import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission; +import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; +import android.content.Attributable; +import android.content.AttributionSource; import android.content.Context; import android.os.Binder; import android.os.Build; @@ -64,10 +70,10 @@ public final class BluetoothHearingAid implements BluetoothProfile { * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}. - * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to - * receive. */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.hearingaid.profile.action.CONNECTION_STATE_CHANGED"; @@ -81,11 +87,11 @@ public final class BluetoothHearingAid implements BluetoothProfile { * be null if no device is active. </li> * </ul> * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to - * receive. - * * @hide */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_ACTIVE_DEVICE_CHANGED = @@ -126,7 +132,8 @@ public final class BluetoothHearingAid implements BluetoothProfile { */ public static final long HI_SYNC_ID_INVALID = IBluetoothHearingAid.HI_SYNC_ID_INVALID; - private BluetoothAdapter mAdapter; + private final BluetoothAdapter mAdapter; + private final AttributionSource mAttributionSource; private final BluetoothProfileConnector<IBluetoothHearingAid> mProfileConnector = new BluetoothProfileConnector(this, BluetoothProfile.HEARING_AID, "BluetoothHearingAid", IBluetoothHearingAid.class.getName()) { @@ -140,8 +147,10 @@ public final class BluetoothHearingAid implements BluetoothProfile { * Create a BluetoothHearingAid proxy object for interacting with the local * Bluetooth Hearing Aid service. */ - /*package*/ BluetoothHearingAid(Context context, ServiceListener listener) { - mAdapter = BluetoothAdapter.getDefaultAdapter(); + /* package */ BluetoothHearingAid(Context context, ServiceListener listener, + BluetoothAdapter adapter) { + mAdapter = adapter; + mAttributionSource = adapter.getAttributionSource(); mProfileConnector.connect(context, listener); } @@ -167,13 +176,17 @@ public final class BluetoothHearingAid implements BluetoothProfile { * @return false on immediate error, true otherwise * @hide */ - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public boolean connect(BluetoothDevice device) { if (DBG) log("connect(" + device + ")"); final IBluetoothHearingAid service = getService(); try { if (service != null && isEnabled() && isValidDevice(device)) { - return service.connect(device); + return service.connect(device, mAttributionSource); } if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; @@ -205,13 +218,17 @@ public final class BluetoothHearingAid implements BluetoothProfile { * @return false on immediate error, true otherwise * @hide */ - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public boolean disconnect(BluetoothDevice device) { if (DBG) log("disconnect(" + device + ")"); final IBluetoothHearingAid service = getService(); try { if (service != null && isEnabled() && isValidDevice(device)) { - return service.disconnect(device); + return service.disconnect(device, mAttributionSource); } if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; @@ -225,12 +242,15 @@ public final class BluetoothHearingAid implements BluetoothProfile { * {@inheritDoc} */ @Override + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public @NonNull List<BluetoothDevice> getConnectedDevices() { if (VDBG) log("getConnectedDevices()"); final IBluetoothHearingAid service = getService(); try { if (service != null && isEnabled()) { - return service.getConnectedDevices(); + return Attributable.setAttributionSource( + service.getConnectedDevices(mAttributionSource), mAttributionSource); } if (service == null) Log.w(TAG, "Proxy not attached to service"); return new ArrayList<BluetoothDevice>(); @@ -244,13 +264,17 @@ public final class BluetoothHearingAid implements BluetoothProfile { * {@inheritDoc} */ @Override + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public @NonNull List<BluetoothDevice> getDevicesMatchingConnectionStates( @NonNull int[] states) { if (VDBG) log("getDevicesMatchingStates()"); final IBluetoothHearingAid service = getService(); try { if (service != null && isEnabled()) { - return service.getDevicesMatchingConnectionStates(states); + return Attributable.setAttributionSource( + service.getDevicesMatchingConnectionStates(states, mAttributionSource), + mAttributionSource); } if (service == null) Log.w(TAG, "Proxy not attached to service"); return new ArrayList<BluetoothDevice>(); @@ -264,6 +288,8 @@ public final class BluetoothHearingAid implements BluetoothProfile { * {@inheritDoc} */ @Override + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public @BluetoothProfile.BtProfileState int getConnectionState( @NonNull BluetoothDevice device) { if (VDBG) log("getState(" + device + ")"); @@ -271,7 +297,7 @@ public final class BluetoothHearingAid implements BluetoothProfile { try { if (service != null && isEnabled() && isValidDevice(device)) { - return service.getConnectionState(device); + return service.getConnectionState(device, mAttributionSource); } if (service == null) Log.w(TAG, "Proxy not attached to service"); return BluetoothProfile.STATE_DISCONNECTED; @@ -295,14 +321,14 @@ public final class BluetoothHearingAid implements BluetoothProfile { * {@link #ACTION_ACTIVE_DEVICE_CHANGED} intent will be broadcasted * with the active device. * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} - * permission. - * * @param device the remote Bluetooth device. Could be null to clear * the active device and stop streaming audio to a Bluetooth device. * @return false on immediate error, true otherwise * @hide */ + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public boolean setActiveDevice(@Nullable BluetoothDevice device) { if (DBG) log("setActiveDevice(" + device + ")"); @@ -310,7 +336,7 @@ public final class BluetoothHearingAid implements BluetoothProfile { try { if (service != null && isEnabled() && ((device == null) || isValidDevice(device))) { - service.setActiveDevice(device); + service.setActiveDevice(device, mAttributionSource); return true; } if (service == null) Log.w(TAG, "Proxy not attached to service"); @@ -330,13 +356,16 @@ public final class BluetoothHearingAid implements BluetoothProfile { * @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public @NonNull List<BluetoothDevice> getActiveDevices() { if (VDBG) log("getActiveDevices()"); final IBluetoothHearingAid service = getService(); try { if (service != null && isEnabled()) { - return service.getActiveDevices(); + return Attributable.setAttributionSource( + service.getActiveDevices(mAttributionSource), mAttributionSource); } if (service == null) Log.w(TAG, "Proxy not attached to service"); return new ArrayList<>(); @@ -357,7 +386,11 @@ public final class BluetoothHearingAid implements BluetoothProfile { * @return true if priority is set, false on error * @hide */ - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public boolean setPriority(BluetoothDevice device, int priority) { if (DBG) log("setPriority(" + device + ", " + priority + ")"); return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority)); @@ -376,7 +409,11 @@ public final class BluetoothHearingAid implements BluetoothProfile { * @hide */ @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public boolean setConnectionPolicy(@NonNull BluetoothDevice device, @ConnectionPolicy int connectionPolicy) { if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); @@ -389,7 +426,7 @@ public final class BluetoothHearingAid implements BluetoothProfile { && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) { return false; } - return service.setConnectionPolicy(device, connectionPolicy); + return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource); } if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; @@ -409,7 +446,11 @@ public final class BluetoothHearingAid implements BluetoothProfile { * @return priority of the device * @hide */ - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public int getPriority(BluetoothDevice device) { if (VDBG) log("getPriority(" + device + ")"); return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device)); @@ -427,7 +468,11 @@ public final class BluetoothHearingAid implements BluetoothProfile { * @hide */ @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) { if (VDBG) log("getConnectionPolicy(" + device + ")"); verifyDeviceNotNull(device, "getConnectionPolicy"); @@ -435,7 +480,7 @@ public final class BluetoothHearingAid implements BluetoothProfile { try { if (service != null && isEnabled() && isValidDevice(device)) { - return service.getConnectionPolicy(device); + return service.getConnectionPolicy(device, mAttributionSource); } if (service == null) Log.w(TAG, "Proxy not attached to service"); return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; @@ -473,6 +518,8 @@ public final class BluetoothHearingAid implements BluetoothProfile { * @param volume Absolute volume to be set on remote * @hide */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void setVolume(int volume) { if (DBG) Log.d(TAG, "setVolume(" + volume + ")"); @@ -485,7 +532,7 @@ public final class BluetoothHearingAid implements BluetoothProfile { if (!isEnabled()) return; - service.setVolume(volume); + service.setVolume(volume, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); } @@ -502,7 +549,11 @@ public final class BluetoothHearingAid implements BluetoothProfile { * @hide */ @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public long getHiSyncId(@NonNull BluetoothDevice device) { if (VDBG) { log("getHiSyncId(" + device + ")"); @@ -517,7 +568,7 @@ public final class BluetoothHearingAid implements BluetoothProfile { if (!isEnabled() || !isValidDevice(device)) return HI_SYNC_ID_INVALID; - return service.getHiSyncId(device); + return service.getHiSyncId(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return HI_SYNC_ID_INVALID; @@ -531,7 +582,9 @@ public final class BluetoothHearingAid implements BluetoothProfile { * @return SIDE_LEFT or SIDE_RIGHT * @hide */ - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getDeviceSide(BluetoothDevice device) { if (VDBG) { log("getDeviceSide(" + device + ")"); @@ -540,7 +593,7 @@ public final class BluetoothHearingAid implements BluetoothProfile { try { if (service != null && isEnabled() && isValidDevice(device)) { - return service.getDeviceSide(device); + return service.getDeviceSide(device, mAttributionSource); } if (service == null) Log.w(TAG, "Proxy not attached to service"); return SIDE_LEFT; @@ -557,7 +610,9 @@ public final class BluetoothHearingAid implements BluetoothProfile { * @return MODE_MONAURAL or MODE_BINAURAL * @hide */ - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getDeviceMode(BluetoothDevice device) { if (VDBG) { log("getDeviceMode(" + device + ")"); @@ -566,7 +621,7 @@ public final class BluetoothHearingAid implements BluetoothProfile { try { if (service != null && isEnabled() && isValidDevice(device)) { - return service.getDeviceMode(device); + return service.getDeviceMode(device, mAttributionSource); } if (service == null) Log.w(TAG, "Proxy not attached to service"); return MODE_MONAURAL; diff --git a/framework/java/android/bluetooth/BluetoothHidDevice.java b/framework/java/android/bluetooth/BluetoothHidDevice.java index b5959c06cc..c2744b89aa 100644 --- a/framework/java/android/bluetooth/BluetoothHidDevice.java +++ b/framework/java/android/bluetooth/BluetoothHidDevice.java @@ -22,6 +22,10 @@ import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SystemApi; +import android.bluetooth.annotations.RequiresBluetoothConnectPermission; +import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; +import android.content.Attributable; +import android.content.AttributionSource; import android.content.Context; import android.os.Binder; import android.os.IBinder; @@ -56,9 +60,10 @@ public final class BluetoothHidDevice implements BluetoothProfile { * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of {@link * #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, {@link #STATE_CONNECTED}, {@link * #STATE_DISCONNECTING}. - * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to receive. */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.hiddevice.profile.action.CONNECTION_STATE_CHANGED"; @@ -334,56 +339,94 @@ public final class BluetoothHidDevice implements BluetoothProfile { private final Executor mExecutor; private final Callback mCallback; + private final AttributionSource mAttributionSource; - CallbackWrapper(Executor executor, Callback callback) { + CallbackWrapper(Executor executor, Callback callback, AttributionSource attributionSource) { mExecutor = executor; mCallback = callback; + mAttributionSource = attributionSource; } @Override public void onAppStatusChanged(BluetoothDevice pluggedDevice, boolean registered) { - clearCallingIdentity(); - mExecutor.execute(() -> mCallback.onAppStatusChanged(pluggedDevice, registered)); + Attributable.setAttributionSource(pluggedDevice, mAttributionSource); + final long token = clearCallingIdentity(); + try { + mExecutor.execute(() -> mCallback.onAppStatusChanged(pluggedDevice, registered)); + } finally { + restoreCallingIdentity(token); + } } @Override public void onConnectionStateChanged(BluetoothDevice device, int state) { - clearCallingIdentity(); - mExecutor.execute(() -> mCallback.onConnectionStateChanged(device, state)); + Attributable.setAttributionSource(device, mAttributionSource); + final long token = clearCallingIdentity(); + try { + mExecutor.execute(() -> mCallback.onConnectionStateChanged(device, state)); + } finally { + restoreCallingIdentity(token); + } } @Override public void onGetReport(BluetoothDevice device, byte type, byte id, int bufferSize) { - clearCallingIdentity(); - mExecutor.execute(() -> mCallback.onGetReport(device, type, id, bufferSize)); + Attributable.setAttributionSource(device, mAttributionSource); + final long token = clearCallingIdentity(); + try { + mExecutor.execute(() -> mCallback.onGetReport(device, type, id, bufferSize)); + } finally { + restoreCallingIdentity(token); + } } @Override public void onSetReport(BluetoothDevice device, byte type, byte id, byte[] data) { - clearCallingIdentity(); - mExecutor.execute(() -> mCallback.onSetReport(device, type, id, data)); + Attributable.setAttributionSource(device, mAttributionSource); + final long token = clearCallingIdentity(); + try { + mExecutor.execute(() -> mCallback.onSetReport(device, type, id, data)); + } finally { + restoreCallingIdentity(token); + } } @Override public void onSetProtocol(BluetoothDevice device, byte protocol) { - clearCallingIdentity(); - mExecutor.execute(() -> mCallback.onSetProtocol(device, protocol)); + Attributable.setAttributionSource(device, mAttributionSource); + final long token = clearCallingIdentity(); + try { + mExecutor.execute(() -> mCallback.onSetProtocol(device, protocol)); + } finally { + restoreCallingIdentity(token); + } } @Override public void onInterruptData(BluetoothDevice device, byte reportId, byte[] data) { - clearCallingIdentity(); - mExecutor.execute(() -> mCallback.onInterruptData(device, reportId, data)); + Attributable.setAttributionSource(device, mAttributionSource); + final long token = clearCallingIdentity(); + try { + mExecutor.execute(() -> mCallback.onInterruptData(device, reportId, data)); + } finally { + restoreCallingIdentity(token); + } } @Override public void onVirtualCableUnplug(BluetoothDevice device) { - clearCallingIdentity(); - mExecutor.execute(() -> mCallback.onVirtualCableUnplug(device)); + Attributable.setAttributionSource(device, mAttributionSource); + final long token = clearCallingIdentity(); + try { + mExecutor.execute(() -> mCallback.onVirtualCableUnplug(device)); + } finally { + restoreCallingIdentity(token); + } } } - private BluetoothAdapter mAdapter; + private final BluetoothAdapter mAdapter; + private final AttributionSource mAttributionSource; private final BluetoothProfileConnector<IBluetoothHidDevice> mProfileConnector = new BluetoothProfileConnector(this, BluetoothProfile.HID_DEVICE, "BluetoothHidDevice", IBluetoothHidDevice.class.getName()) { @@ -393,8 +436,9 @@ public final class BluetoothHidDevice implements BluetoothProfile { } }; - BluetoothHidDevice(Context context, ServiceListener listener) { - mAdapter = BluetoothAdapter.getDefaultAdapter(); + BluetoothHidDevice(Context context, ServiceListener listener, BluetoothAdapter adapter) { + mAdapter = adapter; + mAttributionSource = adapter.getAttributionSource(); mProfileConnector.connect(context, listener); } @@ -408,11 +452,14 @@ public final class BluetoothHidDevice implements BluetoothProfile { /** {@inheritDoc} */ @Override + @RequiresBluetoothConnectPermission + @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) public List<BluetoothDevice> getConnectedDevices() { final IBluetoothHidDevice service = getService(); if (service != null) { try { - return service.getConnectedDevices(); + return Attributable.setAttributionSource( + service.getConnectedDevices(mAttributionSource), mAttributionSource); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -425,11 +472,15 @@ public final class BluetoothHidDevice implements BluetoothProfile { /** {@inheritDoc} */ @Override + @RequiresBluetoothConnectPermission + @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { final IBluetoothHidDevice service = getService(); if (service != null) { try { - return service.getDevicesMatchingConnectionStates(states); + return Attributable.setAttributionSource( + service.getDevicesMatchingConnectionStates(states, mAttributionSource), + mAttributionSource); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -442,11 +493,13 @@ public final class BluetoothHidDevice implements BluetoothProfile { /** {@inheritDoc} */ @Override + @RequiresBluetoothConnectPermission + @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) public int getConnectionState(BluetoothDevice device) { final IBluetoothHidDevice service = getService(); if (service != null) { try { - return service.getConnectionState(device); + return service.getConnectionState(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -480,6 +533,8 @@ public final class BluetoothHidDevice implements BluetoothProfile { * object is required. * @return true if the command is successfully sent; otherwise false. */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean registerApp( BluetoothHidDeviceAppSdpSettings sdp, BluetoothHidDeviceAppQosSettings inQos, @@ -503,8 +558,8 @@ public final class BluetoothHidDevice implements BluetoothProfile { final IBluetoothHidDevice service = getService(); if (service != null) { try { - CallbackWrapper cbw = new CallbackWrapper(executor, callback); - result = service.registerApp(sdp, inQos, outQos, cbw); + CallbackWrapper cbw = new CallbackWrapper(executor, callback, mAttributionSource); + result = service.registerApp(sdp, inQos, outQos, cbw, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -525,13 +580,15 @@ public final class BluetoothHidDevice implements BluetoothProfile { * * @return true if the command is successfully sent; otherwise false. */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean unregisterApp() { boolean result = false; final IBluetoothHidDevice service = getService(); if (service != null) { try { - result = service.unregisterApp(); + result = service.unregisterApp(mAttributionSource); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -550,13 +607,15 @@ public final class BluetoothHidDevice implements BluetoothProfile { * @param data Report data, not including Report Id. * @return true if the command is successfully sent; otherwise false. */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean sendReport(BluetoothDevice device, int id, byte[] data) { boolean result = false; final IBluetoothHidDevice service = getService(); if (service != null) { try { - result = service.sendReport(device, id, data); + result = service.sendReport(device, id, data, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -576,13 +635,15 @@ public final class BluetoothHidDevice implements BluetoothProfile { * @param data Report data, not including Report Id. * @return true if the command is successfully sent; otherwise false. */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean replyReport(BluetoothDevice device, byte type, byte id, byte[] data) { boolean result = false; final IBluetoothHidDevice service = getService(); if (service != null) { try { - result = service.replyReport(device, type, id, data); + result = service.replyReport(device, type, id, data, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -600,13 +661,15 @@ public final class BluetoothHidDevice implements BluetoothProfile { * @param error Error to be sent for SET_REPORT via HANDSHAKE. * @return true if the command is successfully sent; otherwise false. */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean reportError(BluetoothDevice device, byte error) { boolean result = false; final IBluetoothHidDevice service = getService(); if (service != null) { try { - result = service.reportError(device, error); + result = service.reportError(device, error, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -623,12 +686,14 @@ public final class BluetoothHidDevice implements BluetoothProfile { * @return the current user name, or empty string if cannot get the name * {@hide} */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public String getUserAppName() { final IBluetoothHidDevice service = getService(); if (service != null) { try { - return service.getUserAppName(); + return service.getUserAppName(mAttributionSource); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -647,13 +712,15 @@ public final class BluetoothHidDevice implements BluetoothProfile { * * @return true if the command is successfully sent; otherwise false. */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean connect(BluetoothDevice device) { boolean result = false; final IBluetoothHidDevice service = getService(); if (service != null) { try { - result = service.connect(device); + result = service.connect(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -671,13 +738,15 @@ public final class BluetoothHidDevice implements BluetoothProfile { * * @return true if the command is successfully sent; otherwise false. */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean disconnect(BluetoothDevice device) { boolean result = false; final IBluetoothHidDevice service = getService(); if (service != null) { try { - result = service.disconnect(device); + result = service.disconnect(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -706,7 +775,11 @@ public final class BluetoothHidDevice implements BluetoothProfile { * @hide */ @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public boolean setConnectionPolicy(@NonNull BluetoothDevice device, @ConnectionPolicy int connectionPolicy) { log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); @@ -718,7 +791,7 @@ public final class BluetoothHidDevice implements BluetoothProfile { && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) { return false; } - return service.setConnectionPolicy(device, connectionPolicy); + return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource); } if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; diff --git a/framework/java/android/bluetooth/BluetoothHidHost.java b/framework/java/android/bluetooth/BluetoothHidHost.java index 9561d93838..fb4cbb2eb1 100644 --- a/framework/java/android/bluetooth/BluetoothHidHost.java +++ b/framework/java/android/bluetooth/BluetoothHidHost.java @@ -21,8 +21,13 @@ import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; +import android.bluetooth.annotations.RequiresBluetoothConnectPermission; +import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission; +import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; import android.annotation.SuppressLint; import android.annotation.SystemApi; +import android.content.Attributable; +import android.content.AttributionSource; import android.content.Context; import android.os.Binder; import android.os.IBinder; @@ -65,11 +70,11 @@ public final class BluetoothHidHost implements BluetoothProfile { * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}. - * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to - * receive. */ @SuppressLint("ActionValue") + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.input.profile.action.CONNECTION_STATE_CHANGED"; @@ -77,6 +82,8 @@ public final class BluetoothHidHost implements BluetoothProfile { /** * @hide */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_PROTOCOL_MODE_CHANGED = "android.bluetooth.input.profile.action.PROTOCOL_MODE_CHANGED"; @@ -84,6 +91,8 @@ public final class BluetoothHidHost implements BluetoothProfile { /** * @hide */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_HANDSHAKE = "android.bluetooth.input.profile.action.HANDSHAKE"; @@ -91,6 +100,8 @@ public final class BluetoothHidHost implements BluetoothProfile { /** * @hide */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_REPORT = "android.bluetooth.input.profile.action.REPORT"; @@ -98,6 +109,8 @@ public final class BluetoothHidHost implements BluetoothProfile { /** * @hide */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_VIRTUAL_UNPLUG_STATUS = "android.bluetooth.input.profile.action.VIRTUAL_UNPLUG_STATUS"; @@ -105,6 +118,8 @@ public final class BluetoothHidHost implements BluetoothProfile { /** * @hide */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_IDLE_TIME_CHANGED = "android.bluetooth.input.profile.action.IDLE_TIME_CHANGED"; @@ -223,7 +238,8 @@ public final class BluetoothHidHost implements BluetoothProfile { public static final String EXTRA_IDLE_TIME = "android.bluetooth.BluetoothHidHost.extra.IDLE_TIME"; - private BluetoothAdapter mAdapter; + private final BluetoothAdapter mAdapter; + private final AttributionSource mAttributionSource; private final BluetoothProfileConnector<IBluetoothHidHost> mProfileConnector = new BluetoothProfileConnector(this, BluetoothProfile.HID_HOST, "BluetoothHidHost", IBluetoothHidHost.class.getName()) { @@ -237,8 +253,10 @@ public final class BluetoothHidHost implements BluetoothProfile { * Create a BluetoothHidHost proxy object for interacting with the local * Bluetooth Service which handles the InputDevice profile */ - /*package*/ BluetoothHidHost(Context context, ServiceListener listener) { - mAdapter = BluetoothAdapter.getDefaultAdapter(); + /* package */ BluetoothHidHost(Context context, ServiceListener listener, + BluetoothAdapter adapter) { + mAdapter = adapter; + mAttributionSource = adapter.getAttributionSource(); mProfileConnector.connect(context, listener); } @@ -267,13 +285,17 @@ public final class BluetoothHidHost implements BluetoothProfile { * @return false on immediate error, true otherwise * @hide */ - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public boolean connect(BluetoothDevice device) { if (DBG) log("connect(" + device + ")"); final IBluetoothHidHost service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { try { - return service.connect(device); + return service.connect(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return false; @@ -305,13 +327,17 @@ public final class BluetoothHidHost implements BluetoothProfile { * @return false on immediate error, true otherwise * @hide */ - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public boolean disconnect(BluetoothDevice device) { if (DBG) log("disconnect(" + device + ")"); final IBluetoothHidHost service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { try { - return service.disconnect(device); + return service.disconnect(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return false; @@ -328,13 +354,15 @@ public final class BluetoothHidHost implements BluetoothProfile { */ @SystemApi @Override - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) public @NonNull List<BluetoothDevice> getConnectedDevices() { if (VDBG) log("getConnectedDevices()"); final IBluetoothHidHost service = getService(); if (service != null && isEnabled()) { try { - return service.getConnectedDevices(); + return Attributable.setAttributionSource( + service.getConnectedDevices(mAttributionSource), mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return new ArrayList<BluetoothDevice>(); @@ -350,12 +378,16 @@ public final class BluetoothHidHost implements BluetoothProfile { * @hide */ @Override + @RequiresBluetoothConnectPermission + @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { if (VDBG) log("getDevicesMatchingStates()"); final IBluetoothHidHost service = getService(); if (service != null && isEnabled()) { try { - return service.getDevicesMatchingConnectionStates(states); + return Attributable.setAttributionSource( + service.getDevicesMatchingConnectionStates(states, mAttributionSource), + mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return new ArrayList<BluetoothDevice>(); @@ -372,7 +404,8 @@ public final class BluetoothHidHost implements BluetoothProfile { */ @SystemApi @Override - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) public int getConnectionState(@NonNull BluetoothDevice device) { if (VDBG) log("getState(" + device + ")"); if (device == null) { @@ -381,7 +414,7 @@ public final class BluetoothHidHost implements BluetoothProfile { final IBluetoothHidHost service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { try { - return service.getConnectionState(device); + return service.getConnectionState(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return BluetoothProfile.STATE_DISCONNECTED; @@ -402,7 +435,11 @@ public final class BluetoothHidHost implements BluetoothProfile { * @return true if priority is set, false on error * @hide */ - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public boolean setPriority(BluetoothDevice device, int priority) { if (DBG) log("setPriority(" + device + ", " + priority + ")"); return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority)); @@ -421,7 +458,11 @@ public final class BluetoothHidHost implements BluetoothProfile { * @hide */ @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public boolean setConnectionPolicy(@NonNull BluetoothDevice device, @ConnectionPolicy int connectionPolicy) { if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); @@ -435,7 +476,7 @@ public final class BluetoothHidHost implements BluetoothProfile { return false; } try { - return service.setConnectionPolicy(device, connectionPolicy); + return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return false; @@ -455,7 +496,11 @@ public final class BluetoothHidHost implements BluetoothProfile { * @return priority of the device * @hide */ - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public int getPriority(BluetoothDevice device) { if (VDBG) log("getPriority(" + device + ")"); return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device)); @@ -473,7 +518,11 @@ public final class BluetoothHidHost implements BluetoothProfile { * @hide */ @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) { if (VDBG) log("getConnectionPolicy(" + device + ")"); if (device == null) { @@ -482,7 +531,7 @@ public final class BluetoothHidHost implements BluetoothProfile { final IBluetoothHidHost service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { try { - return service.getConnectionPolicy(device); + return service.getConnectionPolicy(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; @@ -503,18 +552,19 @@ public final class BluetoothHidHost implements BluetoothProfile { /** * Initiate virtual unplug for a HID input device. * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission. - * * @param device Remote Bluetooth Device * @return false on immediate error, true otherwise * @hide */ + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean virtualUnplug(BluetoothDevice device) { if (DBG) log("virtualUnplug(" + device + ")"); final IBluetoothHidHost service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { try { - return service.virtualUnplug(device); + return service.virtualUnplug(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return false; @@ -529,18 +579,19 @@ public final class BluetoothHidHost implements BluetoothProfile { /** * Send Get_Protocol_Mode command to the connected HID input device. * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission. - * * @param device Remote Bluetooth Device * @return false on immediate error, true otherwise * @hide */ + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean getProtocolMode(BluetoothDevice device) { if (VDBG) log("getProtocolMode(" + device + ")"); final IBluetoothHidHost service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { try { - return service.getProtocolMode(device); + return service.getProtocolMode(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return false; @@ -553,18 +604,19 @@ public final class BluetoothHidHost implements BluetoothProfile { /** * Send Set_Protocol_Mode command to the connected HID input device. * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission. - * * @param device Remote Bluetooth Device * @return false on immediate error, true otherwise * @hide */ + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean setProtocolMode(BluetoothDevice device, int protocolMode) { if (DBG) log("setProtocolMode(" + device + ")"); final IBluetoothHidHost service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { try { - return service.setProtocolMode(device, protocolMode); + return service.setProtocolMode(device, protocolMode, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return false; @@ -577,8 +629,6 @@ public final class BluetoothHidHost implements BluetoothProfile { /** * Send Get_Report command to the connected HID input device. * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission. - * * @param device Remote Bluetooth Device * @param reportType Report type * @param reportId Report ID @@ -586,6 +636,9 @@ public final class BluetoothHidHost implements BluetoothProfile { * @return false on immediate error, true otherwise * @hide */ + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean getReport(BluetoothDevice device, byte reportType, byte reportId, int bufferSize) { if (VDBG) { @@ -595,7 +648,8 @@ public final class BluetoothHidHost implements BluetoothProfile { final IBluetoothHidHost service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { try { - return service.getReport(device, reportType, reportId, bufferSize); + return service.getReport(device, reportType, reportId, bufferSize, + mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return false; @@ -608,20 +662,21 @@ public final class BluetoothHidHost implements BluetoothProfile { /** * Send Set_Report command to the connected HID input device. * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission. - * * @param device Remote Bluetooth Device * @param reportType Report type * @param report Report receiving buffer size * @return false on immediate error, true otherwise * @hide */ + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean setReport(BluetoothDevice device, byte reportType, String report) { if (VDBG) log("setReport(" + device + "), reportType=" + reportType + " report=" + report); final IBluetoothHidHost service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { try { - return service.setReport(device, reportType, report); + return service.setReport(device, reportType, report, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return false; @@ -634,19 +689,20 @@ public final class BluetoothHidHost implements BluetoothProfile { /** * Send Send_Data command to the connected HID input device. * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission. - * * @param device Remote Bluetooth Device * @param report Report to send * @return false on immediate error, true otherwise * @hide */ + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean sendData(BluetoothDevice device, String report) { if (DBG) log("sendData(" + device + "), report=" + report); final IBluetoothHidHost service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { try { - return service.sendData(device, report); + return service.sendData(device, report, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return false; @@ -659,18 +715,19 @@ public final class BluetoothHidHost implements BluetoothProfile { /** * Send Get_Idle_Time command to the connected HID input device. * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission. - * * @param device Remote Bluetooth Device * @return false on immediate error, true otherwise * @hide */ + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean getIdleTime(BluetoothDevice device) { if (DBG) log("getIdletime(" + device + ")"); final IBluetoothHidHost service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { try { - return service.getIdleTime(device); + return service.getIdleTime(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return false; @@ -683,19 +740,20 @@ public final class BluetoothHidHost implements BluetoothProfile { /** * Send Set_Idle_Time command to the connected HID input device. * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission. - * * @param device Remote Bluetooth Device * @param idleTime Idle time to be set on HID Device * @return false on immediate error, true otherwise * @hide */ + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean setIdleTime(BluetoothDevice device, byte idleTime) { if (DBG) log("setIdletime(" + device + "), idleTime=" + idleTime); final IBluetoothHidHost service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { try { - return service.setIdleTime(device, idleTime); + return service.setIdleTime(device, idleTime, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return false; diff --git a/framework/java/android/bluetooth/BluetoothInputStream.java b/framework/java/android/bluetooth/BluetoothInputStream.java index 8eb79b248d..95f9229f04 100644 --- a/framework/java/android/bluetooth/BluetoothInputStream.java +++ b/framework/java/android/bluetooth/BluetoothInputStream.java @@ -16,6 +16,8 @@ package android.bluetooth; +import android.annotation.SuppressLint; + import java.io.IOException; import java.io.InputStream; @@ -26,6 +28,7 @@ import java.io.InputStream; * * @hide */ +@SuppressLint("AndroidFrameworkBluetoothPermission") /*package*/ final class BluetoothInputStream extends InputStream { private BluetoothSocket mSocket; diff --git a/framework/java/android/bluetooth/BluetoothLeAudio.java b/framework/java/android/bluetooth/BluetoothLeAudio.java index 75fcab9359..c438dd34f8 100644 --- a/framework/java/android/bluetooth/BluetoothLeAudio.java +++ b/framework/java/android/bluetooth/BluetoothLeAudio.java @@ -23,6 +23,11 @@ import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; +import android.annotation.SuppressLint; +import android.bluetooth.annotations.RequiresBluetoothConnectPermission; +import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; +import android.content.Attributable; +import android.content.AttributionSource; import android.content.Context; import android.os.Binder; import android.os.IBinder; @@ -65,10 +70,10 @@ public final class BluetoothLeAudio implements BluetoothProfile, AutoCloseable { * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}. - * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to - * receive. */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED = "android.bluetooth.action.LE_AUDIO_CONNECTION_STATE_CHANGED"; @@ -82,11 +87,11 @@ public final class BluetoothLeAudio implements BluetoothProfile, AutoCloseable { * be null if no device is active. </li> * </ul> * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to - * receive. - * * @hide */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED = "android.bluetooth.action.LE_AUDIO_ACTIVE_DEVICE_CHANGED"; @@ -98,7 +103,8 @@ public final class BluetoothLeAudio implements BluetoothProfile, AutoCloseable { */ public static final int GROUP_ID_INVALID = IBluetoothLeAudio.LE_AUDIO_GROUP_ID_INVALID; - private BluetoothAdapter mAdapter; + private final BluetoothAdapter mAdapter; + private final AttributionSource mAttributionSource; private final BluetoothProfileConnector<IBluetoothLeAudio> mProfileConnector = new BluetoothProfileConnector(this, BluetoothProfile.LE_AUDIO, "BluetoothLeAudio", IBluetoothLeAudio.class.getName()) { @@ -112,8 +118,10 @@ public final class BluetoothLeAudio implements BluetoothProfile, AutoCloseable { * Create a BluetoothLeAudio proxy object for interacting with the local * Bluetooth LeAudio service. */ - /*package*/ BluetoothLeAudio(Context context, ServiceListener listener) { - mAdapter = BluetoothAdapter.getDefaultAdapter(); + /* package */ BluetoothLeAudio(Context context, ServiceListener listener, + BluetoothAdapter adapter) { + mAdapter = adapter; + mAttributionSource = adapter.getAttributionSource(); mProfileConnector.connect(context, listener); mCloseGuard = new CloseGuard(); mCloseGuard.open("close"); @@ -122,7 +130,6 @@ public final class BluetoothLeAudio implements BluetoothProfile, AutoCloseable { /** * @hide */ - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) public void close() { mProfileConnector.disconnect(); } @@ -131,7 +138,6 @@ public final class BluetoothLeAudio implements BluetoothProfile, AutoCloseable { return mProfileConnector.getService(); } - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) protected void finalize() { if (mCloseGuard != null) { mCloseGuard.warnIfOpen(); @@ -154,13 +160,14 @@ public final class BluetoothLeAudio implements BluetoothProfile, AutoCloseable { * @return false on immediate error, true otherwise * @hide */ - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean connect(@Nullable BluetoothDevice device) { if (DBG) log("connect(" + device + ")"); try { final IBluetoothLeAudio service = getService(); if (service != null && mAdapter.isEnabled() && isValidDevice(device)) { - return service.connect(device); + return service.connect(device, mAttributionSource); } if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; @@ -193,13 +200,14 @@ public final class BluetoothLeAudio implements BluetoothProfile, AutoCloseable { * @return false on immediate error, true otherwise * @hide */ - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean disconnect(@Nullable BluetoothDevice device) { if (DBG) log("disconnect(" + device + ")"); try { final IBluetoothLeAudio service = getService(); if (service != null && mAdapter.isEnabled() && isValidDevice(device)) { - return service.disconnect(device); + return service.disconnect(device, mAttributionSource); } if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; @@ -213,12 +221,15 @@ public final class BluetoothLeAudio implements BluetoothProfile, AutoCloseable { * {@inheritDoc} */ @Override + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public @NonNull List<BluetoothDevice> getConnectedDevices() { if (VDBG) log("getConnectedDevices()"); try { final IBluetoothLeAudio service = getService(); if (service != null && mAdapter.isEnabled()) { - return service.getConnectedDevices(); + return Attributable.setAttributionSource( + service.getConnectedDevices(mAttributionSource), mAttributionSource); } if (service == null) Log.w(TAG, "Proxy not attached to service"); return new ArrayList<BluetoothDevice>(); @@ -232,13 +243,17 @@ public final class BluetoothLeAudio implements BluetoothProfile, AutoCloseable { * {@inheritDoc} */ @Override + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public @NonNull List<BluetoothDevice> getDevicesMatchingConnectionStates( @NonNull int[] states) { if (VDBG) log("getDevicesMatchingStates()"); try { final IBluetoothLeAudio service = getService(); if (service != null && mAdapter.isEnabled()) { - return service.getDevicesMatchingConnectionStates(states); + return Attributable.setAttributionSource( + service.getDevicesMatchingConnectionStates(states, mAttributionSource), + mAttributionSource); } if (service == null) Log.w(TAG, "Proxy not attached to service"); return new ArrayList<BluetoothDevice>(); @@ -252,14 +267,16 @@ public final class BluetoothLeAudio implements BluetoothProfile, AutoCloseable { * {@inheritDoc} */ @Override - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public @BtProfileState int getConnectionState(@NonNull BluetoothDevice device) { if (VDBG) log("getState(" + device + ")"); try { final IBluetoothLeAudio service = getService(); if (service != null && mAdapter.isEnabled() && isValidDevice(device)) { - return service.getConnectionState(device); + return service.getConnectionState(device, mAttributionSource); } if (service == null) Log.w(TAG, "Proxy not attached to service"); return BluetoothProfile.STATE_DISCONNECTED; @@ -289,14 +306,15 @@ public final class BluetoothLeAudio implements BluetoothProfile, AutoCloseable { * @return false on immediate error, true otherwise * @hide */ - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean setActiveDevice(@Nullable BluetoothDevice device) { if (DBG) log("setActiveDevice(" + device + ")"); try { final IBluetoothLeAudio service = getService(); if (service != null && mAdapter.isEnabled() && ((device == null) || isValidDevice(device))) { - service.setActiveDevice(device); + service.setActiveDevice(device, mAttributionSource); return true; } if (service == null) Log.w(TAG, "Proxy not attached to service"); @@ -314,13 +332,16 @@ public final class BluetoothLeAudio implements BluetoothProfile, AutoCloseable { * @hide */ @NonNull - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public List<BluetoothDevice> getActiveDevices() { if (VDBG) log("getActiveDevices()"); try { final IBluetoothLeAudio service = getService(); if (service != null && mAdapter.isEnabled()) { - return service.getActiveDevices(); + return Attributable.setAttributionSource( + service.getActiveDevices(mAttributionSource), mAttributionSource); } if (service == null) Log.w(TAG, "Proxy not attached to service"); return new ArrayList<>(); @@ -336,13 +357,15 @@ public final class BluetoothLeAudio implements BluetoothProfile, AutoCloseable { * @param device LE Audio capable device * @return group id that this device currently belongs to */ - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getGroupId(@NonNull BluetoothDevice device) { if (VDBG) log("getGroupId()"); try { final IBluetoothLeAudio service = getService(); if (service != null && mAdapter.isEnabled()) { - return service.getGroupId(device); + return service.getGroupId(device, mAttributionSource); } if (service == null) Log.w(TAG, "Proxy not attached to service"); return GROUP_ID_INVALID; @@ -364,7 +387,11 @@ public final class BluetoothLeAudio implements BluetoothProfile, AutoCloseable { * @return true if connectionPolicy is set, false on error * @hide */ - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public boolean setConnectionPolicy(@NonNull BluetoothDevice device, @ConnectionPolicy int connectionPolicy) { if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); @@ -376,7 +403,7 @@ public final class BluetoothLeAudio implements BluetoothProfile, AutoCloseable { && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) { return false; } - return service.setConnectionPolicy(device, connectionPolicy); + return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource); } if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; @@ -397,14 +424,15 @@ public final class BluetoothLeAudio implements BluetoothProfile, AutoCloseable { * @return connection policy of the device * @hide */ - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public @ConnectionPolicy int getConnectionPolicy(@Nullable BluetoothDevice device) { if (VDBG) log("getConnectionPolicy(" + device + ")"); try { final IBluetoothLeAudio service = getService(); if (service != null && mAdapter.isEnabled() && isValidDevice(device)) { - return service.getConnectionPolicy(device); + return service.getConnectionPolicy(device, mAttributionSource); } if (service == null) Log.w(TAG, "Proxy not attached to service"); return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; diff --git a/framework/java/android/bluetooth/BluetoothManager.java b/framework/java/android/bluetooth/BluetoothManager.java index d5c1c3e2d6..c21362cd89 100644 --- a/framework/java/android/bluetooth/BluetoothManager.java +++ b/framework/java/android/bluetooth/BluetoothManager.java @@ -16,15 +16,21 @@ package android.bluetooth; -import android.Manifest; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.RequiresFeature; +import android.annotation.RequiresNoPermission; import android.annotation.RequiresPermission; import android.annotation.SystemService; +import android.app.ActivityThread; +import android.app.AppGlobals; +import android.bluetooth.annotations.RequiresBluetoothConnectPermission; +import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; +import android.content.Attributable; +import android.content.AttributionSource; import android.content.Context; import android.content.pm.PackageManager; -import android.os.IBinder; import android.os.RemoteException; -import android.os.ServiceManager; import android.util.Log; import java.util.ArrayList; @@ -56,34 +62,41 @@ public final class BluetoothManager { private static final String TAG = "BluetoothManager"; private static final boolean DBG = false; + private final AttributionSource mAttributionSource; private final BluetoothAdapter mAdapter; /** * @hide */ public BluetoothManager(Context context) { - if (context.getAttributionTag() == null) { - context = context.getApplicationContext(); - if (context == null) { - throw new IllegalArgumentException( - "context not associated with any application (using a mock context?)"); - } + mAttributionSource = resolveAttributionSource(context); + mAdapter = BluetoothAdapter.createAdapter(mAttributionSource); + } - mAdapter = BluetoothAdapter.getDefaultAdapter(); - } else { - IBinder b = ServiceManager.getService(BluetoothAdapter.BLUETOOTH_MANAGER_SERVICE); - if (b != null) { - mAdapter = new BluetoothAdapter(IBluetoothManager.Stub.asInterface(b)); - } else { - Log.e(TAG, "Bluetooth binder is null"); - mAdapter = null; + /** {@hide} */ + public static @NonNull AttributionSource resolveAttributionSource(@Nullable Context context) { + AttributionSource res = null; + if (context != null) { + res = context.getAttributionSource(); + } + if (res == null) { + res = ActivityThread.currentAttributionSource(); + } + if (res == null) { + int uid = android.os.Process.myUid(); + if (uid == android.os.Process.ROOT_UID) { + uid = android.os.Process.SYSTEM_UID; + } + try { + res = new AttributionSource(uid, + AppGlobals.getPackageManager().getPackagesForUid(uid)[0], null); + } catch (RemoteException ignored) { } } - - // Context is not initialized in constructor - if (mAdapter != null) { - mAdapter.setContext(context); + if (res == null) { + throw new IllegalStateException("Failed to resolve AttributionSource"); } + return res; } /** @@ -91,6 +104,7 @@ public final class BluetoothManager { * * @return the BLUETOOTH Adapter */ + @RequiresNoPermission public BluetoothAdapter getAdapter() { return mAdapter; } @@ -109,7 +123,9 @@ public final class BluetoothManager { * {@link BluetoothProfile#STATE_CONNECTING}, {@link BluetoothProfile#STATE_DISCONNECTED}, * {@link BluetoothProfile#STATE_DISCONNECTING} */ - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getConnectionState(BluetoothDevice device, int profile) { if (DBG) Log.d(TAG, "getConnectionState()"); @@ -136,27 +152,14 @@ public final class BluetoothManager { * @param profile GATT or GATT_SERVER * @return List of devices. The list will be empty on error. */ - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public List<BluetoothDevice> getConnectedDevices(int profile) { if (DBG) Log.d(TAG, "getConnectedDevices"); - if (profile != BluetoothProfile.GATT && profile != BluetoothProfile.GATT_SERVER) { - throw new IllegalArgumentException("Profile not supported: " + profile); - } - - List<BluetoothDevice> connectedDevices = new ArrayList<BluetoothDevice>(); - - try { - IBluetoothManager managerService = mAdapter.getBluetoothManager(); - IBluetoothGatt iGatt = managerService.getBluetoothGatt(); - if (iGatt == null) return connectedDevices; - - connectedDevices = iGatt.getDevicesMatchingConnectionStates( - new int[]{BluetoothProfile.STATE_CONNECTED}); - } catch (RemoteException e) { - Log.e(TAG, "", e); - } - - return connectedDevices; + return getDevicesMatchingConnectionStates(profile, new int[] { + BluetoothProfile.STATE_CONNECTED + }); } /** @@ -177,7 +180,9 @@ public final class BluetoothManager { * {@link BluetoothProfile#STATE_DISCONNECTING}, * @return List of devices. The list will be empty on error. */ - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public List<BluetoothDevice> getDevicesMatchingConnectionStates(int profile, int[] states) { if (DBG) Log.d(TAG, "getDevicesMatchingConnectionStates"); @@ -191,7 +196,9 @@ public final class BluetoothManager { IBluetoothManager managerService = mAdapter.getBluetoothManager(); IBluetoothGatt iGatt = managerService.getBluetoothGatt(); if (iGatt == null) return devices; - devices = iGatt.getDevicesMatchingConnectionStates(states); + devices = Attributable.setAttributionSource( + iGatt.getDevicesMatchingConnectionStates(states, mAttributionSource), + mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "", e); } @@ -210,6 +217,8 @@ public final class BluetoothManager { * @param callback GATT server callback handler that will receive asynchronous callbacks. * @return BluetoothGattServer instance */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public BluetoothGattServer openGattServer(Context context, BluetoothGattServerCallback callback) { @@ -229,6 +238,8 @@ public final class BluetoothManager { * @return BluetoothGattServer instance * @hide */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public BluetoothGattServer openGattServer(Context context, BluetoothGattServerCallback callback, boolean eatt_support) { return (openGattServer(context, callback, BluetoothDevice.TRANSPORT_AUTO, eatt_support)); @@ -249,6 +260,8 @@ public final class BluetoothManager { * @return BluetoothGattServer instance * @hide */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public BluetoothGattServer openGattServer(Context context, BluetoothGattServerCallback callback, int transport) { return (openGattServer(context, callback, transport, false)); @@ -270,6 +283,8 @@ public final class BluetoothManager { * @return BluetoothGattServer instance * @hide */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public BluetoothGattServer openGattServer(Context context, BluetoothGattServerCallback callback, int transport, boolean eatt_support) { if (context == null || callback == null) { @@ -286,7 +301,8 @@ public final class BluetoothManager { Log.e(TAG, "Fail to get GATT Server connection"); return null; } - BluetoothGattServer mGattServer = new BluetoothGattServer(iGatt, transport); + BluetoothGattServer mGattServer = + new BluetoothGattServer(iGatt, transport, mAdapter); Boolean regStatus = mGattServer.registerCallback(callback, eatt_support); return regStatus ? mGattServer : null; } catch (RemoteException e) { diff --git a/framework/java/android/bluetooth/BluetoothMap.java b/framework/java/android/bluetooth/BluetoothMap.java index 3554995400..86796519df 100644 --- a/framework/java/android/bluetooth/BluetoothMap.java +++ b/framework/java/android/bluetooth/BluetoothMap.java @@ -18,10 +18,16 @@ package android.bluetooth; import android.Manifest; import android.annotation.NonNull; +import android.annotation.RequiresNoPermission; import android.annotation.RequiresPermission; +import android.annotation.SdkConstant; import android.annotation.SuppressLint; import android.annotation.SystemApi; +import android.annotation.SdkConstant.SdkConstantType; +import android.bluetooth.annotations.RequiresBluetoothConnectPermission; import android.compat.annotation.UnsupportedAppUsage; +import android.content.Attributable; +import android.content.AttributionSource; import android.content.Context; import android.os.Binder; import android.os.Build; @@ -51,6 +57,9 @@ public final class BluetoothMap implements BluetoothProfile, AutoCloseable { /** @hide */ @SuppressLint("ActionValue") @SystemApi + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.map.profile.action.CONNECTION_STATE_CHANGED"; @@ -72,7 +81,8 @@ public final class BluetoothMap implements BluetoothProfile, AutoCloseable { */ public static final int RESULT_CANCELED = 2; - private BluetoothAdapter mAdapter; + private final BluetoothAdapter mAdapter; + private final AttributionSource mAttributionSource; private final BluetoothProfileConnector<IBluetoothMap> mProfileConnector = new BluetoothProfileConnector(this, BluetoothProfile.MAP, "BluetoothMap", IBluetoothMap.class.getName()) { @@ -85,15 +95,16 @@ public final class BluetoothMap implements BluetoothProfile, AutoCloseable { /** * Create a BluetoothMap proxy object. */ - /*package*/ BluetoothMap(Context context, ServiceListener listener) { + /* package */ BluetoothMap(Context context, ServiceListener listener, + BluetoothAdapter adapter) { if (DBG) Log.d(TAG, "Create BluetoothMap proxy object"); - mAdapter = BluetoothAdapter.getDefaultAdapter(); + mAdapter = adapter; + mAttributionSource = adapter.getAttributionSource(); mProfileConnector.connect(context, listener); mCloseGuard = new CloseGuard(); mCloseGuard.open("close"); } - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) protected void finalize() { if (mCloseGuard != null) { mCloseGuard.warnIfOpen(); @@ -110,7 +121,6 @@ public final class BluetoothMap implements BluetoothProfile, AutoCloseable { * @hide */ @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) public void close() { if (VDBG) log("close()"); mProfileConnector.disconnect(); @@ -128,12 +138,14 @@ public final class BluetoothMap implements BluetoothProfile, AutoCloseable { * * @hide */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getState() { if (VDBG) log("getState()"); final IBluetoothMap service = getService(); if (service != null) { try { - return service.getState(); + return service.getState(mAttributionSource); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -152,12 +164,15 @@ public final class BluetoothMap implements BluetoothProfile, AutoCloseable { * * @hide */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public BluetoothDevice getClient() { if (VDBG) log("getClient()"); final IBluetoothMap service = getService(); if (service != null) { try { - return service.getClient(); + return Attributable.setAttributionSource( + service.getClient(mAttributionSource), mAttributionSource); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -175,12 +190,14 @@ public final class BluetoothMap implements BluetoothProfile, AutoCloseable { * * @hide */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean isConnected(BluetoothDevice device) { if (VDBG) log("isConnected(" + device + ")"); final IBluetoothMap service = getService(); if (service != null) { try { - return service.isConnected(device); + return service.isConnected(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -197,6 +214,7 @@ public final class BluetoothMap implements BluetoothProfile, AutoCloseable { * * @hide */ + @RequiresNoPermission public boolean connect(BluetoothDevice device) { if (DBG) log("connect(" + device + ")" + "not supported for MAPS"); return false; @@ -211,12 +229,14 @@ public final class BluetoothMap implements BluetoothProfile, AutoCloseable { * @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean disconnect(BluetoothDevice device) { if (DBG) log("disconnect(" + device + ")"); final IBluetoothMap service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { try { - return service.disconnect(device); + return service.disconnect(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return false; @@ -257,13 +277,18 @@ public final class BluetoothMap implements BluetoothProfile, AutoCloseable { * @hide */ @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public @NonNull List<BluetoothDevice> getConnectedDevices() { if (DBG) log("getConnectedDevices()"); final IBluetoothMap service = getService(); if (service != null && isEnabled()) { try { - return service.getConnectedDevices(); + return Attributable.setAttributionSource( + service.getConnectedDevices(mAttributionSource), mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return new ArrayList<BluetoothDevice>(); @@ -280,12 +305,16 @@ public final class BluetoothMap implements BluetoothProfile, AutoCloseable { * * @hide */ + @RequiresBluetoothConnectPermission + @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { if (DBG) log("getDevicesMatchingStates()"); final IBluetoothMap service = getService(); if (service != null && isEnabled()) { try { - return service.getDevicesMatchingConnectionStates(states); + return Attributable.setAttributionSource( + service.getDevicesMatchingConnectionStates(states, mAttributionSource), + mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return new ArrayList<BluetoothDevice>(); @@ -302,12 +331,14 @@ public final class BluetoothMap implements BluetoothProfile, AutoCloseable { * * @hide */ + @RequiresBluetoothConnectPermission + @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) public int getConnectionState(BluetoothDevice device) { if (DBG) log("getConnectionState(" + device + ")"); final IBluetoothMap service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { try { - return service.getConnectionState(device); + return service.getConnectionState(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return BluetoothProfile.STATE_DISCONNECTED; @@ -328,7 +359,11 @@ public final class BluetoothMap implements BluetoothProfile, AutoCloseable { * @return true if priority is set, false on error * @hide */ - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public boolean setPriority(BluetoothDevice device, int priority) { if (DBG) log("setPriority(" + device + ", " + priority + ")"); return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority)); @@ -347,7 +382,11 @@ public final class BluetoothMap implements BluetoothProfile, AutoCloseable { * @hide */ @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public boolean setConnectionPolicy(@NonNull BluetoothDevice device, @ConnectionPolicy int connectionPolicy) { if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); @@ -358,7 +397,7 @@ public final class BluetoothMap implements BluetoothProfile, AutoCloseable { return false; } try { - return service.setConnectionPolicy(device, connectionPolicy); + return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return false; @@ -378,7 +417,11 @@ public final class BluetoothMap implements BluetoothProfile, AutoCloseable { * @return priority of the device * @hide */ - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public int getPriority(BluetoothDevice device) { if (VDBG) log("getPriority(" + device + ")"); return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device)); @@ -396,13 +439,17 @@ public final class BluetoothMap implements BluetoothProfile, AutoCloseable { * @hide */ @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) { if (VDBG) log("getConnectionPolicy(" + device + ")"); final IBluetoothMap service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { try { - return service.getConnectionPolicy(device); + return service.getConnectionPolicy(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; @@ -417,13 +464,10 @@ public final class BluetoothMap implements BluetoothProfile, AutoCloseable { } private boolean isEnabled() { - BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); - if (adapter != null && adapter.getState() == BluetoothAdapter.STATE_ON) return true; - log("Bluetooth is Not enabled"); - return false; + return mAdapter.isEnabled(); } + private static boolean isValidDevice(BluetoothDevice device) { return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); } - } diff --git a/framework/java/android/bluetooth/BluetoothMapClient.java b/framework/java/android/bluetooth/BluetoothMapClient.java index 0312a2190a..042b58669a 100644 --- a/framework/java/android/bluetooth/BluetoothMapClient.java +++ b/framework/java/android/bluetooth/BluetoothMapClient.java @@ -20,9 +20,14 @@ import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; +import android.annotation.SdkConstant; +import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SystemApi; import android.app.PendingIntent; +import android.bluetooth.annotations.RequiresBluetoothConnectPermission; import android.compat.annotation.UnsupportedAppUsage; +import android.content.Attributable; +import android.content.AttributionSource; import android.content.Context; import android.net.Uri; import android.os.Binder; @@ -48,16 +53,27 @@ public final class BluetoothMapClient implements BluetoothProfile { private static final boolean VDBG = Log.isLoggable(TAG, Log.VERBOSE); /** @hide */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.mapmce.profile.action.CONNECTION_STATE_CHANGED"; /** @hide */ + @RequiresPermission(android.Manifest.permission.RECEIVE_SMS) + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_MESSAGE_RECEIVED = "android.bluetooth.mapmce.profile.action.MESSAGE_RECEIVED"; /* Actions to be used for pending intents */ /** @hide */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_MESSAGE_SENT_SUCCESSFULLY = "android.bluetooth.mapmce.profile.action.MESSAGE_SENT_SUCCESSFULLY"; /** @hide */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_MESSAGE_DELIVERED_SUCCESSFULLY = "android.bluetooth.mapmce.profile.action.MESSAGE_DELIVERED_SUCCESSFULLY"; @@ -66,6 +82,9 @@ public final class BluetoothMapClient implements BluetoothProfile { * * @hide */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_MESSAGE_READ_STATUS_CHANGED = "android.bluetooth.mapmce.profile.action.MESSAGE_READ_STATUS_CHANGED"; @@ -74,6 +93,9 @@ public final class BluetoothMapClient implements BluetoothProfile { * * @hide */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_MESSAGE_DELETED_STATUS_CHANGED = "android.bluetooth.mapmce.profile.action.MESSAGE_DELETED_STATUS_CHANGED"; @@ -152,7 +174,8 @@ public final class BluetoothMapClient implements BluetoothProfile { /** @hide */ public static final int DELETED = 3; - private BluetoothAdapter mAdapter; + private final BluetoothAdapter mAdapter; + private final AttributionSource mAttributionSource; private final BluetoothProfileConnector<IBluetoothMapClient> mProfileConnector = new BluetoothProfileConnector(this, BluetoothProfile.MAP_CLIENT, "BluetoothMapClient", IBluetoothMapClient.class.getName()) { @@ -165,9 +188,11 @@ public final class BluetoothMapClient implements BluetoothProfile { /** * Create a BluetoothMapClient proxy object. */ - /*package*/ BluetoothMapClient(Context context, ServiceListener listener) { + /* package */ BluetoothMapClient(Context context, ServiceListener listener, + BluetoothAdapter adapter) { if (DBG) Log.d(TAG, "Create BluetoothMapClient proxy object"); - mAdapter = BluetoothAdapter.getDefaultAdapter(); + mAdapter = adapter; + mAttributionSource = adapter.getAttributionSource(); mProfileConnector.connect(context, listener); } @@ -192,12 +217,14 @@ public final class BluetoothMapClient implements BluetoothProfile { * currently connected to the Map service. * @hide */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean isConnected(BluetoothDevice device) { if (VDBG) Log.d(TAG, "isConnected(" + device + ")"); final IBluetoothMapClient service = getService(); if (service != null) { try { - return service.isConnected(device); + return service.isConnected(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -214,13 +241,17 @@ public final class BluetoothMapClient implements BluetoothProfile { * * @hide */ - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public boolean connect(BluetoothDevice device) { if (DBG) Log.d(TAG, "connect(" + device + ")" + "for MAPS MCE"); final IBluetoothMapClient service = getService(); if (service != null) { try { - return service.connect(device); + return service.connect(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -239,13 +270,17 @@ public final class BluetoothMapClient implements BluetoothProfile { * * @hide */ - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public boolean disconnect(BluetoothDevice device) { if (DBG) Log.d(TAG, "disconnect(" + device + ")"); final IBluetoothMapClient service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { try { - return service.disconnect(device); + return service.disconnect(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); } @@ -261,12 +296,15 @@ public final class BluetoothMapClient implements BluetoothProfile { * @hide */ @Override + @RequiresBluetoothConnectPermission + @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) public List<BluetoothDevice> getConnectedDevices() { if (DBG) Log.d(TAG, "getConnectedDevices()"); final IBluetoothMapClient service = getService(); if (service != null && isEnabled()) { try { - return service.getConnectedDevices(); + return Attributable.setAttributionSource( + service.getConnectedDevices(mAttributionSource), mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return new ArrayList<>(); @@ -283,12 +321,16 @@ public final class BluetoothMapClient implements BluetoothProfile { * @hide */ @Override + @RequiresBluetoothConnectPermission + @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { if (DBG) Log.d(TAG, "getDevicesMatchingStates()"); final IBluetoothMapClient service = getService(); if (service != null && isEnabled()) { try { - return service.getDevicesMatchingConnectionStates(states); + return Attributable.setAttributionSource( + service.getDevicesMatchingConnectionStates(states, mAttributionSource), + mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return new ArrayList<>(); @@ -305,12 +347,14 @@ public final class BluetoothMapClient implements BluetoothProfile { * @hide */ @Override + @RequiresBluetoothConnectPermission + @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) public int getConnectionState(BluetoothDevice device) { if (DBG) Log.d(TAG, "getConnectionState(" + device + ")"); final IBluetoothMapClient service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { try { - return service.getConnectionState(device); + return service.getConnectionState(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return BluetoothProfile.STATE_DISCONNECTED; @@ -331,7 +375,11 @@ public final class BluetoothMapClient implements BluetoothProfile { * @return true if priority is set, false on error * @hide */ - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public boolean setPriority(BluetoothDevice device, int priority) { if (DBG) Log.d(TAG, "setPriority(" + device + ", " + priority + ")"); return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority)); @@ -349,7 +397,11 @@ public final class BluetoothMapClient implements BluetoothProfile { * @return true if connectionPolicy is set, false on error * @hide */ - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public boolean setConnectionPolicy(@NonNull BluetoothDevice device, @ConnectionPolicy int connectionPolicy) { if (DBG) Log.d(TAG, "setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); @@ -360,7 +412,7 @@ public final class BluetoothMapClient implements BluetoothProfile { return false; } try { - return service.setConnectionPolicy(device, connectionPolicy); + return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return false; @@ -380,7 +432,11 @@ public final class BluetoothMapClient implements BluetoothProfile { * @return priority of the device * @hide */ - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public int getPriority(BluetoothDevice device) { if (VDBG) Log.d(TAG, "getPriority(" + device + ")"); return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device)); @@ -397,13 +453,17 @@ public final class BluetoothMapClient implements BluetoothProfile { * @return connection policy of the device * @hide */ - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) { if (VDBG) Log.d(TAG, "getConnectionPolicy(" + device + ")"); final IBluetoothMapClient service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { try { - return service.getConnectionPolicy(device); + return service.getConnectionPolicy(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; @@ -427,7 +487,11 @@ public final class BluetoothMapClient implements BluetoothProfile { * @hide */ @SystemApi - @RequiresPermission(Manifest.permission.SEND_SMS) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.SEND_SMS, + }) public boolean sendMessage(@NonNull BluetoothDevice device, @NonNull Collection<Uri> contacts, @NonNull String message, @Nullable PendingIntent sentIntent, @Nullable PendingIntent deliveredIntent) { @@ -436,7 +500,7 @@ public final class BluetoothMapClient implements BluetoothProfile { if (service != null && isEnabled() && isValidDevice(device)) { try { return service.sendMessage(device, contacts.toArray(new Uri[contacts.size()]), - message, sentIntent, deliveredIntent); + message, sentIntent, deliveredIntent, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return false; @@ -459,13 +523,19 @@ public final class BluetoothMapClient implements BluetoothProfile { * @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.SEND_SMS, + }) public boolean sendMessage(BluetoothDevice device, Uri[] contacts, String message, PendingIntent sentIntent, PendingIntent deliveredIntent) { if (DBG) Log.d(TAG, "sendMessage(" + device + ", " + contacts + ", " + message); final IBluetoothMapClient service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { try { - return service.sendMessage(device, contacts, message, sentIntent, deliveredIntent); + return service.sendMessage(device, contacts, message, sentIntent, deliveredIntent, + mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return false; @@ -481,12 +551,17 @@ public final class BluetoothMapClient implements BluetoothProfile { * @return true if the message is enqueued, false on error * @hide */ + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.READ_SMS, + }) public boolean getUnreadMessages(BluetoothDevice device) { if (DBG) Log.d(TAG, "getUnreadMessages(" + device + ")"); final IBluetoothMapClient service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { try { - return service.getUnreadMessages(device); + return service.getUnreadMessages(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return false; @@ -503,11 +578,14 @@ public final class BluetoothMapClient implements BluetoothProfile { * MapSupportedFeatures field is set. False is returned otherwise. * @hide */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean isUploadingSupported(BluetoothDevice device) { final IBluetoothMapClient service = getService(); try { return (service != null && isEnabled() && isValidDevice(device)) - && ((service.getSupportedFeatures(device) & UPLOADING_FEATURE_BITMASK) > 0); + && ((service.getSupportedFeatures(device, mAttributionSource) + & UPLOADING_FEATURE_BITMASK) > 0); } catch (RemoteException e) { Log.e(TAG, e.getMessage()); } @@ -530,14 +608,18 @@ public final class BluetoothMapClient implements BluetoothProfile { * @return <code>true</code> if request has been sent, <code>false</code> on error * @hide */ - @RequiresPermission(Manifest.permission.READ_SMS) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.READ_SMS, + }) public boolean setMessageStatus(BluetoothDevice device, String handle, int status) { if (DBG) Log.d(TAG, "setMessageStatus(" + device + ", " + handle + ", " + status + ")"); final IBluetoothMapClient service = getService(); if (service != null && isEnabled() && isValidDevice(device) && handle != null && (status == READ || status == UNREAD || status == UNDELETED || status == DELETED)) { try { - return service.setMessageStatus(device, handle, status); + return service.setMessageStatus(device, handle, status, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return false; @@ -547,14 +629,10 @@ public final class BluetoothMapClient implements BluetoothProfile { } private boolean isEnabled() { - BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); - if (adapter != null && adapter.getState() == BluetoothAdapter.STATE_ON) return true; - if (DBG) Log.d(TAG, "Bluetooth is Not enabled"); - return false; + return mAdapter.isEnabled(); } private static boolean isValidDevice(BluetoothDevice device) { return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); } - } diff --git a/framework/java/android/bluetooth/BluetoothMasInstance.java b/framework/java/android/bluetooth/BluetoothMasInstance.java index b64d0492fa..eeaf085451 100644 --- a/framework/java/android/bluetooth/BluetoothMasInstance.java +++ b/framework/java/android/bluetooth/BluetoothMasInstance.java @@ -16,6 +16,7 @@ package android.bluetooth; +import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; @@ -34,7 +35,7 @@ public final class BluetoothMasInstance implements Parcelable { } @Override - public boolean equals(Object o) { + public boolean equals(@Nullable Object o) { if (o instanceof BluetoothMasInstance) { return mId == ((BluetoothMasInstance) o).mId; } diff --git a/framework/java/android/bluetooth/BluetoothOutputStream.java b/framework/java/android/bluetooth/BluetoothOutputStream.java index a0aa2dee9d..ac2b3edb0e 100644 --- a/framework/java/android/bluetooth/BluetoothOutputStream.java +++ b/framework/java/android/bluetooth/BluetoothOutputStream.java @@ -16,6 +16,8 @@ package android.bluetooth; +import android.annotation.SuppressLint; + import java.io.IOException; import java.io.OutputStream; @@ -26,6 +28,7 @@ import java.io.OutputStream; * * @hide */ +@SuppressLint("AndroidFrameworkBluetoothPermission") /*package*/ final class BluetoothOutputStream extends OutputStream { private BluetoothSocket mSocket; diff --git a/framework/java/android/bluetooth/BluetoothPan.java b/framework/java/android/bluetooth/BluetoothPan.java index f06dad8232..577be3d2ae 100644 --- a/framework/java/android/bluetooth/BluetoothPan.java +++ b/framework/java/android/bluetooth/BluetoothPan.java @@ -24,7 +24,11 @@ import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SuppressLint; import android.annotation.SystemApi; +import android.bluetooth.annotations.RequiresBluetoothConnectPermission; +import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; import android.compat.annotation.UnsupportedAppUsage; +import android.content.Attributable; +import android.content.AttributionSource; import android.content.Context; import android.os.Binder; import android.os.Build; @@ -74,10 +78,11 @@ public final class BluetoothPan implements BluetoothProfile { * * <p> {@link #EXTRA_LOCAL_ROLE} can be one of {@link #LOCAL_NAP_ROLE} or * {@link #LOCAL_PANU_ROLE} - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to - * receive. */ @SuppressLint("ActionValue") + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.pan.profile.action.CONNECTION_STATE_CHANGED"; @@ -102,9 +107,8 @@ public final class BluetoothPan implements BluetoothProfile { * * <p> {@link #EXTRA_TETHERING_STATE} can be any of {@link #TETHERING_STATE_OFF} or * {@link #TETHERING_STATE_ON} - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to - * receive. */ + @RequiresLegacyBluetoothPermission @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_TETHERING_STATE_CHANGED = "android.bluetooth.action.TETHERING_STATE_CHANGED"; @@ -179,7 +183,8 @@ public final class BluetoothPan implements BluetoothProfile { private final Context mContext; - private BluetoothAdapter mAdapter; + private final BluetoothAdapter mAdapter; + private final AttributionSource mAttributionSource; private final BluetoothProfileConnector<IBluetoothPan> mProfileConnector = new BluetoothProfileConnector(this, BluetoothProfile.PAN, "BluetoothPan", IBluetoothPan.class.getName()) { @@ -197,8 +202,10 @@ public final class BluetoothPan implements BluetoothProfile { * @hide */ @UnsupportedAppUsage - /*package*/ BluetoothPan(Context context, ServiceListener listener) { - mAdapter = BluetoothAdapter.getDefaultAdapter(); + /* package */ BluetoothPan(Context context, ServiceListener listener, + BluetoothAdapter adapter) { + mAdapter = adapter; + mAttributionSource = adapter.getAttributionSource(); mContext = context; mProfileConnector.connect(context, listener); } @@ -236,12 +243,17 @@ public final class BluetoothPan implements BluetoothProfile { * @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public boolean connect(BluetoothDevice device) { if (DBG) log("connect(" + device + ")"); final IBluetoothPan service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { try { - return service.connect(device); + return service.connect(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return false; @@ -274,12 +286,14 @@ public final class BluetoothPan implements BluetoothProfile { * @hide */ @UnsupportedAppUsage + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean disconnect(BluetoothDevice device) { if (DBG) log("disconnect(" + device + ")"); final IBluetoothPan service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { try { - return service.disconnect(device); + return service.disconnect(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return false; @@ -302,7 +316,11 @@ public final class BluetoothPan implements BluetoothProfile { * @hide */ @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public boolean setConnectionPolicy(@NonNull BluetoothDevice device, @ConnectionPolicy int connectionPolicy) { if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); @@ -314,7 +332,7 @@ public final class BluetoothPan implements BluetoothProfile { && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) { return false; } - return service.setConnectionPolicy(device, connectionPolicy); + return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource); } if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; @@ -330,13 +348,18 @@ public final class BluetoothPan implements BluetoothProfile { */ @SystemApi @Override - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public @NonNull List<BluetoothDevice> getConnectedDevices() { if (VDBG) log("getConnectedDevices()"); final IBluetoothPan service = getService(); if (service != null && isEnabled()) { try { - return service.getConnectedDevices(); + return Attributable.setAttributionSource( + service.getConnectedDevices(mAttributionSource), mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return new ArrayList<BluetoothDevice>(); @@ -351,13 +374,20 @@ public final class BluetoothPan implements BluetoothProfile { * @hide */ @Override - @RequiresPermission(Manifest.permission.BLUETOOTH) + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { if (VDBG) log("getDevicesMatchingStates()"); final IBluetoothPan service = getService(); if (service != null && isEnabled()) { try { - return service.getDevicesMatchingConnectionStates(states); + return Attributable.setAttributionSource( + service.getDevicesMatchingConnectionStates(states, mAttributionSource), + mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return new ArrayList<BluetoothDevice>(); @@ -373,13 +403,17 @@ public final class BluetoothPan implements BluetoothProfile { */ @SystemApi @Override - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public int getConnectionState(@NonNull BluetoothDevice device) { if (VDBG) log("getState(" + device + ")"); final IBluetoothPan service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { try { - return service.getConnectionState(device); + return service.getConnectionState(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); return BluetoothProfile.STATE_DISCONNECTED; @@ -396,14 +430,19 @@ public final class BluetoothPan implements BluetoothProfile { * @hide */ @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + android.Manifest.permission.TETHER_PRIVILEGED, + }) public void setBluetoothTethering(boolean value) { String pkgName = mContext.getOpPackageName(); if (DBG) log("setBluetoothTethering(" + value + "), calling package:" + pkgName); final IBluetoothPan service = getService(); if (service != null && isEnabled()) { try { - service.setBluetoothTethering(value, pkgName, null); + service.setBluetoothTethering(value, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); } @@ -417,13 +456,14 @@ public final class BluetoothPan implements BluetoothProfile { * @hide */ @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean isTetheringOn() { if (VDBG) log("isTetheringOn()"); final IBluetoothPan service = getService(); if (service != null && isEnabled()) { try { - return service.isTetheringOn(); + return service.isTetheringOn(mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); } diff --git a/framework/java/android/bluetooth/BluetoothPbap.java b/framework/java/android/bluetooth/BluetoothPbap.java index 6e5c45f3d1..c000e56e7b 100644 --- a/framework/java/android/bluetooth/BluetoothPbap.java +++ b/framework/java/android/bluetooth/BluetoothPbap.java @@ -22,11 +22,16 @@ import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SuppressLint; import android.annotation.SystemApi; +import android.bluetooth.annotations.RequiresBluetoothConnectPermission; +import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; import android.compat.annotation.UnsupportedAppUsage; +import android.content.Attributable; +import android.content.AttributionSource; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; +import android.content.pm.PackageManager; import android.os.Build; import android.os.IBinder; import android.os.RemoteException; @@ -82,14 +87,13 @@ public class BluetoothPbap implements BluetoothProfile { * can be any of {@link BluetoothProfile#STATE_DISCONNECTED}, * {@link BluetoothProfile#STATE_CONNECTING}, {@link BluetoothProfile#STATE_CONNECTED}, * {@link BluetoothProfile#STATE_DISCONNECTING}. - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to - * receive. * * @hide */ @SuppressLint("ActionValue") @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.pbap.profile.action.CONNECTION_STATE_CHANGED"; @@ -97,7 +101,8 @@ public class BluetoothPbap implements BluetoothProfile { private volatile IBluetoothPbap mService; private final Context mContext; private ServiceListener mServiceListener; - private BluetoothAdapter mAdapter; + private final BluetoothAdapter mAdapter; + private final AttributionSource mAttributionSource; /** @hide */ public static final int RESULT_FAILURE = 0; @@ -110,6 +115,7 @@ public class BluetoothPbap implements BluetoothProfile { */ public static final int RESULT_CANCELED = 2; + @SuppressLint("AndroidFrameworkBluetoothPermission") private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback = new IBluetoothStateChangeCallback.Stub() { public void onBluetoothStateChange(boolean up) { @@ -127,10 +133,21 @@ public class BluetoothPbap implements BluetoothProfile { * * @hide */ - public BluetoothPbap(Context context, ServiceListener l) { + public BluetoothPbap(Context context, ServiceListener l, BluetoothAdapter adapter) { mContext = context; mServiceListener = l; - mAdapter = BluetoothAdapter.getDefaultAdapter(); + mAdapter = adapter; + mAttributionSource = adapter.getAttributionSource(); + + // Preserve legacy compatibility where apps were depending on + // registerStateChangeCallback() performing a permissions check which + // has been relaxed in modern platform versions + if (context.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.R + && context.checkSelfPermission(android.Manifest.permission.BLUETOOTH) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Need BLUETOOTH permission"); + } + IBluetoothManager mgr = mAdapter.getBluetoothManager(); if (mgr != null) { try { @@ -142,6 +159,7 @@ public class BluetoothPbap implements BluetoothProfile { doBind(); } + @SuppressLint("AndroidFrameworkRequiresPermission") boolean doBind() { synchronized (mConnection) { try { @@ -216,6 +234,8 @@ public class BluetoothPbap implements BluetoothProfile { * @hide */ @Override + @RequiresBluetoothConnectPermission + @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) public List<BluetoothDevice> getConnectedDevices() { log("getConnectedDevices()"); final IBluetoothPbap service = mService; @@ -224,7 +244,8 @@ public class BluetoothPbap implements BluetoothProfile { return new ArrayList<BluetoothDevice>(); } try { - return service.getConnectedDevices(); + return Attributable.setAttributionSource( + service.getConnectedDevices(mAttributionSource), mAttributionSource); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -238,13 +259,17 @@ public class BluetoothPbap implements BluetoothProfile { */ @SystemApi @Override - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public @BtProfileState int getConnectionState(@NonNull BluetoothDevice device) { log("getConnectionState: device=" + device); try { final IBluetoothPbap service = mService; if (service != null && isEnabled() && isValidDevice(device)) { - return service.getConnectionState(device); + return service.getConnectionState(device, mAttributionSource); } if (service == null) { Log.w(TAG, "Proxy not attached to service"); @@ -262,6 +287,8 @@ public class BluetoothPbap implements BluetoothProfile { * @hide */ @Override + @RequiresBluetoothConnectPermission + @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { log("getDevicesMatchingConnectionStates: states=" + Arrays.toString(states)); final IBluetoothPbap service = mService; @@ -270,7 +297,9 @@ public class BluetoothPbap implements BluetoothProfile { return new ArrayList<BluetoothDevice>(); } try { - return service.getDevicesMatchingConnectionStates(states); + return Attributable.setAttributionSource( + service.getDevicesMatchingConnectionStates(states, mAttributionSource), + mAttributionSource); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -294,7 +323,11 @@ public class BluetoothPbap implements BluetoothProfile { * @hide */ @SystemApi - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public boolean setConnectionPolicy(@NonNull BluetoothDevice device, @ConnectionPolicy int connectionPolicy) { if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); @@ -306,7 +339,7 @@ public class BluetoothPbap implements BluetoothProfile { && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) { return false; } - return service.setConnectionPolicy(device, connectionPolicy); + return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource); } if (service == null) Log.w(TAG, "Proxy not attached to service"); return false; @@ -324,6 +357,8 @@ public class BluetoothPbap implements BluetoothProfile { * @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean disconnect(BluetoothDevice device) { log("disconnect()"); final IBluetoothPbap service = mService; @@ -332,7 +367,7 @@ public class BluetoothPbap implements BluetoothProfile { return false; } try { - service.disconnect(device); + service.disconnect(device, mAttributionSource); return true; } catch (RemoteException e) { Log.e(TAG, e.toString()); @@ -340,6 +375,7 @@ public class BluetoothPbap implements BluetoothProfile { return false; } + @SuppressLint("AndroidFrameworkBluetoothPermission") private final ServiceConnection mConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { log("Proxy object connected"); diff --git a/framework/java/android/bluetooth/BluetoothPbapClient.java b/framework/java/android/bluetooth/BluetoothPbapClient.java index f356da18fc..c7dd6bd9af 100644 --- a/framework/java/android/bluetooth/BluetoothPbapClient.java +++ b/framework/java/android/bluetooth/BluetoothPbapClient.java @@ -19,6 +19,12 @@ package android.bluetooth; import android.Manifest; import android.annotation.NonNull; import android.annotation.RequiresPermission; +import android.annotation.SdkConstant; +import android.annotation.SuppressLint; +import android.annotation.SdkConstant.SdkConstantType; +import android.bluetooth.annotations.RequiresBluetoothConnectPermission; +import android.content.Attributable; +import android.content.AttributionSource; import android.content.Context; import android.os.Binder; import android.os.IBinder; @@ -39,6 +45,9 @@ public final class BluetoothPbapClient implements BluetoothProfile { private static final boolean DBG = false; private static final boolean VDBG = false; + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.pbapclient.profile.action.CONNECTION_STATE_CHANGED"; @@ -50,7 +59,8 @@ public final class BluetoothPbapClient implements BluetoothProfile { /** Connection canceled before completion. */ public static final int RESULT_CANCELED = 2; - private BluetoothAdapter mAdapter; + private final BluetoothAdapter mAdapter; + private final AttributionSource mAttributionSource; private final BluetoothProfileConnector<IBluetoothPbapClient> mProfileConnector = new BluetoothProfileConnector(this, BluetoothProfile.PBAP_CLIENT, "BluetoothPbapClient", IBluetoothPbapClient.class.getName()) { @@ -63,11 +73,12 @@ public final class BluetoothPbapClient implements BluetoothProfile { /** * Create a BluetoothPbapClient proxy object. */ - BluetoothPbapClient(Context context, ServiceListener listener) { + BluetoothPbapClient(Context context, ServiceListener listener, BluetoothAdapter adapter) { if (DBG) { Log.d(TAG, "Create BluetoothPbapClient proxy object"); } - mAdapter = BluetoothAdapter.getDefaultAdapter(); + mAdapter = adapter; + mAttributionSource = adapter.getAttributionSource(); mProfileConnector.connect(context, listener); } @@ -104,7 +115,11 @@ public final class BluetoothPbapClient implements BluetoothProfile { * * @hide */ - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public boolean connect(BluetoothDevice device) { if (DBG) { log("connect(" + device + ") for PBAP Client."); @@ -112,7 +127,7 @@ public final class BluetoothPbapClient implements BluetoothProfile { final IBluetoothPbapClient service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { try { - return service.connect(device); + return service.connect(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return false; @@ -132,7 +147,11 @@ public final class BluetoothPbapClient implements BluetoothProfile { * * @hide */ - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public boolean disconnect(BluetoothDevice device) { if (DBG) { log("disconnect(" + device + ")" + new Exception()); @@ -140,7 +159,7 @@ public final class BluetoothPbapClient implements BluetoothProfile { final IBluetoothPbapClient service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { try { - service.disconnect(device); + service.disconnect(device, mAttributionSource); return true; } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); @@ -160,6 +179,8 @@ public final class BluetoothPbapClient implements BluetoothProfile { * @return list of connected devices */ @Override + @RequiresBluetoothConnectPermission + @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) public List<BluetoothDevice> getConnectedDevices() { if (DBG) { log("getConnectedDevices()"); @@ -167,7 +188,8 @@ public final class BluetoothPbapClient implements BluetoothProfile { final IBluetoothPbapClient service = getService(); if (service != null && isEnabled()) { try { - return service.getConnectedDevices(); + return Attributable.setAttributionSource( + service.getConnectedDevices(mAttributionSource), mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return new ArrayList<BluetoothDevice>(); @@ -185,6 +207,8 @@ public final class BluetoothPbapClient implements BluetoothProfile { * @return list of matching devices */ @Override + @RequiresBluetoothConnectPermission + @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { if (DBG) { log("getDevicesMatchingStates()"); @@ -192,7 +216,9 @@ public final class BluetoothPbapClient implements BluetoothProfile { final IBluetoothPbapClient service = getService(); if (service != null && isEnabled()) { try { - return service.getDevicesMatchingConnectionStates(states); + return Attributable.setAttributionSource( + service.getDevicesMatchingConnectionStates(states, mAttributionSource), + mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return new ArrayList<BluetoothDevice>(); @@ -210,6 +236,8 @@ public final class BluetoothPbapClient implements BluetoothProfile { * @return device connection state */ @Override + @RequiresBluetoothConnectPermission + @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) public int getConnectionState(BluetoothDevice device) { if (DBG) { log("getConnectionState(" + device + ")"); @@ -217,7 +245,7 @@ public final class BluetoothPbapClient implements BluetoothProfile { final IBluetoothPbapClient service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { try { - return service.getConnectionState(device); + return service.getConnectionState(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return BluetoothProfile.STATE_DISCONNECTED; @@ -234,12 +262,7 @@ public final class BluetoothPbapClient implements BluetoothProfile { } private boolean isEnabled() { - BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); - if (adapter != null && adapter.getState() == BluetoothAdapter.STATE_ON) { - return true; - } - log("Bluetooth is Not enabled"); - return false; + return mAdapter.isEnabled(); } private static boolean isValidDevice(BluetoothDevice device) { @@ -257,7 +280,11 @@ public final class BluetoothPbapClient implements BluetoothProfile { * @return true if priority is set, false on error * @hide */ - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public boolean setPriority(BluetoothDevice device, int priority) { if (DBG) log("setPriority(" + device + ", " + priority + ")"); return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority)); @@ -275,7 +302,11 @@ public final class BluetoothPbapClient implements BluetoothProfile { * @return true if connectionPolicy is set, false on error * @hide */ - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public boolean setConnectionPolicy(@NonNull BluetoothDevice device, @ConnectionPolicy int connectionPolicy) { if (DBG) { @@ -288,7 +319,7 @@ public final class BluetoothPbapClient implements BluetoothProfile { return false; } try { - return service.setConnectionPolicy(device, connectionPolicy); + return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return false; @@ -310,7 +341,11 @@ public final class BluetoothPbapClient implements BluetoothProfile { * @return priority of the device * @hide */ - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public int getPriority(BluetoothDevice device) { if (VDBG) log("getPriority(" + device + ")"); return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device)); @@ -327,7 +362,11 @@ public final class BluetoothPbapClient implements BluetoothProfile { * @return connection policy of the device * @hide */ - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) { if (VDBG) { log("getConnectionPolicy(" + device + ")"); @@ -335,7 +374,7 @@ public final class BluetoothPbapClient implements BluetoothProfile { final IBluetoothPbapClient service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { try { - return service.getConnectionPolicy(device); + return service.getConnectionPolicy(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; diff --git a/framework/java/android/bluetooth/BluetoothProfile.java b/framework/java/android/bluetooth/BluetoothProfile.java index b76d6b8691..83a272fce3 100644 --- a/framework/java/android/bluetooth/BluetoothProfile.java +++ b/framework/java/android/bluetooth/BluetoothProfile.java @@ -14,12 +14,10 @@ * limitations under the License. */ - package android.bluetooth; -import android.Manifest; import android.annotation.IntDef; -import android.annotation.RequiresPermission; +import android.annotation.RequiresNoPermission; import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; @@ -307,7 +305,6 @@ public interface BluetoothProfile { * * @return List of devices. The list will be empty on error. */ - @RequiresPermission(Manifest.permission.BLUETOOTH) public List<BluetoothDevice> getConnectedDevices(); /** @@ -321,7 +318,6 @@ public interface BluetoothProfile { * #STATE_CONNECTING}, {@link #STATE_DISCONNECTED}, {@link #STATE_DISCONNECTING}, * @return List of devices. The list will be empty on error. */ - @RequiresPermission(Manifest.permission.BLUETOOTH) public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states); /** @@ -331,7 +327,6 @@ public interface BluetoothProfile { * @return State of the profile connection. One of {@link #STATE_CONNECTED}, {@link * #STATE_CONNECTING}, {@link #STATE_DISCONNECTED}, {@link #STATE_DISCONNECTING} */ - @RequiresPermission(Manifest.permission.BLUETOOTH) @BtProfileState int getConnectionState(BluetoothDevice device); /** @@ -346,6 +341,7 @@ public interface BluetoothProfile { * @param profile - One of {@link #HEADSET} or {@link #A2DP} * @param proxy - One of {@link BluetoothHeadset} or {@link BluetoothA2dp} */ + @RequiresNoPermission public void onServiceConnected(int profile, BluetoothProfile proxy); /** @@ -354,6 +350,7 @@ public interface BluetoothProfile { * * @param profile - One of {@link #HEADSET} or {@link #A2DP} */ + @RequiresNoPermission public void onServiceDisconnected(int profile); } diff --git a/framework/java/android/bluetooth/BluetoothProfileConnector.java b/framework/java/android/bluetooth/BluetoothProfileConnector.java index 863fd3698c..a254291f57 100644 --- a/framework/java/android/bluetooth/BluetoothProfileConnector.java +++ b/framework/java/android/bluetooth/BluetoothProfileConnector.java @@ -16,13 +16,17 @@ package android.bluetooth; +import android.annotation.SuppressLint; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; +import android.content.pm.PackageManager; +import android.os.Build; import android.os.IBinder; import android.os.RemoteException; import android.os.UserHandle; +import android.util.CloseGuard; import android.util.Log; /** @@ -31,7 +35,9 @@ import android.util.Log; * @param <T> The Bluetooth profile interface for this connection. * @hide */ +@SuppressLint("AndroidFrameworkBluetoothPermission") public abstract class BluetoothProfileConnector<T> { + private final CloseGuard mCloseGuard = new CloseGuard(); private final int mProfileId; private BluetoothProfile.ServiceListener mServiceListener; private final BluetoothProfile mProfileProxy; @@ -78,10 +84,19 @@ public abstract class BluetoothProfileConnector<T> { mServiceName = serviceName; } + /** {@hide} */ + @Override + public void finalize() { + mCloseGuard.warnIfOpen(); + doUnbind(); + } + + @SuppressLint("AndroidFrameworkRequiresPermission") private boolean doBind() { synchronized (mConnection) { if (mService == null) { logDebug("Binding service..."); + mCloseGuard.open("doUnbind"); try { Intent intent = new Intent(mServiceName); ComponentName comp = intent.resolveSystemService( @@ -105,6 +120,7 @@ public abstract class BluetoothProfileConnector<T> { synchronized (mConnection) { if (mService != null) { logDebug("Unbinding service..."); + mCloseGuard.close(); try { mContext.unbindService(mConnection); } catch (IllegalArgumentException ie) { @@ -120,6 +136,16 @@ public abstract class BluetoothProfileConnector<T> { mContext = context; mServiceListener = listener; IBluetoothManager mgr = BluetoothAdapter.getDefaultAdapter().getBluetoothManager(); + + // Preserve legacy compatibility where apps were depending on + // registerStateChangeCallback() performing a permissions check which + // has been relaxed in modern platform versions + if (context.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.R + && context.checkSelfPermission(android.Manifest.permission.BLUETOOTH) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Need BLUETOOTH permission"); + } + if (mgr != null) { try { mgr.registerStateChangeCallback(mBluetoothStateChangeCallback); diff --git a/framework/java/android/bluetooth/BluetoothSap.java b/framework/java/android/bluetooth/BluetoothSap.java index 0d70dbdd84..fda19ed6d0 100644 --- a/framework/java/android/bluetooth/BluetoothSap.java +++ b/framework/java/android/bluetooth/BluetoothSap.java @@ -17,8 +17,15 @@ package android.bluetooth; import android.Manifest; +import android.annotation.RequiresNoPermission; import android.annotation.RequiresPermission; +import android.annotation.SdkConstant; +import android.annotation.SdkConstant.SdkConstantType; +import android.bluetooth.annotations.RequiresBluetoothConnectPermission; +import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; import android.compat.annotation.UnsupportedAppUsage; +import android.content.Attributable; +import android.content.AttributionSource; import android.content.Context; import android.os.Binder; import android.os.Build; @@ -61,11 +68,12 @@ public final class BluetoothSap implements BluetoothProfile { * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}. * - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to - * receive. - * * @hide */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.sap.profile.action.CONNECTION_STATE_CHANGED"; @@ -90,7 +98,8 @@ public final class BluetoothSap implements BluetoothProfile { */ public static final int RESULT_CANCELED = 2; - private BluetoothAdapter mAdapter; + private final BluetoothAdapter mAdapter; + private final AttributionSource mAttributionSource; private final BluetoothProfileConnector<IBluetoothSap> mProfileConnector = new BluetoothProfileConnector(this, BluetoothProfile.SAP, "BluetoothSap", IBluetoothSap.class.getName()) { @@ -103,9 +112,11 @@ public final class BluetoothSap implements BluetoothProfile { /** * Create a BluetoothSap proxy object. */ - /*package*/ BluetoothSap(Context context, ServiceListener listener) { + /* package */ BluetoothSap(Context context, ServiceListener listener, + BluetoothAdapter adapter) { if (DBG) Log.d(TAG, "Create BluetoothSap proxy object"); - mAdapter = BluetoothAdapter.getDefaultAdapter(); + mAdapter = adapter; + mAttributionSource = adapter.getAttributionSource(); mProfileConnector.connect(context, listener); } @@ -140,12 +151,14 @@ public final class BluetoothSap implements BluetoothProfile { * connected to the Sap service. * @hide */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getState() { if (VDBG) log("getState()"); final IBluetoothSap service = getService(); if (service != null) { try { - return service.getState(); + return service.getState(mAttributionSource); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -163,12 +176,15 @@ public final class BluetoothSap implements BluetoothProfile { * this proxy object is not connected to the Sap service. * @hide */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public BluetoothDevice getClient() { if (VDBG) log("getClient()"); final IBluetoothSap service = getService(); if (service != null) { try { - return service.getClient(); + return Attributable.setAttributionSource( + service.getClient(mAttributionSource), mAttributionSource); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -186,12 +202,14 @@ public final class BluetoothSap implements BluetoothProfile { * * @hide */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean isConnected(BluetoothDevice device) { if (VDBG) log("isConnected(" + device + ")"); final IBluetoothSap service = getService(); if (service != null) { try { - return service.isConnected(device); + return service.isConnected(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, e.toString()); } @@ -208,6 +226,7 @@ public final class BluetoothSap implements BluetoothProfile { * * @hide */ + @RequiresNoPermission public boolean connect(BluetoothDevice device) { if (DBG) log("connect(" + device + ")" + "not supported for SAPS"); return false; @@ -221,12 +240,14 @@ public final class BluetoothSap implements BluetoothProfile { * @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean disconnect(BluetoothDevice device) { if (DBG) log("disconnect(" + device + ")"); final IBluetoothSap service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { try { - return service.disconnect(device); + return service.disconnect(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return false; @@ -242,12 +263,15 @@ public final class BluetoothSap implements BluetoothProfile { * @return list of connected devices * @hide */ + @RequiresBluetoothConnectPermission + @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) public List<BluetoothDevice> getConnectedDevices() { if (DBG) log("getConnectedDevices()"); final IBluetoothSap service = getService(); if (service != null && isEnabled()) { try { - return service.getConnectedDevices(); + return Attributable.setAttributionSource( + service.getConnectedDevices(mAttributionSource), mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return new ArrayList<BluetoothDevice>(); @@ -263,12 +287,16 @@ public final class BluetoothSap implements BluetoothProfile { * @return list of matching devices * @hide */ + @RequiresBluetoothConnectPermission + @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { if (DBG) log("getDevicesMatchingStates()"); final IBluetoothSap service = getService(); if (service != null && isEnabled()) { try { - return service.getDevicesMatchingConnectionStates(states); + return Attributable.setAttributionSource( + service.getDevicesMatchingConnectionStates(states, mAttributionSource), + mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return new ArrayList<BluetoothDevice>(); @@ -284,12 +312,14 @@ public final class BluetoothSap implements BluetoothProfile { * @return device connection state * @hide */ + @RequiresBluetoothConnectPermission + @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) public int getConnectionState(BluetoothDevice device) { if (DBG) log("getConnectionState(" + device + ")"); final IBluetoothSap service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { try { - return service.getConnectionState(device); + return service.getConnectionState(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return BluetoothProfile.STATE_DISCONNECTED; @@ -310,7 +340,11 @@ public final class BluetoothSap implements BluetoothProfile { * @return true if priority is set, false on error * @hide */ - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public boolean setPriority(BluetoothDevice device, int priority) { if (DBG) log("setPriority(" + device + ", " + priority + ")"); return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority)); @@ -328,7 +362,11 @@ public final class BluetoothSap implements BluetoothProfile { * @return true if connectionPolicy is set, false on error * @hide */ - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public boolean setConnectionPolicy(BluetoothDevice device, @ConnectionPolicy int connectionPolicy) { if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); @@ -339,7 +377,7 @@ public final class BluetoothSap implements BluetoothProfile { return false; } try { - return service.setConnectionPolicy(device, connectionPolicy); + return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return false; @@ -359,7 +397,11 @@ public final class BluetoothSap implements BluetoothProfile { * @return priority of the device * @hide */ - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public int getPriority(BluetoothDevice device) { if (VDBG) log("getPriority(" + device + ")"); return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device)); @@ -376,13 +418,17 @@ public final class BluetoothSap implements BluetoothProfile { * @return connection policy of the device * @hide */ - @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public @ConnectionPolicy int getConnectionPolicy(BluetoothDevice device) { if (VDBG) log("getConnectionPolicy(" + device + ")"); final IBluetoothSap service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { try { - return service.getConnectionPolicy(device); + return service.getConnectionPolicy(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; @@ -397,17 +443,10 @@ public final class BluetoothSap implements BluetoothProfile { } private boolean isEnabled() { - BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); - - if (adapter != null && adapter.getState() == BluetoothAdapter.STATE_ON) { - return true; - } - log("Bluetooth is Not enabled"); - return false; + return mAdapter.isEnabled(); } private static boolean isValidDevice(BluetoothDevice device) { return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); } - } diff --git a/framework/java/android/bluetooth/BluetoothServerSocket.java b/framework/java/android/bluetooth/BluetoothServerSocket.java index 5c1bcaf313..bb4e35483f 100644 --- a/framework/java/android/bluetooth/BluetoothServerSocket.java +++ b/framework/java/android/bluetooth/BluetoothServerSocket.java @@ -16,6 +16,7 @@ package android.bluetooth; +import android.annotation.SuppressLint; import android.compat.annotation.UnsupportedAppUsage; import android.os.Handler; import android.os.ParcelUuid; @@ -62,9 +63,6 @@ import java.io.IOException; * safe. In particular, {@link #close} will always immediately abort ongoing * operations and close the server socket. * - * <p class="note"><strong>Note:</strong> - * Requires the {@link android.Manifest.permission#BLUETOOTH} permission. - * * <div class="special reference"> * <h3>Developer Guides</h3> * <p>For more information about using Bluetooth, read the @@ -73,6 +71,7 @@ import java.io.IOException; * * {@see BluetoothSocket} */ +@SuppressLint("AndroidFrameworkBluetoothPermission") public final class BluetoothServerSocket implements Closeable { private static final String TAG = "BluetoothServerSocket"; diff --git a/framework/java/android/bluetooth/BluetoothSocket.java b/framework/java/android/bluetooth/BluetoothSocket.java index 65381dbb23..bb409d5360 100644 --- a/framework/java/android/bluetooth/BluetoothSocket.java +++ b/framework/java/android/bluetooth/BluetoothSocket.java @@ -16,6 +16,10 @@ package android.bluetooth; +import android.annotation.RequiresNoPermission; +import android.annotation.RequiresPermission; +import android.annotation.SuppressLint; +import android.bluetooth.annotations.RequiresBluetoothConnectPermission; import android.compat.annotation.UnsupportedAppUsage; import android.net.LocalSocket; import android.os.Build; @@ -70,9 +74,6 @@ import java.util.UUID; * safe. In particular, {@link #close} will always immediately abort ongoing * operations and close the socket. * - * <p class="note"><strong>Note:</strong> - * Requires the {@link android.Manifest.permission#BLUETOOTH} permission. - * * <div class="special reference"> * <h3>Developer Guides</h3> * <p>For more information about using Bluetooth, read the @@ -326,6 +327,7 @@ public final class BluetoothSocket implements Closeable { * * @return remote device */ + @RequiresNoPermission public BluetoothDevice getRemoteDevice() { return mDevice; } @@ -338,6 +340,7 @@ public final class BluetoothSocket implements Closeable { * * @return InputStream */ + @RequiresNoPermission public InputStream getInputStream() throws IOException { return mInputStream; } @@ -350,6 +353,7 @@ public final class BluetoothSocket implements Closeable { * * @return OutputStream */ + @RequiresNoPermission public OutputStream getOutputStream() throws IOException { return mOutputStream; } @@ -360,6 +364,7 @@ public final class BluetoothSocket implements Closeable { * * @return true if connected false if not connected */ + @RequiresNoPermission public boolean isConnected() { return mSocketState == SocketState.CONNECTED; } @@ -386,6 +391,8 @@ public final class BluetoothSocket implements Closeable { * * @throws IOException on error, for example connection failure */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void connect() throws IOException { if (mDevice == null) throw new IOException("Connect is called on null device"); @@ -427,6 +434,7 @@ public final class BluetoothSocket implements Closeable { * Currently returns unix errno instead of throwing IOException, * so that BluetoothAdapter can check the error code for EADDRINUSE */ + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) /*package*/ int bindListen() { int ret; if (mSocketState == SocketState.CLOSED) return EBADFD; @@ -635,6 +643,7 @@ public final class BluetoothSocket implements Closeable { * * @return the maximum supported Transmit packet size for the underlying transport. */ + @RequiresNoPermission public int getMaxTransmitPacketSize() { return mMaxTxPacketSize; } @@ -647,6 +656,7 @@ public final class BluetoothSocket implements Closeable { * * @return the maximum supported Receive packet size for the underlying transport. */ + @RequiresNoPermission public int getMaxReceivePacketSize() { return mMaxRxPacketSize; } @@ -656,6 +666,7 @@ public final class BluetoothSocket implements Closeable { * * @return one of {@link #TYPE_RFCOMM}, {@link #TYPE_SCO} or {@link #TYPE_L2CAP} */ + @RequiresNoPermission public int getConnectionType() { if (mType == TYPE_L2CAP_LE) { // Treat the LE CoC to be the same type as L2CAP. @@ -672,6 +683,7 @@ public final class BluetoothSocket implements Closeable { * generate SPP SDP record. * @hide */ + @RequiresNoPermission public void setExcludeSdp(boolean excludeSdp) { mExcludeSdp = excludeSdp; } @@ -682,6 +694,8 @@ public final class BluetoothSocket implements Closeable { * connection. This function is currently used for testing only. * @hide */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void requestMaximumTxDataLength() throws IOException { if (mDevice == null) { throw new IOException("requestMaximumTxDataLength is called on null device"); diff --git a/framework/java/android/bluetooth/BluetoothStatusCodes.java b/framework/java/android/bluetooth/BluetoothStatusCodes.java new file mode 100644 index 0000000000..31bb0f68c6 --- /dev/null +++ b/framework/java/android/bluetooth/BluetoothStatusCodes.java @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.bluetooth; + +import android.annotation.SystemApi; + +/** + * A class with constants representing possible return values for Bluetooth APIs. General return + * values occupy the range 0 to 99. Profile-specific return values occupy the range 100-999. + * API-specific return values start at 1000. The exception to this is the "other" error code which + * occupies the max integer value. + */ +public final class BluetoothStatusCodes { + + private BluetoothStatusCodes() {} + + /** + * Indicates that the API call was successful + */ + public static final int SUCCESS = 0; + + /** + * Error code indicating that Bluetooth is not enabled + */ + public static final int ERROR_BLUETOOTH_NOT_ENABLED = 1; + + /** + * Error code indicating that the API call was initiated by neither the system nor the active + * Zuser + */ + public static final int ERROR_BLUETOOTH_NOT_ALLOWED = 2; + + /** + * Error code indicating that the Bluetooth Device specified is not bonded + */ + public static final int ERROR_DEVICE_NOT_BONDED = 3; + + /** + * Error code indicating that the Bluetooth Device specified is not connected, but is bonded + * + * @hide + */ + public static final int ERROR_DEVICE_NOT_CONNECTED = 4; + + /** + * Error code indicating that the caller does not have the + * {@link android.Manifest.permission#BLUETOOTH_ADVERTISE} permission + * + * @hide + */ + public static final int ERROR_MISSING_BLUETOOTH_ADVERTISE_PERMISSION = 5; + + /** + * Error code indicating that the caller does not have the + * {@link android.Manifest.permission#BLUETOOTH_CONNECT} permission + */ + public static final int ERROR_MISSING_BLUETOOTH_CONNECT_PERMISSION = 6; + + /** + * Error code indicating that the caller does not have the + * {@link android.Manifest.permission#BLUETOOTH_SCAN} permission + * + * @hide + */ + public static final int ERROR_MISSING_BLUETOOTH_SCAN_PERMISSION = 7; + + /** + * If another application has already requested {@link OobData} then another fetch will be + * disallowed until the callback is removed. + * + * @hide + */ + @SystemApi + public static final int ERROR_ANOTHER_ACTIVE_OOB_REQUEST = 1000; + + /** + * Indicates that the ACL disconnected due to an explicit request from the local device. + * <p> + * Example cause: This is a normal disconnect reason, e.g., user/app initiates + * disconnection. + * + * @hide + */ + public static final int ERROR_DISCONNECT_REASON_LOCAL_REQUEST = 1100; + + /** + * Indicates that the ACL disconnected due to an explicit request from the remote device. + * <p> + * Example cause: This is a normal disconnect reason, e.g., user/app initiates + * disconnection. + * <p> + * Example solution: The app can also prompt the user to check their remote device. + * + * @hide + */ + public static final int ERROR_DISCONNECT_REASON_REMOTE_REQUEST = 1101; + + /** + * Generic disconnect reason indicating the ACL disconnected due to an error on the local + * device. + * <p> + * Example solution: Prompt the user to check their local device (e.g., phone, car + * headunit). + * + * @hide + */ + public static final int ERROR_DISCONNECT_REASON_LOCAL = 1102; + + /** + * Generic disconnect reason indicating the ACL disconnected due to an error on the remote + * device. + * <p> + * Example solution: Prompt the user to check their remote device (e.g., headset, car + * headunit, watch). + * + * @hide + */ + public static final int ERROR_DISCONNECT_REASON_REMOTE = 1103; + + /** + * Indicates that the ACL disconnected due to a timeout. + * <p> + * Example cause: remote device might be out of range. + * <p> + * Example solution: Prompt user to verify their remote device is on or in + * connection/pairing mode. + * + * @hide + */ + public static final int ERROR_DISCONNECT_REASON_TIMEOUT = 1104; + + /** + * Indicates that the ACL disconnected due to link key issues. + * <p> + * Example cause: Devices are either unpaired or remote device is refusing our pairing + * request. + * <p> + * Example solution: Prompt user to unpair and pair again. + * + * @hide + */ + public static final int ERROR_DISCONNECT_REASON_SECURITY = 1105; + + /** + * Indicates that the ACL disconnected due to the local device's system policy. + * <p> + * Example cause: privacy policy, power management policy, permissions, etc. + * <p> + * Example solution: Prompt the user to check settings, or check with their system + * administrator (e.g. some corp-managed devices do not allow OPP connection). + * + * @hide + */ + public static final int ERROR_DISCONNECT_REASON_SYSTEM_POLICY = 1106; + + /** + * Indicates that the ACL disconnected due to resource constraints, either on the local + * device or the remote device. + * <p> + * Example cause: controller is busy, memory limit reached, maximum number of connections + * reached. + * <p> + * Example solution: The app should wait and try again. If still failing, prompt the user + * to disconnect some devices, or toggle Bluetooth on the local and/or the remote device. + * + * @hide + */ + public static final int ERROR_DISCONNECT_REASON_RESOURCE_LIMIT_REACHED = 1107; + + /** + * Indicates that the ACL disconnected because another ACL connection already exists. + * + * @hide + */ + public static final int ERROR_DISCONNECT_REASON_CONNECTION_ALREADY_EXISTS = 1108; + + /** + * Indicates that the ACL disconnected due to incorrect parameters passed in from the app. + * <p> + * Example solution: Change parameters and try again. If error persists, the app can report + * telemetry and/or log the error in a bugreport. + * + * @hide + */ + public static final int ERROR_DISCONNECT_REASON_BAD_PARAMETERS = 1109; + + /** + * Indicates that an unknown error has occurred has occurred. + */ + public static final int ERROR_UNKNOWN = Integer.MAX_VALUE; +} diff --git a/framework/java/android/bluetooth/BluetoothUuid.java b/framework/java/android/bluetooth/BluetoothUuid.java index d82cf19e88..bc3754a2fc 100644 --- a/framework/java/android/bluetooth/BluetoothUuid.java +++ b/framework/java/android/bluetooth/BluetoothUuid.java @@ -18,6 +18,7 @@ package android.bluetooth; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; import android.os.ParcelUuid; @@ -34,6 +35,7 @@ import java.util.UUID; * @hide */ @SystemApi +@SuppressLint("AndroidFrameworkBluetoothPermission") public final class BluetoothUuid { /* See Bluetooth Assigned Numbers document - SDP section, to get the values of UUIDs diff --git a/framework/java/android/bluetooth/BluetoothVolumeControl.java b/framework/java/android/bluetooth/BluetoothVolumeControl.java index e5092833d7..678c11a59f 100644 --- a/framework/java/android/bluetooth/BluetoothVolumeControl.java +++ b/framework/java/android/bluetooth/BluetoothVolumeControl.java @@ -26,7 +26,10 @@ import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SuppressLint; import android.annotation.SystemApi; +import android.bluetooth.annotations.RequiresBluetoothConnectPermission; import android.compat.annotation.UnsupportedAppUsage; +import android.content.Attributable; +import android.content.AttributionSource; import android.content.Context; import android.os.Binder; import android.os.IBinder; @@ -72,12 +75,14 @@ public final class BluetoothVolumeControl implements BluetoothProfile, AutoClose */ @SystemApi @SuppressLint("ActionValue") - @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.volume-control.profile.action.CONNECTION_STATE_CHANGED"; private BluetoothAdapter mAdapter; + private final AttributionSource mAttributionSource; private final BluetoothProfileConnector<IBluetoothVolumeControl> mProfileConnector = new BluetoothProfileConnector(this, BluetoothProfile.VOLUME_CONTROL, TAG, IBluetoothVolumeControl.class.getName()) { @@ -94,6 +99,7 @@ public final class BluetoothVolumeControl implements BluetoothProfile, AutoClose /*package*/ BluetoothVolumeControl(Context context, ServiceListener listener, BluetoothAdapter adapter) { mAdapter = adapter; + mAttributionSource = adapter.getAttributionSource(); mProfileConnector.connect(context, listener); mCloseGuard = new CloseGuard(); mCloseGuard.open("close"); @@ -122,13 +128,18 @@ public final class BluetoothVolumeControl implements BluetoothProfile, AutoClose * @hide */ @SystemApi - @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public @NonNull List<BluetoothDevice> getConnectedDevices() { if (DBG) log("getConnectedDevices()"); final IBluetoothVolumeControl service = getService(); if (service != null && isEnabled()) { try { - return service.getConnectedDevices(); + return Attributable.setAttributionSource( + service.getConnectedDevices(mAttributionSource), mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return new ArrayList<BluetoothDevice>(); @@ -145,13 +156,16 @@ public final class BluetoothVolumeControl implements BluetoothProfile, AutoClose * * @hide */ - @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { if (DBG) log("getDevicesMatchingStates()"); final IBluetoothVolumeControl service = getService(); if (service != null && isEnabled()) { try { - return service.getDevicesMatchingConnectionStates(states); + return Attributable.setAttributionSource( + service.getDevicesMatchingConnectionStates(states, mAttributionSource), + mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return new ArrayList<BluetoothDevice>(); @@ -168,13 +182,14 @@ public final class BluetoothVolumeControl implements BluetoothProfile, AutoClose * * @hide */ - @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT) public int getConnectionState(BluetoothDevice device) { if (DBG) log("getConnectionState(" + device + ")"); final IBluetoothVolumeControl service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { try { - return service.getConnectionState(device); + return service.getConnectionState(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return BluetoothProfile.STATE_DISCONNECTED; @@ -192,7 +207,11 @@ public final class BluetoothVolumeControl implements BluetoothProfile, AutoClose * @hide */ @SystemApi - @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public void setVolume(@Nullable BluetoothDevice device, @IntRange(from = 0, to = 255) int volume) { if (DBG) @@ -200,7 +219,7 @@ public final class BluetoothVolumeControl implements BluetoothProfile, AutoClose final IBluetoothVolumeControl service = getService(); try { if (service != null && isEnabled()) { - service.setVolume(device, volume); + service.setVolume(device, volume, mAttributionSource); return; } if (service == null) @@ -223,7 +242,11 @@ public final class BluetoothVolumeControl implements BluetoothProfile, AutoClose * @hide */ @SystemApi - @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public boolean setConnectionPolicy(@NonNull BluetoothDevice device, @ConnectionPolicy int connectionPolicy) { if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); @@ -234,7 +257,7 @@ public final class BluetoothVolumeControl implements BluetoothProfile, AutoClose return false; } try { - return service.setConnectionPolicy(device, connectionPolicy); + return service.setConnectionPolicy(device, connectionPolicy, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return false; @@ -256,13 +279,17 @@ public final class BluetoothVolumeControl implements BluetoothProfile, AutoClose * @hide */ @SystemApi - @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) + @RequiresBluetoothConnectPermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_CONNECT, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) { if (VDBG) log("getConnectionPolicy(" + device + ")"); final IBluetoothVolumeControl service = getService(); if (service != null && isEnabled() && isValidDevice(device)) { try { - return service.getConnectionPolicy(device); + return service.getConnectionPolicy(device, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, Log.getStackTraceString(new Throwable())); return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; diff --git a/framework/java/android/bluetooth/annotations/RequiresBluetoothAdvertisePermission.java b/framework/java/android/bluetooth/annotations/RequiresBluetoothAdvertisePermission.java new file mode 100644 index 0000000000..c508c2c9ca --- /dev/null +++ b/framework/java/android/bluetooth/annotations/RequiresBluetoothAdvertisePermission.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.bluetooth.annotations; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +import android.Manifest; +import android.os.Build; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * @memberDoc For apps targeting {@link Build.VERSION_CODES#S} or or higher, + * this requires the {@link Manifest.permission#BLUETOOTH_ADVERTISE} + * permission which can be gained with + * {@link android.app.Activity#requestPermissions(String[], int)}. + * @hide + */ +@Retention(SOURCE) +@Target({METHOD, FIELD}) +public @interface RequiresBluetoothAdvertisePermission { +} diff --git a/framework/java/android/bluetooth/annotations/RequiresBluetoothConnectPermission.java b/framework/java/android/bluetooth/annotations/RequiresBluetoothConnectPermission.java new file mode 100644 index 0000000000..e159eaafe2 --- /dev/null +++ b/framework/java/android/bluetooth/annotations/RequiresBluetoothConnectPermission.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.bluetooth.annotations; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +import android.Manifest; +import android.os.Build; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * @memberDoc For apps targeting {@link Build.VERSION_CODES#S} or or higher, + * this requires the {@link Manifest.permission#BLUETOOTH_CONNECT} + * permission which can be gained with + * {@link android.app.Activity#requestPermissions(String[], int)}. + * @hide + */ +@Retention(SOURCE) +@Target({METHOD, FIELD}) +public @interface RequiresBluetoothConnectPermission { +} diff --git a/framework/java/android/bluetooth/annotations/RequiresBluetoothLocationPermission.java b/framework/java/android/bluetooth/annotations/RequiresBluetoothLocationPermission.java new file mode 100644 index 0000000000..2bb3204139 --- /dev/null +++ b/framework/java/android/bluetooth/annotations/RequiresBluetoothLocationPermission.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.bluetooth.annotations; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +import android.Manifest; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * @memberDoc In addition, this requires either the + * {@link Manifest.permission#ACCESS_FINE_LOCATION} + * permission or a strong assertion that you will never derive the + * physical location of the device. You can make this assertion by + * declaring {@code usesPermissionFlags="neverForLocation"} on the + * relevant {@code <uses-permission>} manifest tag, but it may + * restrict the types of Bluetooth devices you can interact with. + * @hide + */ +@Retention(SOURCE) +@Target({METHOD, FIELD}) +public @interface RequiresBluetoothLocationPermission { +} diff --git a/framework/java/android/bluetooth/annotations/RequiresBluetoothScanPermission.java b/framework/java/android/bluetooth/annotations/RequiresBluetoothScanPermission.java new file mode 100644 index 0000000000..800ff39933 --- /dev/null +++ b/framework/java/android/bluetooth/annotations/RequiresBluetoothScanPermission.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.bluetooth.annotations; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +import android.Manifest; +import android.os.Build; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * @memberDoc For apps targeting {@link Build.VERSION_CODES#S} or or higher, + * this requires the {@link Manifest.permission#BLUETOOTH_SCAN} + * permission which can be gained with + * {@link android.app.Activity#requestPermissions(String[], int)}. + * @hide + */ +@Retention(SOURCE) +@Target({METHOD, FIELD}) +public @interface RequiresBluetoothScanPermission { +} diff --git a/framework/java/android/bluetooth/annotations/RequiresLegacyBluetoothAdminPermission.java b/framework/java/android/bluetooth/annotations/RequiresLegacyBluetoothAdminPermission.java new file mode 100644 index 0000000000..9adf695cde --- /dev/null +++ b/framework/java/android/bluetooth/annotations/RequiresLegacyBluetoothAdminPermission.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.bluetooth.annotations; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +import android.Manifest; +import android.os.Build; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * @memberDoc For apps targeting {@link Build.VERSION_CODES#R} or lower, this + * requires the {@link Manifest.permission#BLUETOOTH_ADMIN} + * permission which can be gained with a simple + * {@code <uses-permission>} manifest tag. + * @hide + */ +@Retention(SOURCE) +@Target({METHOD, FIELD}) +public @interface RequiresLegacyBluetoothAdminPermission { +} diff --git a/framework/java/android/bluetooth/annotations/RequiresLegacyBluetoothPermission.java b/framework/java/android/bluetooth/annotations/RequiresLegacyBluetoothPermission.java new file mode 100644 index 0000000000..79621c366f --- /dev/null +++ b/framework/java/android/bluetooth/annotations/RequiresLegacyBluetoothPermission.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.bluetooth.annotations; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +import android.Manifest; +import android.os.Build; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * @memberDoc For apps targeting {@link Build.VERSION_CODES#R} or lower, this + * requires the {@link Manifest.permission#BLUETOOTH} permission + * which can be gained with a simple {@code <uses-permission>} + * manifest tag. + * @hide + */ +@Retention(SOURCE) +@Target({METHOD, FIELD}) +public @interface RequiresLegacyBluetoothPermission { +} diff --git a/framework/java/android/bluetooth/le/AdvertiseData.java b/framework/java/android/bluetooth/le/AdvertiseData.java index fa7ac2b27c..cec658049c 100644 --- a/framework/java/android/bluetooth/le/AdvertiseData.java +++ b/framework/java/android/bluetooth/le/AdvertiseData.java @@ -124,7 +124,7 @@ public final class AdvertiseData implements Parcelable { * @hide */ @Override - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { if (this == obj) { return true; } diff --git a/framework/java/android/bluetooth/le/AdvertisingSet.java b/framework/java/android/bluetooth/le/AdvertisingSet.java index 1df35e1e38..bbdb6953af 100644 --- a/framework/java/android/bluetooth/le/AdvertisingSet.java +++ b/framework/java/android/bluetooth/le/AdvertisingSet.java @@ -16,9 +16,14 @@ package android.bluetooth.le; +import android.annotation.RequiresNoPermission; +import android.annotation.RequiresPermission; import android.bluetooth.BluetoothAdapter; import android.bluetooth.IBluetoothGatt; import android.bluetooth.IBluetoothManager; +import android.bluetooth.annotations.RequiresBluetoothAdvertisePermission; +import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission; +import android.content.AttributionSource; import android.os.RemoteException; import android.util.Log; @@ -27,9 +32,6 @@ import android.util.Log; * <p> * To get an instance of {@link AdvertisingSet}, call the * {@link BluetoothLeAdvertiser#startAdvertisingSet} method. - * <p> - * <b>Note:</b> Most of the methods here require {@link android.Manifest.permission#BLUETOOTH_ADMIN} - * permission. * * @see AdvertiseData */ @@ -38,11 +40,12 @@ public final class AdvertisingSet { private final IBluetoothGatt mGatt; private int mAdvertiserId; + private AttributionSource mAttributionSource; - /* package */ AdvertisingSet(int advertiserId, - IBluetoothManager bluetoothManager) { + /* package */ AdvertisingSet(int advertiserId, IBluetoothManager bluetoothManager, + AttributionSource attributionSource) { mAdvertiserId = advertiserId; - + mAttributionSource = attributionSource; try { mGatt = bluetoothManager.getBluetoothGatt(); } catch (RemoteException e) { @@ -58,8 +61,6 @@ public final class AdvertisingSet { /** * Enables Advertising. This method returns immediately, the operation status is * delivered through {@code callback.onAdvertisingEnabled()}. - * <p> - * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} * * @param enable whether the advertising should be enabled (true), or disabled (false) * @param duration advertising duration, in 10ms unit. Valid range is from 1 (10ms) to 65535 @@ -68,11 +69,14 @@ public final class AdvertisingSet { * controller shall attempt to send prior to terminating the extended advertising, even if the * duration has not expired. Valid range is from 1 to 255. */ + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothAdvertisePermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) public void enableAdvertising(boolean enable, int duration, int maxExtendedAdvertisingEvents) { try { mGatt.enableAdvertisingSet(mAdvertiserId, enable, duration, - maxExtendedAdvertisingEvents); + maxExtendedAdvertisingEvents, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "remote exception - ", e); } @@ -90,9 +94,12 @@ public final class AdvertisingSet { * three bytes will be added for flags. If the update takes place when the advertising set is * enabled, the data can be maximum 251 bytes long. */ + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothAdvertisePermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) public void setAdvertisingData(AdvertiseData advertiseData) { try { - mGatt.setAdvertisingData(mAdvertiserId, advertiseData); + mGatt.setAdvertisingData(mAdvertiserId, advertiseData, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "remote exception - ", e); } @@ -107,9 +114,12 @@ public final class AdvertisingSet { * exceed {@link BluetoothAdapter#getLeMaximumAdvertisingDataLength}. If the update takes place * when the advertising set is enabled, the data can be maximum 251 bytes long. */ + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothAdvertisePermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) public void setScanResponseData(AdvertiseData scanResponse) { try { - mGatt.setScanResponseData(mAdvertiserId, scanResponse); + mGatt.setScanResponseData(mAdvertiserId, scanResponse, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "remote exception - ", e); } @@ -122,9 +132,12 @@ public final class AdvertisingSet { * * @param parameters advertising set parameters. */ + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothAdvertisePermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) public void setAdvertisingParameters(AdvertisingSetParameters parameters) { try { - mGatt.setAdvertisingParameters(mAdvertiserId, parameters); + mGatt.setAdvertisingParameters(mAdvertiserId, parameters, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "remote exception - ", e); } @@ -135,9 +148,12 @@ public final class AdvertisingSet { * periodic advertising is not enabled. This method returns immediately, the operation * status is delivered through {@code callback.onPeriodicAdvertisingParametersUpdated()}. */ + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothAdvertisePermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) public void setPeriodicAdvertisingParameters(PeriodicAdvertisingParameters parameters) { try { - mGatt.setPeriodicAdvertisingParameters(mAdvertiserId, parameters); + mGatt.setPeriodicAdvertisingParameters(mAdvertiserId, parameters, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "remote exception - ", e); } @@ -153,9 +169,12 @@ public final class AdvertisingSet { * BluetoothAdapter#getLeMaximumAdvertisingDataLength}. If the update takes place when the * periodic advertising is enabled for this set, the data can be maximum 251 bytes long. */ + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothAdvertisePermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) public void setPeriodicAdvertisingData(AdvertiseData periodicData) { try { - mGatt.setPeriodicAdvertisingData(mAdvertiserId, periodicData); + mGatt.setPeriodicAdvertisingData(mAdvertiserId, periodicData, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "remote exception - ", e); } @@ -168,9 +187,12 @@ public final class AdvertisingSet { * @param enable whether the periodic advertising should be enabled (true), or disabled * (false). */ + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothAdvertisePermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) public void setPeriodicAdvertisingEnabled(boolean enable) { try { - mGatt.setPeriodicAdvertisingEnable(mAdvertiserId, enable); + mGatt.setPeriodicAdvertisingEnable(mAdvertiserId, enable, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "remote exception - ", e); } @@ -181,13 +203,16 @@ public final class AdvertisingSet { * This method is exposed only for Bluetooth PTS tests, no app or system service * should ever use it. * - * This method requires {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED} permission. - * * @hide */ + @RequiresBluetoothAdvertisePermission + @RequiresPermission(allOf = { + android.Manifest.permission.BLUETOOTH_ADVERTISE, + android.Manifest.permission.BLUETOOTH_PRIVILEGED, + }) public void getOwnAddress() { try { - mGatt.getOwnAddress(mAdvertiserId); + mGatt.getOwnAddress(mAdvertiserId, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "remote exception - ", e); } @@ -198,6 +223,7 @@ public final class AdvertisingSet { * * @hide */ + @RequiresNoPermission public int getAdvertiserId() { return mAdvertiserId; } diff --git a/framework/java/android/bluetooth/le/BluetoothLeAdvertiser.java b/framework/java/android/bluetooth/le/BluetoothLeAdvertiser.java index 5f166f4a41..58029745ab 100644 --- a/framework/java/android/bluetooth/le/BluetoothLeAdvertiser.java +++ b/framework/java/android/bluetooth/le/BluetoothLeAdvertiser.java @@ -16,11 +16,17 @@ package android.bluetooth.le; +import android.annotation.RequiresNoPermission; +import android.annotation.RequiresPermission; +import android.annotation.SuppressLint; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothUuid; import android.bluetooth.IBluetoothGatt; import android.bluetooth.IBluetoothManager; +import android.bluetooth.annotations.RequiresBluetoothAdvertisePermission; +import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission; +import android.content.AttributionSource; import android.os.Handler; import android.os.Looper; import android.os.ParcelUuid; @@ -30,6 +36,7 @@ import android.util.Log; import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.Objects; /** * This class provides a way to perform Bluetooth LE advertise operations, such as starting and @@ -38,9 +45,6 @@ import java.util.Map; * <p> * To get an instance of {@link BluetoothLeAdvertiser}, call the * {@link BluetoothAdapter#getBluetoothLeAdvertiser()} method. - * <p> - * <b>Note:</b> Most of the methods here require {@link android.Manifest.permission#BLUETOOTH_ADMIN} - * permission. * * @see AdvertiseData */ @@ -56,9 +60,11 @@ public final class BluetoothLeAdvertiser { private static final int FLAGS_FIELD_BYTES = 3; private static final int MANUFACTURER_SPECIFIC_DATA_LENGTH = 2; + private final BluetoothAdapter mBluetoothAdapter; private final IBluetoothManager mBluetoothManager; + private final AttributionSource mAttributionSource; + private final Handler mHandler; - private BluetoothAdapter mBluetoothAdapter; private final Map<AdvertiseCallback, AdvertisingSetCallback> mLegacyAdvertisers = new HashMap<>(); private final Map<AdvertisingSetCallback, IAdvertisingSetCallback> @@ -72,22 +78,24 @@ public final class BluetoothLeAdvertiser { * @param bluetoothManager BluetoothManager that conducts overall Bluetooth Management * @hide */ - public BluetoothLeAdvertiser(IBluetoothManager bluetoothManager) { - mBluetoothManager = bluetoothManager; - mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + public BluetoothLeAdvertiser(BluetoothAdapter bluetoothAdapter) { + mBluetoothAdapter = Objects.requireNonNull(bluetoothAdapter); + mBluetoothManager = mBluetoothAdapter.getBluetoothManager(); + mAttributionSource = mBluetoothAdapter.getAttributionSource(); mHandler = new Handler(Looper.getMainLooper()); } /** * Start Bluetooth LE Advertising. On success, the {@code advertiseData} will be broadcasted. * Returns immediately, the operation status is delivered through {@code callback}. - * <p> - * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission. * * @param settings Settings for Bluetooth LE advertising. * @param advertiseData Advertisement data to be broadcasted. * @param callback Callback for advertising status. */ + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothAdvertisePermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) public void startAdvertising(AdvertiseSettings settings, AdvertiseData advertiseData, final AdvertiseCallback callback) { startAdvertising(settings, advertiseData, null, callback); @@ -98,14 +106,15 @@ public final class BluetoothLeAdvertiser { * operation succeeds. The {@code scanResponse} is returned when a scanning device sends an * active scan request. This method returns immediately, the operation status is delivered * through {@code callback}. - * <p> - * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} * * @param settings Settings for Bluetooth LE advertising. * @param advertiseData Advertisement data to be advertised in advertisement packet. * @param scanResponse Scan response associated with the advertisement data. * @param callback Callback for advertising status. */ + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothAdvertisePermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) public void startAdvertising(AdvertiseSettings settings, AdvertiseData advertiseData, AdvertiseData scanResponse, final AdvertiseCallback callback) { @@ -160,6 +169,10 @@ public final class BluetoothLeAdvertiser { } } + @SuppressLint({ + "AndroidFrameworkBluetoothPermission", + "AndroidFrameworkRequiresPermission", + }) AdvertisingSetCallback wrapOldCallback(AdvertiseCallback callback, AdvertiseSettings settings) { return new AdvertisingSetCallback() { @Override @@ -192,11 +205,12 @@ public final class BluetoothLeAdvertiser { /** * Stop Bluetooth LE advertising. The {@code callback} must be the same one use in * {@link BluetoothLeAdvertiser#startAdvertising}. - * <p> - * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission. * * @param callback {@link AdvertiseCallback} identifies the advertising instance to stop. */ + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothAdvertisePermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) public void stopAdvertising(final AdvertiseCallback callback) { synchronized (mLegacyAdvertisers) { if (callback == null) { @@ -232,6 +246,9 @@ public final class BluetoothLeAdvertiser { * size, or unsupported advertising PHY is selected, or when attempt to use Periodic Advertising * feature is made when it's not supported by the controller. */ + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothAdvertisePermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) public void startAdvertisingSet(AdvertisingSetParameters parameters, AdvertiseData advertiseData, AdvertiseData scanResponse, PeriodicAdvertisingParameters periodicParameters, @@ -262,6 +279,9 @@ public final class BluetoothLeAdvertiser { * size, or unsupported advertising PHY is selected, or when attempt to use Periodic Advertising * feature is made when it's not supported by the controller. */ + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothAdvertisePermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) public void startAdvertisingSet(AdvertisingSetParameters parameters, AdvertiseData advertiseData, AdvertiseData scanResponse, PeriodicAdvertisingParameters periodicParameters, @@ -297,6 +317,9 @@ public final class BluetoothLeAdvertiser { * size, or unsupported advertising PHY is selected, or when attempt to use Periodic Advertising * feature is made when it's not supported by the controller. */ + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothAdvertisePermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) public void startAdvertisingSet(AdvertisingSetParameters parameters, AdvertiseData advertiseData, AdvertiseData scanResponse, PeriodicAdvertisingParameters periodicParameters, @@ -337,6 +360,9 @@ public final class BluetoothLeAdvertiser { * maxExtendedAdvertisingEvents is used on a controller that doesn't support the LE Extended * Advertising */ + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothAdvertisePermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) public void startAdvertisingSet(AdvertisingSetParameters parameters, AdvertiseData advertiseData, AdvertiseData scanResponse, PeriodicAdvertisingParameters periodicParameters, @@ -432,7 +458,8 @@ public final class BluetoothLeAdvertiser { try { gatt.startAdvertisingSet(parameters, advertiseData, scanResponse, periodicParameters, - periodicData, duration, maxExtendedAdvertisingEvents, wrapped); + periodicData, duration, maxExtendedAdvertisingEvents, wrapped, + mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "Failed to start advertising set - ", e); postStartSetFailure(handler, callback, @@ -445,6 +472,9 @@ public final class BluetoothLeAdvertiser { * Used to dispose of a {@link AdvertisingSet} object, obtained with {@link * BluetoothLeAdvertiser#startAdvertisingSet}. */ + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothAdvertisePermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) public void stopAdvertisingSet(AdvertisingSetCallback callback) { if (callback == null) { throw new IllegalArgumentException("callback cannot be null"); @@ -458,7 +488,7 @@ public final class BluetoothLeAdvertiser { IBluetoothGatt gatt; try { gatt = mBluetoothManager.getBluetoothGatt(); - gatt.stopAdvertisingSet(wrapped); + gatt.stopAdvertisingSet(wrapped, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "Failed to stop advertising - ", e); } @@ -469,6 +499,7 @@ public final class BluetoothLeAdvertiser { * * @hide */ + @RequiresNoPermission public void cleanup() { mLegacyAdvertisers.clear(); mCallbackWrappers.clear(); @@ -476,6 +507,8 @@ public final class BluetoothLeAdvertiser { } // Compute the size of advertisement data or scan resp + @RequiresBluetoothAdvertisePermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE) private int totalBytes(AdvertiseData data, boolean isFlagsIncluded) { if (data == null) return 0; // Flags field is omitted if the advertising is not connectable. @@ -546,8 +579,11 @@ public final class BluetoothLeAdvertiser { if (data.getIncludeTxPowerLevel()) { size += OVERHEAD_BYTES_PER_FIELD + 1; // tx power level value is one byte. } - if (data.getIncludeDeviceName() && mBluetoothAdapter.getName() != null) { - size += OVERHEAD_BYTES_PER_FIELD + mBluetoothAdapter.getName().length(); + if (data.getIncludeDeviceName()) { + final int length = mBluetoothAdapter.getNameLengthForAdvertise(); + if (length >= 0) { + size += OVERHEAD_BYTES_PER_FIELD + length; + } } return size; } @@ -556,6 +592,7 @@ public final class BluetoothLeAdvertiser { return array == null ? 0 : array.length; } + @SuppressLint("AndroidFrameworkBluetoothPermission") IAdvertisingSetCallback wrap(AdvertisingSetCallback callback, Handler handler) { return new IAdvertisingSetCallback.Stub() { @Override @@ -569,8 +606,8 @@ public final class BluetoothLeAdvertiser { return; } - AdvertisingSet advertisingSet = - new AdvertisingSet(advertiserId, mBluetoothManager); + AdvertisingSet advertisingSet = new AdvertisingSet( + advertiserId, mBluetoothManager, mAttributionSource); mAdvertisingSets.put(advertiserId, advertisingSet); callback.onAdvertisingSetStarted(advertisingSet, txPower, status); } @@ -680,6 +717,7 @@ public final class BluetoothLeAdvertiser { }; } + @SuppressLint("AndroidFrameworkBluetoothPermission") private void postStartSetFailure(Handler handler, final AdvertisingSetCallback callback, final int error) { handler.post(new Runnable() { @@ -690,6 +728,7 @@ public final class BluetoothLeAdvertiser { }); } + @SuppressLint("AndroidFrameworkBluetoothPermission") private void postStartFailure(final AdvertiseCallback callback, final int error) { mHandler.post(new Runnable() { @Override @@ -699,6 +738,7 @@ public final class BluetoothLeAdvertiser { }); } + @SuppressLint("AndroidFrameworkBluetoothPermission") private void postStartSuccess(final AdvertiseCallback callback, final AdvertiseSettings settings) { mHandler.post(new Runnable() { diff --git a/framework/java/android/bluetooth/le/BluetoothLeScanner.java b/framework/java/android/bluetooth/le/BluetoothLeScanner.java index 2888fbd8a3..34aac8bfdb 100644 --- a/framework/java/android/bluetooth/le/BluetoothLeScanner.java +++ b/framework/java/android/bluetooth/le/BluetoothLeScanner.java @@ -16,16 +16,22 @@ package android.bluetooth.le; -import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresNoPermission; import android.annotation.RequiresPermission; +import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.app.PendingIntent; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothGatt; import android.bluetooth.IBluetoothGatt; import android.bluetooth.IBluetoothManager; +import android.bluetooth.annotations.RequiresBluetoothLocationPermission; +import android.bluetooth.annotations.RequiresBluetoothScanPermission; +import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission; +import android.content.Attributable; +import android.content.AttributionSource; import android.os.Handler; import android.os.Looper; import android.os.RemoteException; @@ -36,6 +42,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; /** * This class provides methods to perform scan related operations for Bluetooth LE devices. An @@ -44,9 +51,6 @@ import java.util.Map; * <p> * Use {@link BluetoothAdapter#getBluetoothLeScanner()} to get an instance of * {@link BluetoothLeScanner}. - * <p> - * <b>Note:</b> Most of the scan methods here require - * {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission. * * @see ScanFilter */ @@ -78,14 +82,13 @@ public final class BluetoothLeScanner { */ public static final String EXTRA_CALLBACK_TYPE = "android.bluetooth.le.extra.CALLBACK_TYPE"; + private final BluetoothAdapter mBluetoothAdapter; private final IBluetoothManager mBluetoothManager; + private final AttributionSource mAttributionSource; + private final Handler mHandler; - private BluetoothAdapter mBluetoothAdapter; private final Map<ScanCallback, BleScanCallbackWrapper> mLeScanClients; - private final String mOpPackageName; - private final String mFeatureId; - /** * Use {@link BluetoothAdapter#getBluetoothLeScanner()} instead. * @@ -94,14 +97,12 @@ public final class BluetoothLeScanner { * @param featureId The featureId of the context this object was created from * @hide */ - public BluetoothLeScanner(IBluetoothManager bluetoothManager, - @NonNull String opPackageName, @Nullable String featureId) { - mBluetoothManager = bluetoothManager; - mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + public BluetoothLeScanner(BluetoothAdapter bluetoothAdapter) { + mBluetoothAdapter = Objects.requireNonNull(bluetoothAdapter); + mBluetoothManager = mBluetoothAdapter.getBluetoothManager(); + mAttributionSource = mBluetoothAdapter.getAttributionSource(); mHandler = new Handler(Looper.getMainLooper()); mLeScanClients = new HashMap<ScanCallback, BleScanCallbackWrapper>(); - mOpPackageName = opPackageName; - mFeatureId = featureId; } /** @@ -119,7 +120,10 @@ public final class BluetoothLeScanner { * @param callback Callback used to deliver scan results. * @throws IllegalArgumentException If {@code callback} is null. */ - @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothScanPermission + @RequiresBluetoothLocationPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public void startScan(final ScanCallback callback) { startScan(null, new ScanSettings.Builder().build(), callback); } @@ -141,7 +145,10 @@ public final class BluetoothLeScanner { * @param callback Callback used to deliver scan results. * @throws IllegalArgumentException If {@code settings} or {@code callback} is null. */ - @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothScanPermission + @RequiresBluetoothLocationPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public void startScan(List<ScanFilter> filters, ScanSettings settings, final ScanCallback callback) { startScan(filters, settings, null, callback, /*callbackIntent=*/ null, null); @@ -170,7 +177,10 @@ public final class BluetoothLeScanner { * could not be sent. * @see #stopScan(PendingIntent) */ - @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothScanPermission + @RequiresBluetoothLocationPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public int startScan(@Nullable List<ScanFilter> filters, @Nullable ScanSettings settings, @NonNull PendingIntent callbackIntent) { return startScan(filters, @@ -188,8 +198,13 @@ public final class BluetoothLeScanner { * @hide */ @SystemApi + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothScanPermission + @RequiresBluetoothLocationPermission @RequiresPermission(allOf = { - Manifest.permission.BLUETOOTH_ADMIN, Manifest.permission.UPDATE_DEVICE_STATS}) + android.Manifest.permission.BLUETOOTH_SCAN, + android.Manifest.permission.UPDATE_DEVICE_STATS + }) public void startScanFromSource(final WorkSource workSource, final ScanCallback callback) { startScanFromSource(null, new ScanSettings.Builder().build(), workSource, callback); } @@ -206,13 +221,20 @@ public final class BluetoothLeScanner { * @hide */ @SystemApi + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothScanPermission + @RequiresBluetoothLocationPermission @RequiresPermission(allOf = { - Manifest.permission.BLUETOOTH_ADMIN, Manifest.permission.UPDATE_DEVICE_STATS}) + android.Manifest.permission.BLUETOOTH_SCAN, + android.Manifest.permission.UPDATE_DEVICE_STATS + }) + @SuppressLint("AndroidFrameworkRequiresPermission") public void startScanFromSource(List<ScanFilter> filters, ScanSettings settings, final WorkSource workSource, final ScanCallback callback) { startScan(filters, settings, workSource, callback, null, null); } + @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) private int startScan(List<ScanFilter> filters, ScanSettings settings, final WorkSource workSource, final ScanCallback callback, final PendingIntent callbackIntent, @@ -256,8 +278,8 @@ public final class BluetoothLeScanner { wrapper.startRegistration(); } else { try { - gatt.startScanForIntent(callbackIntent, settings, filters, mOpPackageName, - mFeatureId); + gatt.startScanForIntent(callbackIntent, settings, filters, + mAttributionSource); } catch (RemoteException e) { return ScanCallback.SCAN_FAILED_INTERNAL_ERROR; } @@ -271,7 +293,9 @@ public final class BluetoothLeScanner { * * @param callback */ - @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothScanPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public void stopScan(ScanCallback callback) { BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter); synchronized (mLeScanClients) { @@ -292,13 +316,15 @@ public final class BluetoothLeScanner { * @param callbackIntent The PendingIntent that was used to start the scan. * @see #startScan(List, ScanSettings, PendingIntent) */ - @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothScanPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public void stopScan(PendingIntent callbackIntent) { BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter); IBluetoothGatt gatt; try { gatt = mBluetoothManager.getBluetoothGatt(); - gatt.stopScanForIntent(callbackIntent, mOpPackageName); + gatt.stopScanForIntent(callbackIntent, mAttributionSource); } catch (RemoteException e) { } } @@ -311,6 +337,9 @@ public final class BluetoothLeScanner { * @param callback Callback of the Bluetooth LE Scan, it has to be the same instance as the one * used to start scan. */ + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothScanPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public void flushPendingScanResults(ScanCallback callback) { BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter); if (callback == null) { @@ -331,6 +360,8 @@ public final class BluetoothLeScanner { * @hide */ @SystemApi + @RequiresBluetoothScanPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public void startTruncatedScan(List<TruncatedFilter> truncatedFilters, ScanSettings settings, final ScanCallback callback) { int filterSize = truncatedFilters.size(); @@ -349,6 +380,7 @@ public final class BluetoothLeScanner { * * @hide */ + @RequiresNoPermission public void cleanup() { mLeScanClients.clear(); } @@ -356,6 +388,7 @@ public final class BluetoothLeScanner { /** * Bluetooth GATT interface callbacks */ + @SuppressLint("AndroidFrameworkRequiresPermission") private class BleScanCallbackWrapper extends IScannerCallback.Stub { private static final int REGISTRATION_CALLBACK_TIMEOUT_MILLIS = 2000; @@ -390,7 +423,7 @@ public final class BluetoothLeScanner { // Scan stopped. if (mScannerId == -1 || mScannerId == -2) return; try { - mBluetoothGatt.registerScanner(this, mWorkSource); + mBluetoothGatt.registerScanner(this, mWorkSource, mAttributionSource); wait(REGISTRATION_CALLBACK_TIMEOUT_MILLIS); } catch (InterruptedException | RemoteException e) { Log.e(TAG, "application registeration exception", e); @@ -412,6 +445,7 @@ public final class BluetoothLeScanner { } } + @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public void stopLeScan() { synchronized (this) { if (mScannerId <= 0) { @@ -419,8 +453,8 @@ public final class BluetoothLeScanner { return; } try { - mBluetoothGatt.stopScan(mScannerId); - mBluetoothGatt.unregisterScanner(mScannerId); + mBluetoothGatt.stopScan(mScannerId, mAttributionSource); + mBluetoothGatt.unregisterScanner(mScannerId, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "Failed to stop scan and unregister", e); } @@ -428,6 +462,7 @@ public final class BluetoothLeScanner { } } + @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) void flushPendingBatchResults() { synchronized (this) { if (mScannerId <= 0) { @@ -435,7 +470,7 @@ public final class BluetoothLeScanner { return; } try { - mBluetoothGatt.flushPendingBatchResults(mScannerId); + mBluetoothGatt.flushPendingBatchResults(mScannerId, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "Failed to get pending scan results", e); } @@ -454,11 +489,11 @@ public final class BluetoothLeScanner { try { if (mScannerId == -1) { // Registration succeeds after timeout, unregister scanner. - mBluetoothGatt.unregisterScanner(scannerId); + mBluetoothGatt.unregisterScanner(scannerId, mAttributionSource); } else { mScannerId = scannerId; mBluetoothGatt.startScan(mScannerId, mSettings, mFilters, - mResultStorages, mOpPackageName, mFeatureId); + mResultStorages, mAttributionSource); } } catch (RemoteException e) { Log.e(TAG, "fail to start le scan: " + e); @@ -482,6 +517,7 @@ public final class BluetoothLeScanner { */ @Override public void onScanResult(final ScanResult scanResult) { + Attributable.setAttributionSource(scanResult, mAttributionSource); if (VDBG) Log.d(TAG, "onScanResult() - " + scanResult.toString()); // Check null in case the scan has been stopped @@ -499,6 +535,7 @@ public final class BluetoothLeScanner { @Override public void onBatchScanResults(final List<ScanResult> results) { + Attributable.setAttributionSource(results, mAttributionSource); Handler handler = new Handler(Looper.getMainLooper()); handler.post(new Runnable() { @Override @@ -510,6 +547,7 @@ public final class BluetoothLeScanner { @Override public void onFoundOrLost(final boolean onFound, final ScanResult scanResult) { + Attributable.setAttributionSource(scanResult, mAttributionSource); if (VDBG) { Log.d(TAG, "onFoundOrLost() - onFound = " + onFound + " " + scanResult.toString()); } @@ -558,6 +596,7 @@ public final class BluetoothLeScanner { } } + @SuppressLint("AndroidFrameworkBluetoothPermission") private void postCallbackError(final ScanCallback callback, final int errorCode) { mHandler.post(new Runnable() { @Override @@ -598,6 +637,7 @@ public final class BluetoothLeScanner { return true; } + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) private boolean isHardwareResourcesAvailableForScan(ScanSettings settings) { final int callbackType = settings.getCallbackType(); if ((callbackType & ScanSettings.CALLBACK_TYPE_FIRST_MATCH) != 0 diff --git a/framework/java/android/bluetooth/le/PeriodicAdvertisingManager.java b/framework/java/android/bluetooth/le/PeriodicAdvertisingManager.java index 0f1a8e913b..dea686d18e 100644 --- a/framework/java/android/bluetooth/le/PeriodicAdvertisingManager.java +++ b/framework/java/android/bluetooth/le/PeriodicAdvertisingManager.java @@ -16,10 +16,17 @@ package android.bluetooth.le; +import android.annotation.RequiresPermission; +import android.annotation.SuppressLint; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.IBluetoothGatt; import android.bluetooth.IBluetoothManager; +import android.bluetooth.annotations.RequiresBluetoothLocationPermission; +import android.bluetooth.annotations.RequiresBluetoothScanPermission; +import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission; +import android.content.Attributable; +import android.content.AttributionSource; import android.os.Handler; import android.os.Looper; import android.os.RemoteException; @@ -27,6 +34,7 @@ import android.util.Log; import java.util.IdentityHashMap; import java.util.Map; +import java.util.Objects; /** * This class provides methods to perform periodic advertising related @@ -35,9 +43,6 @@ import java.util.Map; * <p> * Use {@link BluetoothAdapter#getPeriodicAdvertisingManager()} to get an * instance of {@link PeriodicAdvertisingManager}. - * <p> - * <b>Note:</b> Most of the methods here require - * {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission. * * @hide */ @@ -52,8 +57,9 @@ public final class PeriodicAdvertisingManager { private static final int SYNC_STARTING = -1; + private final BluetoothAdapter mBluetoothAdapter; private final IBluetoothManager mBluetoothManager; - private BluetoothAdapter mBluetoothAdapter; + private final AttributionSource mAttributionSource; /* maps callback, to callback wrapper and sync handle */ Map<PeriodicAdvertisingCallback, @@ -65,9 +71,10 @@ public final class PeriodicAdvertisingManager { * @param bluetoothManager BluetoothManager that conducts overall Bluetooth Management. * @hide */ - public PeriodicAdvertisingManager(IBluetoothManager bluetoothManager) { - mBluetoothManager = bluetoothManager; - mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + public PeriodicAdvertisingManager(BluetoothAdapter bluetoothAdapter) { + mBluetoothAdapter = Objects.requireNonNull(bluetoothAdapter); + mBluetoothManager = mBluetoothAdapter.getBluetoothManager(); + mAttributionSource = mBluetoothAdapter.getAttributionSource(); mCallbackWrappers = new IdentityHashMap<>(); } @@ -89,6 +96,10 @@ public final class PeriodicAdvertisingManager { * @throws IllegalArgumentException if {@code scanResult} is null or {@code skip} is invalid or * {@code timeout} is invalid or {@code callback} is null. */ + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothScanPermission + @RequiresBluetoothLocationPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public void registerSync(ScanResult scanResult, int skip, int timeout, PeriodicAdvertisingCallback callback) { registerSync(scanResult, skip, timeout, callback, null); @@ -113,6 +124,10 @@ public final class PeriodicAdvertisingManager { * @throws IllegalArgumentException if {@code scanResult} is null or {@code skip} is invalid or * {@code timeout} is invalid or {@code callback} is null. */ + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothScanPermission + @RequiresBluetoothLocationPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public void registerSync(ScanResult scanResult, int skip, int timeout, PeriodicAdvertisingCallback callback, Handler handler) { if (callback == null) { @@ -156,7 +171,8 @@ public final class PeriodicAdvertisingManager { mCallbackWrappers.put(callback, wrapped); try { - gatt.registerSync(scanResult, skip, timeout, wrapped); + gatt.registerSync( + scanResult, skip, timeout, wrapped, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "Failed to register sync - ", e); return; @@ -170,6 +186,9 @@ public final class PeriodicAdvertisingManager { * @throws IllegalArgumentException if {@code callback} is null, or not a properly registered * callback. */ + @RequiresLegacyBluetoothAdminPermission + @RequiresBluetoothScanPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public void unregisterSync(PeriodicAdvertisingCallback callback) { if (callback == null) { throw new IllegalArgumentException("callback can't be null"); @@ -189,19 +208,20 @@ public final class PeriodicAdvertisingManager { } try { - gatt.unregisterSync(wrapper); + gatt.unregisterSync(wrapper, mAttributionSource); } catch (RemoteException e) { Log.e(TAG, "Failed to cancel sync creation - ", e); return; } } + @SuppressLint("AndroidFrameworkBluetoothPermission") private IPeriodicAdvertisingCallback wrap(PeriodicAdvertisingCallback callback, Handler handler) { return new IPeriodicAdvertisingCallback.Stub() { public void onSyncEstablished(int syncHandle, BluetoothDevice device, int advertisingSid, int skip, int timeout, int status) { - + Attributable.setAttributionSource(device, mAttributionSource); handler.post(new Runnable() { @Override public void run() { diff --git a/framework/java/android/bluetooth/le/PeriodicAdvertisingReport.java b/framework/java/android/bluetooth/le/PeriodicAdvertisingReport.java index 7a8c2c6716..54b953c25c 100644 --- a/framework/java/android/bluetooth/le/PeriodicAdvertisingReport.java +++ b/framework/java/android/bluetooth/le/PeriodicAdvertisingReport.java @@ -148,7 +148,7 @@ public final class PeriodicAdvertisingReport implements Parcelable { } @Override - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { if (this == obj) { return true; } diff --git a/framework/java/android/bluetooth/le/ScanFilter.java b/framework/java/android/bluetooth/le/ScanFilter.java index ddc93327b6..cb3bf29767 100644 --- a/framework/java/android/bluetooth/le/ScanFilter.java +++ b/framework/java/android/bluetooth/le/ScanFilter.java @@ -507,7 +507,7 @@ public final class ScanFilter implements Parcelable { } @Override - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { if (this == obj) { return true; } diff --git a/framework/java/android/bluetooth/le/ScanRecord.java b/framework/java/android/bluetooth/le/ScanRecord.java index c0c1aa1634..9b8c2eaf4d 100644 --- a/framework/java/android/bluetooth/le/ScanRecord.java +++ b/framework/java/android/bluetooth/le/ScanRecord.java @@ -18,6 +18,7 @@ package android.bluetooth.le; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.bluetooth.BluetoothUuid; import android.compat.annotation.UnsupportedAppUsage; import android.os.ParcelUuid; @@ -29,10 +30,12 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.function.Predicate; /** * Represents a scan record from Bluetooth LE scan. */ +@SuppressLint("AndroidFrameworkBluetoothPermission") public final class ScanRecord { private static final String TAG = "ScanRecord"; @@ -168,6 +171,27 @@ public final class ScanRecord { return mBytes; } + /** + * Test if any fields contained inside this scan record are matched by the + * given matcher. + * + * @hide + */ + public boolean matchesAnyField(@NonNull Predicate<byte[]> matcher) { + int pos = 0; + while (pos < mBytes.length) { + final int length = mBytes[pos] & 0xFF; + if (length == 0) { + break; + } + if (matcher.test(Arrays.copyOfRange(mBytes, pos, pos + length + 1))) { + return true; + } + pos += length + 1; + } + return false; + } + private ScanRecord(List<ParcelUuid> serviceUuids, List<ParcelUuid> serviceSolicitationUuids, SparseArray<byte[]> manufacturerData, diff --git a/framework/java/android/bluetooth/le/ScanResult.java b/framework/java/android/bluetooth/le/ScanResult.java index 855d345417..5228456284 100644 --- a/framework/java/android/bluetooth/le/ScanResult.java +++ b/framework/java/android/bluetooth/le/ScanResult.java @@ -16,8 +16,11 @@ package android.bluetooth.le; +import android.annotation.NonNull; import android.annotation.Nullable; import android.bluetooth.BluetoothDevice; +import android.content.Attributable; +import android.content.AttributionSource; import android.os.Parcel; import android.os.Parcelable; @@ -26,7 +29,7 @@ import java.util.Objects; /** * ScanResult for Bluetooth LE scan. */ -public final class ScanResult implements Parcelable { +public final class ScanResult implements Parcelable, Attributable { /** * For chained advertisements, inidcates tha the data contained in this @@ -195,6 +198,11 @@ public final class ScanResult implements Parcelable { return 0; } + /** {@hide} */ + public void setAttributionSource(@NonNull AttributionSource attributionSource) { + Attributable.setAttributionSource(mDevice, attributionSource); + } + /** * Returns the remote Bluetooth device identified by the Bluetooth device address. */ @@ -309,7 +317,7 @@ public final class ScanResult implements Parcelable { } @Override - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { if (this == obj) { return true; } diff --git a/framework/java/android/bluetooth/le/TruncatedFilter.java b/framework/java/android/bluetooth/le/TruncatedFilter.java index a753aa6fef..93f526bb9f 100644 --- a/framework/java/android/bluetooth/le/TruncatedFilter.java +++ b/framework/java/android/bluetooth/le/TruncatedFilter.java @@ -16,6 +16,7 @@ package android.bluetooth.le; +import android.annotation.SuppressLint; import android.annotation.SystemApi; import java.util.List; @@ -26,6 +27,7 @@ import java.util.List; * @hide */ @SystemApi +@SuppressLint("AndroidFrameworkBluetoothPermission") public final class TruncatedFilter { private final ScanFilter mFilter; private final List<ResultStorageDescriptor> mStorageDescriptors; |