aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--framework/java/android/bluetooth/BluetoothA2dp.java137
-rwxr-xr-xframework/java/android/bluetooth/BluetoothA2dpSink.java3
-rw-r--r--framework/java/android/bluetooth/BluetoothAdapter.java379
-rwxr-xr-xframework/java/android/bluetooth/BluetoothClass.java4
-rw-r--r--framework/java/android/bluetooth/BluetoothCodecConfig.java3
-rw-r--r--framework/java/android/bluetooth/BluetoothCodecStatus.java9
-rw-r--r--framework/java/android/bluetooth/BluetoothDevice.java243
-rw-r--r--framework/java/android/bluetooth/BluetoothGatt.java56
-rw-r--r--framework/java/android/bluetooth/BluetoothGattCallback.java16
-rw-r--r--framework/java/android/bluetooth/BluetoothGattCharacteristic.java6
-rw-r--r--framework/java/android/bluetooth/BluetoothGattDescriptor.java6
-rw-r--r--framework/java/android/bluetooth/BluetoothGattServer.java21
-rw-r--r--framework/java/android/bluetooth/BluetoothGattServerCallback.java2
-rw-r--r--framework/java/android/bluetooth/BluetoothGattService.java5
-rw-r--r--framework/java/android/bluetooth/BluetoothHeadset.java58
-rw-r--r--framework/java/android/bluetooth/BluetoothHeadsetClient.java14
-rw-r--r--framework/java/android/bluetooth/BluetoothHeadsetClientCall.java11
-rw-r--r--framework/java/android/bluetooth/BluetoothHearingAid.java7
-rw-r--r--framework/java/android/bluetooth/BluetoothLeAudio.java451
-rw-r--r--framework/java/android/bluetooth/BluetoothManager.java41
-rw-r--r--framework/java/android/bluetooth/BluetoothMap.java3
-rw-r--r--framework/java/android/bluetooth/BluetoothMapClient.java170
-rw-r--r--framework/java/android/bluetooth/BluetoothPan.java40
-rw-r--r--framework/java/android/bluetooth/BluetoothPbap.java3
-rw-r--r--framework/java/android/bluetooth/BluetoothPbapClient.java3
-rw-r--r--framework/java/android/bluetooth/BluetoothProfile.java13
-rw-r--r--framework/java/android/bluetooth/BluetoothSap.java6
-rw-r--r--framework/java/android/bluetooth/BluetoothServerSocket.java2
-rw-r--r--framework/java/android/bluetooth/BluetoothSocket.java14
-rw-r--r--framework/java/android/bluetooth/BluetoothUuid.java17
-rw-r--r--framework/java/android/bluetooth/BufferConstraint.java105
-rw-r--r--framework/java/android/bluetooth/BufferConstraints.java96
-rw-r--r--framework/java/android/bluetooth/OWNERS4
-rw-r--r--framework/java/android/bluetooth/OobData.java982
-rw-r--r--framework/java/android/bluetooth/SdpDipRecord.java104
-rw-r--r--framework/java/android/bluetooth/le/AdvertiseData.java54
-rw-r--r--framework/java/android/bluetooth/le/BluetoothLeAdvertiser.java27
-rw-r--r--framework/java/android/bluetooth/le/BluetoothLeScanner.java15
-rw-r--r--framework/java/android/bluetooth/le/OWNERS4
-rw-r--r--framework/java/android/bluetooth/le/PeriodicAdvertisingParameters.java2
-rw-r--r--framework/java/android/bluetooth/le/ScanFilter.java163
-rw-r--r--framework/java/android/bluetooth/le/ScanSettings.java23
-rw-r--r--framework/tests/Android.bp9
-rw-r--r--framework/tests/AndroidManifest.xml6
-rw-r--r--framework/tests/OWNERS1
-rw-r--r--framework/tests/src/android/bluetooth/BluetoothStressTest.java24
-rw-r--r--framework/tests/src/android/bluetooth/BluetoothTestRunner.java11
-rw-r--r--framework/tests/src/android/bluetooth/BluetoothTestUtils.java156
-rw-r--r--service/java/com/android/server/bluetooth/BluetoothAirplaneModeListener.java126
-rw-r--r--service/java/com/android/server/bluetooth/BluetoothDeviceConfigListener.java76
-rw-r--r--service/java/com/android/server/bluetooth/BluetoothManagerService.java251
-rw-r--r--service/java/com/android/server/bluetooth/BluetoothModeChangeHelper.java143
-rw-r--r--service/tests/src/com/android/server/BluetoothAirplaneModeListenerTest.java6
53 files changed, 3660 insertions, 471 deletions
diff --git a/framework/java/android/bluetooth/BluetoothA2dp.java b/framework/java/android/bluetooth/BluetoothA2dp.java
index 5374d6d55e..16413e1a1d 100644
--- a/framework/java/android/bluetooth/BluetoothA2dp.java
+++ b/framework/java/android/bluetooth/BluetoothA2dp.java
@@ -118,7 +118,7 @@ public final class BluetoothA2dp implements BluetoothProfile {
* @hide
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(trackingBug = 171933273)
public static final String ACTION_ACTIVE_DEVICE_CHANGED =
"android.bluetooth.a2dp.profile.action.ACTIVE_DEVICE_CHANGED";
@@ -139,7 +139,7 @@ public final class BluetoothA2dp implements BluetoothProfile {
* @hide
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(trackingBug = 181103983)
public static final String ACTION_CODEC_CONFIG_CHANGED =
"android.bluetooth.a2dp.profile.action.CODEC_CONFIG_CHANGED";
@@ -225,6 +225,39 @@ public final class BluetoothA2dp implements BluetoothProfile {
@SystemApi
public static final int OPTIONAL_CODECS_PREF_ENABLED = 1;
+ /** @hide */
+ @IntDef(prefix = "DYNAMIC_BUFFER_SUPPORT_", value = {
+ DYNAMIC_BUFFER_SUPPORT_NONE,
+ DYNAMIC_BUFFER_SUPPORT_A2DP_OFFLOAD,
+ DYNAMIC_BUFFER_SUPPORT_A2DP_SOFTWARE_ENCODING
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Type {}
+
+ /**
+ * Indicates the supported type of Dynamic Audio Buffer is not supported.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int DYNAMIC_BUFFER_SUPPORT_NONE = 0;
+
+ /**
+ * Indicates the supported type of Dynamic Audio Buffer is A2DP offload.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int DYNAMIC_BUFFER_SUPPORT_A2DP_OFFLOAD = 1;
+
+ /**
+ * Indicates the supported type of Dynamic Audio Buffer is A2DP software encoding.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int DYNAMIC_BUFFER_SUPPORT_A2DP_SOFTWARE_ENCODING = 2;
+
private BluetoothAdapter mAdapter;
private final BluetoothProfileConnector<IBluetoothA2dp> mProfileConnector =
new BluetoothProfileConnector(this, BluetoothProfile.A2DP, "BluetoothA2dp",
@@ -409,7 +442,7 @@ public final class BluetoothA2dp implements BluetoothProfile {
* @hide
*/
@RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(trackingBug = 171933273)
public boolean setActiveDevice(@Nullable BluetoothDevice device) {
if (DBG) log("setActiveDevice(" + device + ")");
try {
@@ -433,7 +466,7 @@ public final class BluetoothA2dp implements BluetoothProfile {
* is active
* @hide
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(trackingBug = 171933273)
@Nullable
@RequiresPermission(Manifest.permission.BLUETOOTH)
public BluetoothDevice getActiveDevice() {
@@ -651,7 +684,7 @@ public final class BluetoothA2dp implements BluetoothProfile {
* @return the current codec status
* @hide
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(trackingBug = 181103983)
@Nullable
@RequiresPermission(Manifest.permission.BLUETOOTH)
public BluetoothCodecStatus getCodecStatus(@NonNull BluetoothDevice device) {
@@ -680,7 +713,7 @@ public final class BluetoothA2dp implements BluetoothProfile {
* @param codecConfig the codec configuration preference
* @hide
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(trackingBug = 181103983)
@RequiresPermission(Manifest.permission.BLUETOOTH)
public void setCodecConfigPreference(@NonNull BluetoothDevice device,
@NonNull BluetoothCodecConfig codecConfig) {
@@ -710,7 +743,7 @@ public final class BluetoothA2dp implements BluetoothProfile {
* active A2DP Bluetooth device.
* @hide
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@RequiresPermission(Manifest.permission.BLUETOOTH)
public void enableOptionalCodecs(@NonNull BluetoothDevice device) {
if (DBG) Log.d(TAG, "enableOptionalCodecs(" + device + ")");
@@ -725,7 +758,7 @@ public final class BluetoothA2dp implements BluetoothProfile {
* active A2DP Bluetooth device.
* @hide
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@RequiresPermission(Manifest.permission.BLUETOOTH)
public void disableOptionalCodecs(@NonNull BluetoothDevice device) {
if (DBG) Log.d(TAG, "disableOptionalCodecs(" + device + ")");
@@ -766,7 +799,7 @@ public final class BluetoothA2dp implements BluetoothProfile {
* OPTIONAL_CODECS_SUPPORTED.
* @hide
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
@OptionalCodecsSupportStatus
public int isOptionalCodecsSupported(@NonNull BluetoothDevice device) {
@@ -792,7 +825,7 @@ public final class BluetoothA2dp implements BluetoothProfile {
* OPTIONAL_CODECS_PREF_DISABLED.
* @hide
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
@OptionalCodecsPreferenceStatus
public int isOptionalCodecsEnabled(@NonNull BluetoothDevice device) {
@@ -819,7 +852,7 @@ public final class BluetoothA2dp implements BluetoothProfile {
* OPTIONAL_CODECS_PREF_DISABLED.
* @hide
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
public void setOptionalCodecsEnabled(@NonNull BluetoothDevice device,
@OptionalCodecsPreferenceStatus int value) {
@@ -845,6 +878,88 @@ public final class BluetoothA2dp implements BluetoothProfile {
}
/**
+ * Get the supported type of the Dynamic Audio Buffer.
+ * <p>Possible return values are
+ * {@link #DYNAMIC_BUFFER_SUPPORT_NONE},
+ * {@link #DYNAMIC_BUFFER_SUPPORT_A2DP_OFFLOAD},
+ * {@link #DYNAMIC_BUFFER_SUPPORT_A2DP_SOFTWARE_ENCODING}.
+ *
+ * @return supported type of Dynamic Audio Buffer feature
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ public @Type int getDynamicBufferSupport() {
+ if (VDBG) log("getDynamicBufferSupport()");
+ try {
+ final IBluetoothA2dp service = getService();
+ if (service != null && isEnabled()) {
+ return service.getDynamicBufferSupport();
+ }
+ if (service == null) Log.w(TAG, "Proxy not attached to service");
+ return DYNAMIC_BUFFER_SUPPORT_NONE;
+ } catch (RemoteException e) {
+ Log.e(TAG, "failed to get getDynamicBufferSupport, error: ", e);
+ return DYNAMIC_BUFFER_SUPPORT_NONE;
+ }
+ }
+
+ /**
+ * Return the record of {@link BufferConstraints} object that
+ * has the default/maximum/minimum audio buffer. This can be used to inform what the controller
+ * has support for the audio buffer.
+ *
+ * @return a record with {@link BufferConstraints} or null if report is unavailable
+ * or unsupported
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ public @Nullable BufferConstraints getBufferConstraints() {
+ if (VDBG) log("getBufferConstraints()");
+ try {
+ final IBluetoothA2dp service = getService();
+ if (service != null && isEnabled()) {
+ return service.getBufferConstraints();
+ }
+ if (service == null) Log.w(TAG, "Proxy not attached to service");
+ return null;
+ } catch (RemoteException e) {
+ Log.e(TAG, "", e);
+ return null;
+ }
+ }
+
+ /**
+ * Set Dynamic Audio Buffer Size.
+ *
+ * @param codec audio codec
+ * @param value buffer millis
+ * @return true to indicate success, or false on immediate error
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(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);
+ }
+ if (service == null) Log.w(TAG, "Proxy not attached to service");
+ return false;
+ } catch (RemoteException e) {
+ Log.e(TAG, "", e);
+ return false;
+ }
+ }
+
+ /**
* Helper for converting a state to a string.
*
* For debug use only - strings are not internationalized.
diff --git a/framework/java/android/bluetooth/BluetoothA2dpSink.java b/framework/java/android/bluetooth/BluetoothA2dpSink.java
index 53f87e6bc0..67f3d7b5d7 100755
--- a/framework/java/android/bluetooth/BluetoothA2dpSink.java
+++ b/framework/java/android/bluetooth/BluetoothA2dpSink.java
@@ -24,6 +24,7 @@ import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.os.Binder;
+import android.os.Build;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
@@ -158,7 +159,7 @@ public final class BluetoothA2dpSink implements BluetoothProfile {
* @return false on immediate error, true otherwise
* @hide
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
public boolean disconnect(BluetoothDevice device) {
if (DBG) log("disconnect(" + device + ")");
diff --git a/framework/java/android/bluetooth/BluetoothAdapter.java b/framework/java/android/bluetooth/BluetoothAdapter.java
index 29a98faf5c..38863c2c8c 100644
--- a/framework/java/android/bluetooth/BluetoothAdapter.java
+++ b/framework/java/android/bluetooth/BluetoothAdapter.java
@@ -18,6 +18,7 @@
package android.bluetooth;
import android.Manifest;
+import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -40,6 +41,7 @@ import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.os.BatteryStats;
import android.os.Binder;
+import android.os.Build;
import android.os.IBinder;
import android.os.ParcelUuid;
import android.os.RemoteException;
@@ -50,6 +52,8 @@ import android.os.SystemProperties;
import android.util.Log;
import android.util.Pair;
+import com.android.internal.util.Preconditions;
+
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -686,6 +690,8 @@ public final class BluetoothAdapter {
private final Map<LeScanCallback, ScanCallback> mLeScanClients;
private static final Map<BluetoothDevice, List<Pair<OnMetadataChangedListener, Executor>>>
sMetadataListeners = new HashMap<>();
+ private final Map<BluetoothConnectionCallback, Executor>
+ mBluetoothConnectionCallbackExecutorMap = new HashMap<>();
/**
* Bluetooth metadata listener. Overrides the default BluetoothMetadataListener
@@ -1170,7 +1176,7 @@ public final class BluetoothAdapter {
* @return true to indicate adapter shutdown has begun, or false on immediate error
* @hide
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(trackingBug = 171933273)
public boolean disable(boolean persist) {
try {
@@ -1219,7 +1225,7 @@ public final class BluetoothAdapter {
* @return true to indicate that the config file was successfully cleared
* @hide
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
public boolean factoryReset() {
try {
@@ -1812,6 +1818,7 @@ public final class BluetoothAdapter {
try {
mServiceLock.readLock().lock();
if (mService != null) {
+ if (DBG) Log.d(TAG, "removeActiveDevice, profiles: " + profiles);
return mService.removeActiveDevice(profiles);
}
} catch (RemoteException e) {
@@ -1856,6 +1863,9 @@ public final class BluetoothAdapter {
try {
mServiceLock.readLock().lock();
if (mService != null) {
+ if (DBG) {
+ Log.d(TAG, "setActiveDevice, device: " + device + ", profiles: " + profiles);
+ }
return mService.setActiveDevice(device, profiles);
}
} catch (RemoteException e) {
@@ -2466,7 +2476,7 @@ public final class BluetoothAdapter {
* {@link #SOCKET_CHANNEL_AUTO_STATIC_NO_SDP} as channel number.
*
* @param channel RFCOMM channel to listen on
- * @param mitm enforce man-in-the-middle protection for authentication.
+ * @param mitm enforce person-in-the-middle protection for authentication.
* @param min16DigitPin enforce a pin key length og minimum 16 digit for sec mode 2
* connections.
* @return a listening RFCOMM BluetoothServerSocket
@@ -2524,8 +2534,8 @@ public final class BluetoothAdapter {
/**
* Create a listening, insecure RFCOMM Bluetooth socket with Service Record.
* <p>The link key is not required to be authenticated, i.e the communication may be
- * vulnerable to Man In the Middle attacks. For Bluetooth 2.1 devices,
- * the link will be encrypted, as encryption is mandartory.
+ * vulnerable to Person In the Middle attacks. For Bluetooth 2.1 devices,
+ * the link will be encrypted, as encryption is mandatory.
* For legacy devices (pre Bluetooth 2.1 devices) the link will not
* be encrypted. Use {@link #listenUsingRfcommWithServiceRecord}, if an
* encrypted and authenticated communication channel is desired.
@@ -2557,14 +2567,14 @@ public final class BluetoothAdapter {
* Create a listening, encrypted,
* RFCOMM Bluetooth socket with Service Record.
* <p>The link will be encrypted, but the link key is not required to be authenticated
- * i.e the communication is vulnerable to Man In the Middle attacks. Use
+ * i.e the communication is vulnerable to Person In the Middle attacks. Use
* {@link #listenUsingRfcommWithServiceRecord}, to ensure an authenticated link key.
* <p> Use this socket if authentication of link key is not possible.
* For example, for Bluetooth 2.1 devices, if any of the devices does not have
* an input and output capability or just has the ability to display a numeric key,
* a secure socket connection is not possible and this socket can be used.
* Use {@link #listenUsingInsecureRfcommWithServiceRecord}, if encryption is not required.
- * For Bluetooth 2.1 devices, the link will be encrypted, as encryption is mandartory.
+ * For Bluetooth 2.1 devices, the link will be encrypted, as encryption is mandatory.
* For more details, refer to the Security Model section 5.2 (vol 3) of
* Bluetooth Core Specification version 2.1 + EDR.
* <p>Use {@link BluetoothServerSocket#accept} to retrieve incoming
@@ -2587,7 +2597,7 @@ public final class BluetoothAdapter {
* permissions, or channel in use.
* @hide
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public BluetoothServerSocket listenUsingEncryptedRfcommWithServiceRecord(String name, UUID uuid)
throws IOException {
return createNewRfcommSocketAndRecord(name, uuid, false, true);
@@ -2636,59 +2646,13 @@ public final class BluetoothAdapter {
}
/**
- * Construct an encrypted, RFCOMM server socket.
- * Call #accept to retrieve connections to this socket.
- *
- * @return An RFCOMM BluetoothServerSocket
- * @throws IOException On error, for example Bluetooth not available, or insufficient
- * permissions.
- * @hide
- */
- public BluetoothServerSocket listenUsingEncryptedRfcommOn(int port) throws IOException {
- BluetoothServerSocket socket =
- new BluetoothServerSocket(BluetoothSocket.TYPE_RFCOMM, false, true, port);
- int errno = socket.mSocket.bindListen();
- if (port == SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) {
- socket.setChannel(socket.mSocket.getPort());
- }
- if (errno < 0) {
- //TODO(BT): Throw the same exception error code
- // that the previous code was using.
- //socket.mSocket.throwErrnoNative(errno);
- throw new IOException("Error: " + errno);
- }
- return socket;
- }
-
- /**
- * Construct a SCO server socket.
- * Call #accept to retrieve connections to this socket.
- *
- * @return A SCO BluetoothServerSocket
- * @throws IOException On error, for example Bluetooth not available, or insufficient
- * permissions.
- * @hide
- */
- public static BluetoothServerSocket listenUsingScoOn() throws IOException {
- BluetoothServerSocket socket =
- new BluetoothServerSocket(BluetoothSocket.TYPE_SCO, false, false, -1);
- int errno = socket.mSocket.bindListen();
- if (errno < 0) {
- //TODO(BT): Throw the same exception error code
- // that the previous code was using.
- //socket.mSocket.throwErrnoNative(errno);
- }
- return socket;
- }
-
- /**
* Construct an encrypted, authenticated, L2CAP server socket.
* Call #accept to retrieve connections to this socket.
* <p>To auto assign a port without creating a SDP record use
* {@link #SOCKET_CHANNEL_AUTO_STATIC_NO_SDP} as port number.
*
* @param port the PSM to listen on
- * @param mitm enforce man-in-the-middle protection for authentication.
+ * @param mitm enforce person-in-the-middle protection for authentication.
* @param min16DigitPin enforce a pin key length og minimum 16 digit for sec mode 2
* connections.
* @return An L2CAP BluetoothServerSocket
@@ -2971,6 +2935,16 @@ public final class BluetoothAdapter {
}
});
}
+ synchronized (mBluetoothConnectionCallbackExecutorMap) {
+ if (!mBluetoothConnectionCallbackExecutorMap.isEmpty()) {
+ try {
+ mService.registerBluetoothConnectionCallback(mConnectionCallback);
+ } catch (RemoteException e) {
+ Log.e(TAG, "onBluetoothServiceUp: Failed to register bluetooth"
+ + "connection callback", e);
+ }
+ }
+ }
}
public void onBluetoothServiceDown() {
@@ -3146,6 +3120,25 @@ public final class BluetoothAdapter {
return true;
}
+ /**
+ * Determines whether a String Bluetooth address, such as "00:43:A8:23:10:F0"
+ * is a RANDOM STATIC address.
+ *
+ * RANDOM STATIC: (addr & 0b11) == 0b11
+ * RANDOM RESOLVABLE: (addr & 0b11) == 0b10
+ * RANDOM non-RESOLVABLE: (addr & 0b11) == 0b00
+ *
+ * @param address Bluetooth address as string
+ * @return true if the 2 Least Significant Bits of the address equals 0b11.
+ *
+ * @hide
+ */
+ public static boolean isAddressRandomStatic(@NonNull String address) {
+ Preconditions.checkNotNull(address);
+ return checkBluetoothAddress(address)
+ && (Integer.parseInt(address.split(":")[5], 16) & 0b11) == 0b11;
+ }
+
@UnsupportedAppUsage
/*package*/ IBluetoothManager getBluetoothManager() {
return mManagerService;
@@ -3389,7 +3382,7 @@ public final class BluetoothAdapter {
* assign a dynamic PSM value. This socket can be used to listen for incoming connections. The
* supported Bluetooth transport is LE only.
* <p>The link key is not required to be authenticated, i.e the communication may be vulnerable
- * to man-in-the-middle attacks. Use {@link #listenUsingL2capChannel}, if an encrypted and
+ * to person-in-the-middle attacks. Use {@link #listenUsingL2capChannel}, if an encrypted and
* authenticated communication channel is desired.
* <p>Use {@link BluetoothServerSocket#accept} to retrieve incoming connections from a listening
* {@link BluetoothServerSocket}.
@@ -3580,6 +3573,280 @@ public final class BluetoothAdapter {
@Nullable byte[] value);
}
+ private final IBluetoothConnectionCallback mConnectionCallback =
+ new IBluetoothConnectionCallback.Stub() {
+ @Override
+ public void onDeviceConnected(BluetoothDevice device) {
+ for (Map.Entry<BluetoothConnectionCallback, Executor> callbackExecutorEntry:
+ mBluetoothConnectionCallbackExecutorMap.entrySet()) {
+ BluetoothConnectionCallback callback = callbackExecutorEntry.getKey();
+ Executor executor = callbackExecutorEntry.getValue();
+ executor.execute(() -> callback.onDeviceConnected(device));
+ }
+ }
+
+ @Override
+ public void onDeviceDisconnected(BluetoothDevice device, int hciReason) {
+ for (Map.Entry<BluetoothConnectionCallback, Executor> callbackExecutorEntry:
+ mBluetoothConnectionCallbackExecutorMap.entrySet()) {
+ BluetoothConnectionCallback callback = callbackExecutorEntry.getKey();
+ Executor executor = callbackExecutorEntry.getValue();
+ executor.execute(() -> callback.onDeviceDisconnected(device, hciReason));
+ }
+ }
+ };
+
+ /**
+ * Registers the BluetoothConnectionCallback to receive callback events when a bluetooth device
+ * (classic or low energy) is connected or disconnected.
+ *
+ * @param executor is the callback executor
+ * @param callback is the connection callback you wish to register
+ * @return true if the callback was registered successfully, false otherwise
+ * @throws IllegalArgumentException if the callback is already registered
+ * @hide
+ */
+ public boolean registerBluetoothConnectionCallback(@NonNull @CallbackExecutor Executor executor,
+ @NonNull BluetoothConnectionCallback callback) {
+ if (DBG) Log.d(TAG, "registerBluetoothConnectionCallback()");
+ if (callback == null) {
+ return false;
+ }
+
+ synchronized (mBluetoothConnectionCallbackExecutorMap) {
+ // If the callback map is empty, we register the service-to-app callback
+ if (mBluetoothConnectionCallbackExecutorMap.isEmpty()) {
+ try {
+ mServiceLock.readLock().lock();
+ if (mService != null) {
+ if (!mService.registerBluetoothConnectionCallback(mConnectionCallback)) {
+ return false;
+ }
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "", e);
+ mBluetoothConnectionCallbackExecutorMap.remove(callback);
+ } finally {
+ mServiceLock.readLock().unlock();
+ }
+ }
+
+ // Adds the passed in callback to our map of callbacks to executors
+ if (mBluetoothConnectionCallbackExecutorMap.containsKey(callback)) {
+ throw new IllegalArgumentException("This callback has already been registered");
+ }
+ mBluetoothConnectionCallbackExecutorMap.put(callback, executor);
+ }
+
+ return true;
+ }
+
+ /**
+ * Unregisters the BluetoothConnectionCallback that was previously registered by the application
+ *
+ * @param callback is the connection callback you wish to unregister
+ * @return true if the callback was unregistered successfully, false otherwise
+ * @hide
+ */
+ public boolean unregisterBluetoothConnectionCallback(
+ @NonNull BluetoothConnectionCallback callback) {
+ if (DBG) Log.d(TAG, "unregisterBluetoothConnectionCallback()");
+ if (callback == null) {
+ return false;
+ }
+
+ synchronized (mBluetoothConnectionCallbackExecutorMap) {
+ if (mBluetoothConnectionCallbackExecutorMap.remove(callback) != null) {
+ return false;
+ }
+ }
+
+ if (!mBluetoothConnectionCallbackExecutorMap.isEmpty()) {
+ return true;
+ }
+
+ // If the callback map is empty, we unregister the service-to-app callback
+ try {
+ mServiceLock.readLock().lock();
+ if (mService != null) {
+ return mService.unregisterBluetoothConnectionCallback(mConnectionCallback);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "", e);
+ } finally {
+ mServiceLock.readLock().unlock();
+ }
+
+ return false;
+ }
+
+ /**
+ * This abstract class is used to implement callbacks for when a bluetooth classic or Bluetooth
+ * Low Energy (BLE) device is either connected or disconnected.
+ *
+ * @hide
+ */
+ public abstract static class BluetoothConnectionCallback {
+ /**
+ * Callback triggered when a bluetooth device (classic or BLE) is connected
+ * @param device is the connected bluetooth device
+ */
+ public void onDeviceConnected(BluetoothDevice device) {}
+
+ /**
+ * Callback triggered when a bluetooth device (classic or BLE) is disconnected
+ * @param device is the disconnected bluetooth device
+ * @param reason is the disconnect reason
+ */
+ public void onDeviceDisconnected(BluetoothDevice device, @DisconnectReason int reason) {}
+
+ /**
+ * @hide
+ */
+ @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})
+ 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:
+ return "Reason unknown";
+ case REASON_LOCAL_REQUEST:
+ return "Local request";
+ case REASON_REMOTE_REQUEST:
+ return "Remote request";
+ case REASON_LOCAL_ERROR:
+ return "Local error";
+ case REASON_REMOTE_ERROR:
+ return "Remote error";
+ case REASON_TIMEOUT:
+ return "Timeout";
+ case REASON_SECURITY:
+ return "Security";
+ case REASON_SYSTEM_POLICY:
+ return "System policy";
+ case REASON_RESOURCE_LIMIT_REACHED:
+ return "Resource constrained";
+ case REASON_CONNECTION_EXISTS:
+ return "Connection already exists";
+ case REASON_BAD_PARAMETERS:
+ return "Bad parameters";
+ default:
+ return "Unrecognized disconnect reason: " + reason;
+ }
+ }
+ }
+
/**
* Converts old constant of priority to the new for connection policy
*
diff --git a/framework/java/android/bluetooth/BluetoothClass.java b/framework/java/android/bluetooth/BluetoothClass.java
index 905b0ceec4..1e92fc0568 100755
--- a/framework/java/android/bluetooth/BluetoothClass.java
+++ b/framework/java/android/bluetooth/BluetoothClass.java
@@ -425,13 +425,13 @@ public final class BluetoothClass implements Parcelable {
return false;
}
} else if (profile == PROFILE_HID) {
- return (getDeviceClass() & Device.Major.PERIPHERAL) == Device.Major.PERIPHERAL;
+ return getMajorDeviceClass() == Device.Major.PERIPHERAL;
} else if (profile == PROFILE_PANU || profile == PROFILE_NAP) {
// No good way to distinguish between the two, based on class bits.
if (hasService(Service.NETWORKING)) {
return true;
}
- return (getDeviceClass() & Device.Major.NETWORKING) == Device.Major.NETWORKING;
+ return getMajorDeviceClass() == Device.Major.NETWORKING;
} else {
return false;
}
diff --git a/framework/java/android/bluetooth/BluetoothCodecConfig.java b/framework/java/android/bluetooth/BluetoothCodecConfig.java
index 735980beeb..a52fc89179 100644
--- a/framework/java/android/bluetooth/BluetoothCodecConfig.java
+++ b/framework/java/android/bluetooth/BluetoothCodecConfig.java
@@ -644,8 +644,9 @@ public final class BluetoothCodecConfig implements Parcelable {
if (other == null && mCodecType != other.mCodecType) {
return false;
}
- // Currently we only care about the LDAC Playback Quality at CodecSpecific1
+ // Currently we only care about the AAC VBR and LDAC Playback Quality at CodecSpecific1
switch (mCodecType) {
+ case SOURCE_CODEC_TYPE_AAC:
case SOURCE_CODEC_TYPE_LDAC:
if (mCodecSpecific1 != other.mCodecSpecific1) {
return false;
diff --git a/framework/java/android/bluetooth/BluetoothCodecStatus.java b/framework/java/android/bluetooth/BluetoothCodecStatus.java
index 7b567b4098..f43a9e8cab 100644
--- a/framework/java/android/bluetooth/BluetoothCodecStatus.java
+++ b/framework/java/android/bluetooth/BluetoothCodecStatus.java
@@ -18,6 +18,7 @@ package android.bluetooth;
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
@@ -39,7 +40,7 @@ public final class BluetoothCodecStatus implements Parcelable {
* This extra represents the current codec status of the A2DP
* profile.
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public static final String EXTRA_CODEC_STATUS =
"android.bluetooth.extra.CODEC_STATUS";
@@ -198,7 +199,7 @@ public final class BluetoothCodecStatus implements Parcelable {
*
* @return the current codec configuration
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public @Nullable BluetoothCodecConfig getCodecConfig() {
return mCodecConfig;
}
@@ -208,7 +209,7 @@ public final class BluetoothCodecStatus implements Parcelable {
*
* @return an array with the codecs local capabilities
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public @Nullable BluetoothCodecConfig[] getCodecsLocalCapabilities() {
return mCodecsLocalCapabilities;
}
@@ -218,7 +219,7 @@ public final class BluetoothCodecStatus implements Parcelable {
*
* @return an array with the codecs selectable capabilities
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public @Nullable BluetoothCodecConfig[] getCodecsSelectableCapabilities() {
return mCodecsSelectableCapabilities;
}
diff --git a/framework/java/android/bluetooth/BluetoothDevice.java b/framework/java/android/bluetooth/BluetoothDevice.java
index 594e5ffa77..c30b8af3da 100644
--- a/framework/java/android/bluetooth/BluetoothDevice.java
+++ b/framework/java/android/bluetooth/BluetoothDevice.java
@@ -28,6 +28,7 @@ import android.annotation.SystemApi;
import android.app.PropertyInvalidatedCache;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
+import android.os.Build;
import android.os.Handler;
import android.os.Parcel;
import android.os.ParcelUuid;
@@ -349,10 +350,39 @@ public final class BluetoothDevice implements Parcelable {
/** @hide */
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public static final String ACTION_SDP_RECORD =
"android.bluetooth.device.action.SDP_RECORD";
+ /** @hide */
+ @IntDef(prefix = "METADATA_", value = {
+ METADATA_MANUFACTURER_NAME,
+ METADATA_MODEL_NAME,
+ METADATA_SOFTWARE_VERSION,
+ METADATA_HARDWARE_VERSION,
+ METADATA_COMPANION_APP,
+ METADATA_MAIN_ICON,
+ METADATA_IS_UNTETHERED_HEADSET,
+ METADATA_UNTETHERED_LEFT_ICON,
+ METADATA_UNTETHERED_RIGHT_ICON,
+ METADATA_UNTETHERED_CASE_ICON,
+ METADATA_UNTETHERED_LEFT_BATTERY,
+ METADATA_UNTETHERED_RIGHT_BATTERY,
+ METADATA_UNTETHERED_CASE_BATTERY,
+ METADATA_UNTETHERED_LEFT_CHARGING,
+ METADATA_UNTETHERED_RIGHT_CHARGING,
+ METADATA_UNTETHERED_CASE_CHARGING,
+ METADATA_ENHANCED_SETTINGS_UI_URI,
+ METADATA_DEVICE_TYPE,
+ METADATA_MAIN_BATTERY,
+ METADATA_MAIN_CHARGING,
+ METADATA_MAIN_LOW_BATTERY_THRESHOLD,
+ METADATA_UNTETHERED_LEFT_LOW_BATTERY_THRESHOLD,
+ METADATA_UNTETHERED_RIGHT_LOW_BATTERY_THRESHOLD,
+ METADATA_UNTETHERED_CASE_LOW_BATTERY_THRESHOLD})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface MetadataKey{}
+
/**
* Maximum length of a metadata entry, this is to avoid exploding Bluetooth
* disk usage
@@ -502,6 +532,89 @@ public final class BluetoothDevice implements Parcelable {
public static final int METADATA_ENHANCED_SETTINGS_UI_URI = 16;
/**
+ * Type of the Bluetooth device, must be within the list of
+ * BluetoothDevice.DEVICE_TYPE_*
+ * Data type should be {@String} as {@link Byte} array.
+ * @hide
+ */
+ @SystemApi
+ public static final int METADATA_DEVICE_TYPE = 17;
+
+ /**
+ * Battery level of the Bluetooth device, use when the Bluetooth device
+ * does not support HFP battery indicator.
+ * Data type should be {@String} as {@link Byte} array.
+ * @hide
+ */
+ @SystemApi
+ public static final int METADATA_MAIN_BATTERY = 18;
+
+ /**
+ * Whether the device is charging.
+ * Data type should be {@String} as {@link Byte} array.
+ * @hide
+ */
+ @SystemApi
+ public static final int METADATA_MAIN_CHARGING = 19;
+
+ /**
+ * The battery threshold of the Bluetooth device to show low battery icon.
+ * Data type should be {@String} as {@link Byte} array.
+ * @hide
+ */
+ @SystemApi
+ public static final int METADATA_MAIN_LOW_BATTERY_THRESHOLD = 20;
+
+ /**
+ * The battery threshold of the left headset to show low battery icon.
+ * Data type should be {@String} as {@link Byte} array.
+ * @hide
+ */
+ @SystemApi
+ public static final int METADATA_UNTETHERED_LEFT_LOW_BATTERY_THRESHOLD = 21;
+
+ /**
+ * The battery threshold of the right headset to show low battery icon.
+ * Data type should be {@String} as {@link Byte} array.
+ * @hide
+ */
+ @SystemApi
+ public static final int METADATA_UNTETHERED_RIGHT_LOW_BATTERY_THRESHOLD = 22;
+
+ /**
+ * The battery threshold of the case to show low battery icon.
+ * Data type should be {@String} as {@link Byte} array.
+ * @hide
+ */
+ @SystemApi
+ public static final int METADATA_UNTETHERED_CASE_LOW_BATTERY_THRESHOLD = 23;
+
+ /**
+ * Device type which is used in METADATA_DEVICE_TYPE
+ * Indicates this Bluetooth device is a standard Bluetooth accessory or
+ * not listed in METADATA_DEVICE_TYPE_*.
+ * @hide
+ */
+ @SystemApi
+ public static final String DEVICE_TYPE_DEFAULT = "Default";
+
+ /**
+ * Device type which is used in METADATA_DEVICE_TYPE
+ * Indicates this Bluetooth device is a watch.
+ * @hide
+ */
+ @SystemApi
+ public static final String DEVICE_TYPE_WATCH = "Watch";
+
+ /**
+ * Device type which is used in METADATA_DEVICE_TYPE
+ * Indicates this Bluetooth device is an untethered headset.
+ * @hide
+ */
+ @SystemApi
+ public static final String DEVICE_TYPE_UNTETHERED_HEADSET = "Untethered Headset";
+
+ /**
* Broadcast Action: This intent is used to broadcast the {@link UUID}
* wrapped as a {@link android.os.ParcelUuid} of the remote device after it
* has been fetched. This intent is sent only when the UUIDs of the remote
@@ -645,7 +758,7 @@ public final class BluetoothDevice implements Parcelable {
*
* @hide
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public static final int UNBOND_REASON_AUTH_FAILED = 1;
/**
@@ -654,7 +767,7 @@ public final class BluetoothDevice implements Parcelable {
*
* @hide
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public static final int UNBOND_REASON_AUTH_REJECTED = 2;
/**
@@ -669,7 +782,7 @@ public final class BluetoothDevice implements Parcelable {
*
* @hide
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public static final int UNBOND_REASON_REMOTE_DEVICE_DOWN = 4;
/**
@@ -677,7 +790,7 @@ public final class BluetoothDevice implements Parcelable {
*
* @hide
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public static final int UNBOND_REASON_DISCOVERY_IN_PROGRESS = 5;
/**
@@ -685,7 +798,7 @@ public final class BluetoothDevice implements Parcelable {
*
* @hide
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public static final int UNBOND_REASON_AUTH_TIMEOUT = 6;
/**
@@ -693,7 +806,7 @@ public final class BluetoothDevice implements Parcelable {
*
* @hide
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public static final int UNBOND_REASON_REPEATED_ATTEMPTS = 7;
/**
@@ -702,7 +815,7 @@ public final class BluetoothDevice implements Parcelable {
*
* @hide
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public static final int UNBOND_REASON_REMOTE_AUTH_CANCELED = 8;
/**
@@ -781,7 +894,7 @@ public final class BluetoothDevice implements Parcelable {
"android.bluetooth.device.extra.SDP_RECORD";
/** @hide */
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public static final String EXTRA_SDP_SEARCH_STATUS =
"android.bluetooth.device.extra.SDP_SEARCH_STATUS";
@@ -889,6 +1002,24 @@ public final class BluetoothDevice implements Parcelable {
public static final String EXTRA_MAS_INSTANCE =
"android.bluetooth.device.extra.MAS_INSTANCE";
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(
+ prefix = { "ADDRESS_TYPE_" },
+ value = {
+ /** Hardware MAC Address */
+ ADDRESS_TYPE_PUBLIC,
+ /** Address is either resolvable, non-resolvable or static.*/
+ ADDRESS_TYPE_RANDOM,
+ }
+ )
+ public @interface AddressType {}
+
+ /** Hardware MAC Address of the device */
+ public static final int ADDRESS_TYPE_PUBLIC = 0;
+ /** Address is either resolvable, non-resolvable or static. */
+ public static final int ADDRESS_TYPE_RANDOM = 1;
+
/**
* Lazy initialization. Guaranteed final after first object constructed, or
* getService() called.
@@ -897,6 +1028,7 @@ public final class BluetoothDevice implements Parcelable {
private static volatile IBluetooth sService;
private final String mAddress;
+ @AddressType private final int mAddressType;
/*package*/
@UnsupportedAppUsage
@@ -951,6 +1083,7 @@ public final class BluetoothDevice implements Parcelable {
}
mAddress = address;
+ mAddressType = ADDRESS_TYPE_PUBLIC;
}
@Override
@@ -1031,7 +1164,11 @@ public final class BluetoothDevice implements Parcelable {
try {
String name = service.getRemoteName(this);
if (name != null) {
- return name.replaceAll("[\\t\\n\\r]+", " ");
+ // remove whitespace characters from the name
+ return name
+ .replace('\t', ' ')
+ .replace('\n', ' ')
+ .replace('\r', ' ');
}
return null;
} catch (RemoteException e) {
@@ -1098,7 +1235,7 @@ public final class BluetoothDevice implements Parcelable {
* @return true on success, false on error
* @hide
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@RequiresPermission(Manifest.permission.BLUETOOTH)
public boolean setAlias(@NonNull String alias) {
final IBluetooth service = sService;
@@ -1162,7 +1299,6 @@ public final class BluetoothDevice implements Parcelable {
* the bonding process completes, and its result.
* <p>Android system services will handle the necessary user interactions
* to confirm and complete the bonding process.
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}.
*
* @param transport The transport to use for the pairing procedure.
* @return false on immediate error, true if bonding will begin
@@ -1170,8 +1306,9 @@ public final class BluetoothDevice implements Parcelable {
* @hide
*/
@UnsupportedAppUsage
+ @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
public boolean createBond(int transport) {
- return createBondOutOfBand(transport, null);
+ return createBondInternal(transport, null, null);
}
/**
@@ -1185,21 +1322,38 @@ public final class BluetoothDevice implements Parcelable {
* <p>Android system services will handle the necessary user interactions
* to confirm and complete the bonding process.
*
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}.
+ * <p>There are two possible versions of OOB Data. This data can come in as
+ * P192 or P256. This is a reference to the cryptography used to generate the key.
+ * The caller may pass one or both. If both types of data are passed, then the
+ * P256 data will be preferred, and thus used.
*
* @param transport - Transport to use
- * @param oobData - Out Of Band data
+ * @param remoteP192Data - Out Of Band data (P192) or null
+ * @param remoteP256Data - Out Of Band data (P256) or null
* @return false on immediate error, true if bonding will begin
* @hide
*/
- public boolean createBondOutOfBand(int transport, OobData oobData) {
+ @SystemApi
+ @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ public boolean createBondOutOfBand(int transport, @Nullable OobData remoteP192Data,
+ @Nullable OobData remoteP256Data) {
+ if (remoteP192Data == null && remoteP256Data == null) {
+ throw new IllegalArgumentException(
+ "One or both arguments for the OOB data types are required to not be null."
+ + " Please use createBond() instead if you do not have OOB data to pass.");
+ }
+ return createBondInternal(transport, remoteP192Data, remoteP256Data);
+ }
+
+ private boolean createBondInternal(int transport, @Nullable OobData remoteP192Data,
+ @Nullable OobData remoteP256Data) {
final IBluetooth service = sService;
if (service == null) {
Log.w(TAG, "BT not enabled, createBondOutOfBand failed");
return false;
}
try {
- return service.createBond(this, transport, oobData);
+ return service.createBond(this, transport, remoteP192Data, remoteP256Data);
} catch (RemoteException e) {
Log.e(TAG, "", e);
}
@@ -1230,27 +1384,6 @@ public final class BluetoothDevice implements Parcelable {
}
/**
- * Set the Out Of Band data for a remote device to be used later
- * in the pairing mechanism. Users can obtain this data through other
- * trusted channels
- *
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}.
- *
- * @param hash Simple Secure pairing hash
- * @param randomizer The random key obtained using OOB
- * @return false on error; true otherwise
- * @hide
- */
- public boolean setDeviceOutOfBandData(byte[] hash, byte[] randomizer) {
- //TODO(BT)
- /*
- try {
- return sService.setDeviceOutOfBandData(this, hash, randomizer);
- } catch (RemoteException e) {Log.e(TAG, "", e);} */
- return false;
- }
-
- /**
* Cancel an in-progress bonding request started with {@link #createBond}.
*
* @return true on success, false on error
@@ -1458,7 +1591,7 @@ public final class BluetoothDevice implements Parcelable {
* present in the cache. Clients should use the {@link #getUuids} to get UUIDs
* if service discovery is not to be performed.
*
- * @return False if the sanity check fails, True if the process of initiating an ACL connection
+ * @return False if the check fails, True if the process of initiating an ACL connection
* to the remote device was started.
*/
@RequiresPermission(Manifest.permission.BLUETOOTH)
@@ -1492,7 +1625,7 @@ public final class BluetoothDevice implements Parcelable {
* The object type will match one of the SdpXxxRecord types, depending on the UUID searched
* for.
*
- * @return False if the sanity check fails, True if the process
+ * @return False if the check fails, True if the process
* of initiating an ACL connection to the remote device
* was started.
*/
@@ -1537,7 +1670,7 @@ public final class BluetoothDevice implements Parcelable {
* @return true pin has been set false for error
* @hide
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
public boolean setPin(@NonNull String pin) {
byte[] pinBytes = convertPinToBytes(pin);
@@ -1809,7 +1942,7 @@ public final class BluetoothDevice implements Parcelable {
* socket will be encrypted.
* <p> Use this socket only if an authenticated socket link is possible.
* Authentication refers to the authentication of the link key to
- * prevent man-in-the-middle type of attacks.
+ * prevent person-in-the-middle type of attacks.
* For example, for Bluetooth 2.1 devices, if any of the devices does not
* have an input and output capability or just has the ability to
* display a numeric key, a secure socket connection is not possible.
@@ -1844,7 +1977,7 @@ public final class BluetoothDevice implements Parcelable {
* socket will be encrypted.
* <p> Use this socket only if an authenticated socket link is possible.
* Authentication refers to the authentication of the link key to
- * prevent man-in-the-middle type of attacks.
+ * prevent person-in-the-middle type of attacks.
* For example, for Bluetooth 2.1 devices, if any of the devices does not
* have an input and output capability or just has the ability to
* display a numeric key, a secure socket connection is not possible.
@@ -1901,7 +2034,7 @@ public final class BluetoothDevice implements Parcelable {
* socket will be encrypted.
* <p> Use this socket only if an authenticated socket link is possible.
* Authentication refers to the authentication of the link key to
- * prevent man-in-the-middle type of attacks.
+ * prevent person-in-the-middle type of attacks.
* For example, for Bluetooth 2.1 devices, if any of the devices does not
* have an input and output capability or just has the ability to
* display a numeric key, a secure socket connection is not possible.
@@ -1933,7 +2066,7 @@ public final class BluetoothDevice implements Parcelable {
* Create an RFCOMM {@link BluetoothSocket} socket ready to start an insecure
* outgoing connection to this remote device using SDP lookup of uuid.
* <p> The communication channel will not have an authenticated link key
- * i.e it will be subject to man-in-the-middle attacks. For Bluetooth 2.1
+ * i.e it will be subject to person-in-the-middle attacks. For Bluetooth 2.1
* devices, the link key will be encrypted, as encryption is mandatory.
* For legacy devices (pre Bluetooth 2.1 devices) the link key will
* be not be encrypted. Use {@link #createRfcommSocketToServiceRecord} if an
@@ -2151,7 +2284,7 @@ public final class BluetoothDevice implements Parcelable {
* operations.
* @hide
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public BluetoothGatt connectGatt(Context context, boolean autoConnect,
BluetoothGattCallback callback, int transport,
boolean opportunistic, int phy, Handler handler) {
@@ -2189,7 +2322,7 @@ public final class BluetoothDevice implements Parcelable {
* <p>The remote device will be authenticated and communication on this socket will be
* encrypted.
* <p> Use this socket if an authenticated socket link is possible. Authentication refers
- * to the authentication of the link key to prevent man-in-the-middle type of attacks.
+ * to the authentication of the link key to prevent person-in-the-middle type of attacks.
*
* @param psm dynamic PSM value from remote device
* @return a CoC #BluetoothSocket ready for an outgoing connection
@@ -2216,7 +2349,7 @@ public final class BluetoothDevice implements Parcelable {
* <p>Use {@link BluetoothSocket#connect} to initiate the outgoing connection.
* <p>Application using this API is responsible for obtaining PSM value from remote device.
* <p> The communication channel may not have an authenticated link key, i.e. it may be subject
- * to man-in-the-middle attacks. Use {@link #createL2capChannel(int)} if an encrypted and
+ * to person-in-the-middle attacks. Use {@link #createL2capChannel(int)} if an encrypted and
* authenticated communication channel is possible.
*
* @param psm dynamic PSM value from remote device
@@ -2253,7 +2386,7 @@ public final class BluetoothDevice implements Parcelable {
*/
@SystemApi
@RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
- public boolean setMetadata(int key, @NonNull byte[] value) {
+ public boolean setMetadata(@MetadataKey int key, @NonNull byte[] value) {
final IBluetooth service = sService;
if (service == null) {
Log.e(TAG, "Bluetooth is not enabled. Cannot set metadata");
@@ -2281,7 +2414,7 @@ public final class BluetoothDevice implements Parcelable {
@SystemApi
@Nullable
@RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
- public byte[] getMetadata(int key) {
+ public byte[] getMetadata(@MetadataKey int key) {
final IBluetooth service = sService;
if (service == null) {
Log.e(TAG, "Bluetooth is not enabled. Cannot get metadata");
@@ -2294,4 +2427,14 @@ public final class BluetoothDevice implements Parcelable {
return null;
}
}
+
+ /**
+ * Get the maxinum metadata key ID.
+ *
+ * @return the last supported metadata key
+ * @hide
+ */
+ public static @MetadataKey int getMaxMetadataKey() {
+ return METADATA_UNTETHERED_CASE_LOW_BATTERY_THRESHOLD;
+ }
}
diff --git a/framework/java/android/bluetooth/BluetoothGatt.java b/framework/java/android/bluetooth/BluetoothGatt.java
index f877f04626..381318b26d 100644
--- a/framework/java/android/bluetooth/BluetoothGatt.java
+++ b/framework/java/android/bluetooth/BluetoothGatt.java
@@ -58,9 +58,9 @@ public final class BluetoothGatt implements BluetoothProfile {
private int mConnState;
private final Object mStateLock = new Object();
private final Object mDeviceBusyLock = new Object();
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private Boolean mDeviceBusy = false;
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private int mTransport;
private int mPhy;
private boolean mOpportunistic;
@@ -134,14 +134,14 @@ public final class BluetoothGatt implements BluetoothProfile {
/*package*/ static final int AUTHENTICATION_NONE = 0;
/**
- * Authentication requested; no man-in-the-middle protection required.
+ * Authentication requested; no person-in-the-middle protection required.
*
* @hide
*/
/*package*/ static final int AUTHENTICATION_NO_MITM = 1;
/**
- * Authentication with man-in-the-middle protection requested.
+ * Authentication with person-in-the-middle protection requested.
*
* @hide
*/
@@ -688,6 +688,31 @@ public final class BluetoothGatt implements BluetoothProfile {
}
});
}
+
+ /**
+ * Callback invoked when service changed event is received
+ * @hide
+ */
+ @Override
+ public void onServiceChanged(String address) {
+ if (DBG) {
+ Log.d(TAG, "onServiceChanged() - Device=" + address);
+ }
+
+ if (!address.equals(mDevice.getAddress())) {
+ return;
+ }
+
+ runOrQueueCallback(new Runnable() {
+ @Override
+ public void run() {
+ final BluetoothGattCallback callback = mCallback;
+ if (callback != null) {
+ callback.onServiceChanged(BluetoothGatt.this);
+ }
+ }
+ });
+ }
};
/*package*/ BluetoothGatt(IBluetoothGatt iGatt, BluetoothDevice device,
@@ -799,6 +824,25 @@ public final class BluetoothGatt implements BluetoothProfile {
* error
*/
private boolean registerApp(BluetoothGattCallback callback, Handler handler) {
+ return registerApp(callback, handler, false);
+ }
+
+ /**
+ * Register an application callback to start using GATT.
+ *
+ * <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
+ */
+ private boolean registerApp(BluetoothGattCallback callback, Handler handler,
+ boolean eatt_support) {
if (DBG) Log.d(TAG, "registerApp()");
if (mService == null) return false;
@@ -808,7 +852,7 @@ public final class BluetoothGatt implements BluetoothProfile {
if (DBG) Log.d(TAG, "registerApp() - UUID=" + uuid);
try {
- mService.registerClient(new ParcelUuid(uuid), mBluetoothGattCallback);
+ mService.registerClient(new ParcelUuid(uuid), mBluetoothGattCallback, eatt_support);
} catch (RemoteException e) {
Log.e(TAG, "", e);
return false;
@@ -856,7 +900,7 @@ public final class BluetoothGatt implements BluetoothProfile {
* automatically connect as soon as the remote device becomes available (true).
* @return true, if the connection attempt was initiated successfully
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
/*package*/ boolean connect(Boolean autoConnect, BluetoothGattCallback callback,
Handler handler) {
if (DBG) {
diff --git a/framework/java/android/bluetooth/BluetoothGattCallback.java b/framework/java/android/bluetooth/BluetoothGattCallback.java
index cf82a33045..1c40cff076 100644
--- a/framework/java/android/bluetooth/BluetoothGattCallback.java
+++ b/framework/java/android/bluetooth/BluetoothGattCallback.java
@@ -16,6 +16,8 @@
package android.bluetooth;
+import android.annotation.NonNull;
+
/**
* This abstract class is used to implement {@link BluetoothGatt} callbacks.
*/
@@ -183,7 +185,7 @@ public abstract class BluetoothGattCallback {
* @param gatt GATT client involved
* @param interval Connection interval used on this connection, 1.25ms unit. Valid range is from
* 6 (7.5ms) to 3200 (4000ms).
- * @param latency Slave latency for the connection in number of connection events. Valid range
+ * @param latency Worker latency for the connection in number of connection events. Valid range
* is from 0 to 499
* @param timeout Supervision timeout for this connection, in 10ms unit. Valid range is from 10
* (0.1s) to 3200 (32s)
@@ -194,4 +196,16 @@ public abstract class BluetoothGattCallback {
public void onConnectionUpdated(BluetoothGatt gatt, int interval, int latency, int timeout,
int status) {
}
+
+ /**
+ * Callback indicating service changed event is received
+ *
+ * <p>Receiving this event means that the GATT database is out of sync with
+ * the remote device. {@link BluetoothGatt#discoverServices} should be
+ * called to re-discover the services.
+ *
+ * @param gatt GATT client involved
+ */
+ public void onServiceChanged(@NonNull BluetoothGatt gatt) {
+ }
}
diff --git a/framework/java/android/bluetooth/BluetoothGattCharacteristic.java b/framework/java/android/bluetooth/BluetoothGattCharacteristic.java
index 7066f470aa..8f1b59cf69 100644
--- a/framework/java/android/bluetooth/BluetoothGattCharacteristic.java
+++ b/framework/java/android/bluetooth/BluetoothGattCharacteristic.java
@@ -84,7 +84,7 @@ public class BluetoothGattCharacteristic implements Parcelable {
public static final int PERMISSION_READ_ENCRYPTED = 0x02;
/**
- * Characteristic permission: Allow reading with man-in-the-middle protection
+ * Characteristic permission: Allow reading with person-in-the-middle protection
*/
public static final int PERMISSION_READ_ENCRYPTED_MITM = 0x04;
@@ -99,7 +99,7 @@ public class BluetoothGattCharacteristic implements Parcelable {
public static final int PERMISSION_WRITE_ENCRYPTED = 0x20;
/**
- * Characteristic permission: Allow encrypted writes with man-in-the-middle
+ * Characteristic permission: Allow encrypted writes with person-in-the-middle
* protection
*/
public static final int PERMISSION_WRITE_ENCRYPTED_MITM = 0x40;
@@ -111,7 +111,7 @@ public class BluetoothGattCharacteristic implements Parcelable {
/**
* Characteristic permission: Allow signed write operations with
- * man-in-the-middle protection
+ * person-in-the-middle protection
*/
public static final int PERMISSION_WRITE_SIGNED_MITM = 0x100;
diff --git a/framework/java/android/bluetooth/BluetoothGattDescriptor.java b/framework/java/android/bluetooth/BluetoothGattDescriptor.java
index 7cc2d6bc53..49ba281e2e 100644
--- a/framework/java/android/bluetooth/BluetoothGattDescriptor.java
+++ b/framework/java/android/bluetooth/BluetoothGattDescriptor.java
@@ -58,7 +58,7 @@ public class BluetoothGattDescriptor implements Parcelable {
public static final int PERMISSION_READ_ENCRYPTED = 0x02;
/**
- * Descriptor permission: Allow reading with man-in-the-middle protection
+ * Descriptor permission: Allow reading with person-in-the-middle protection
*/
public static final int PERMISSION_READ_ENCRYPTED_MITM = 0x04;
@@ -73,7 +73,7 @@ public class BluetoothGattDescriptor implements Parcelable {
public static final int PERMISSION_WRITE_ENCRYPTED = 0x20;
/**
- * Descriptor permission: Allow encrypted writes with man-in-the-middle
+ * Descriptor permission: Allow encrypted writes with person-in-the-middle
* protection
*/
public static final int PERMISSION_WRITE_ENCRYPTED_MITM = 0x40;
@@ -85,7 +85,7 @@ public class BluetoothGattDescriptor implements Parcelable {
/**
* Descriptor permission: Allow signed write operations with
- * man-in-the-middle protection
+ * person-in-the-middle protection
*/
public static final int PERMISSION_WRITE_SIGNED_MITM = 0x100;
diff --git a/framework/java/android/bluetooth/BluetoothGattServer.java b/framework/java/android/bluetooth/BluetoothGattServer.java
index 13b1b4f93c..088b0169b6 100644
--- a/framework/java/android/bluetooth/BluetoothGattServer.java
+++ b/framework/java/android/bluetooth/BluetoothGattServer.java
@@ -443,6 +443,25 @@ public final class BluetoothGattServer implements BluetoothProfile {
* error
*/
/*package*/ boolean registerCallback(BluetoothGattServerCallback callback) {
+ return registerCallback(callback, false);
+ }
+
+ /**
+ * Register an application callback to start using GattServer.
+ *
+ * <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
+ */
+ /*package*/ boolean registerCallback(BluetoothGattServerCallback callback,
+ boolean eatt_support) {
if (DBG) Log.d(TAG, "registerCallback()");
if (mService == null) {
Log.e(TAG, "GATT service not available");
@@ -459,7 +478,7 @@ public final class BluetoothGattServer implements BluetoothProfile {
mCallback = callback;
try {
- mService.registerServer(new ParcelUuid(uuid), mBluetoothGattServerCallback);
+ mService.registerServer(new ParcelUuid(uuid), mBluetoothGattServerCallback, eatt_support);
} catch (RemoteException e) {
Log.e(TAG, "", e);
mCallback = null;
diff --git a/framework/java/android/bluetooth/BluetoothGattServerCallback.java b/framework/java/android/bluetooth/BluetoothGattServerCallback.java
index 2c8114be3f..0ead5f57e8 100644
--- a/framework/java/android/bluetooth/BluetoothGattServerCallback.java
+++ b/framework/java/android/bluetooth/BluetoothGattServerCallback.java
@@ -187,7 +187,7 @@ public abstract class BluetoothGattServerCallback {
* @param device The remote device involved
* @param interval Connection interval used on this connection, 1.25ms unit. Valid range is from
* 6 (7.5ms) to 3200 (4000ms).
- * @param latency Slave latency for the connection in number of connection events. Valid range
+ * @param latency Worker latency for the connection in number of connection events. Valid range
* is from 0 to 499
* @param timeout Supervision timeout for this connection, in 10ms unit. Valid range is from 10
* (0.1s) to 3200 (32s)
diff --git a/framework/java/android/bluetooth/BluetoothGattService.java b/framework/java/android/bluetooth/BluetoothGattService.java
index 13d6d7021e..23dc7c8308 100644
--- a/framework/java/android/bluetooth/BluetoothGattService.java
+++ b/framework/java/android/bluetooth/BluetoothGattService.java
@@ -16,6 +16,7 @@
package android.bluetooth;
import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
import android.os.Parcel;
import android.os.ParcelUuid;
import android.os.Parcelable;
@@ -44,7 +45,7 @@ public class BluetoothGattService implements Parcelable {
/**
- * The remote device his service is associated with.
+ * The remote device this service is associated with.
* This applies to client applications only.
*
* @hide
@@ -385,7 +386,7 @@ public class BluetoothGattService implements Parcelable {
*
* @hide
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public void setAdvertisePreferred(boolean advertisePreferred) {
mAdvertisePreferred = advertisePreferred;
}
diff --git a/framework/java/android/bluetooth/BluetoothHeadset.java b/framework/java/android/bluetooth/BluetoothHeadset.java
index e6d6e7ac5d..632572dea3 100644
--- a/framework/java/android/bluetooth/BluetoothHeadset.java
+++ b/framework/java/android/bluetooth/BluetoothHeadset.java
@@ -27,6 +27,7 @@ import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
import android.os.Binder;
+import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -79,7 +80,7 @@ public final class BluetoothHeadset implements BluetoothProfile {
/**
* Intent used to broadcast the change in the Audio Connection state of the
- * HDP profile.
+ * HFP profile.
*
* <p>This intent will have 3 extras:
* <ul>
@@ -112,7 +113,7 @@ public final class BluetoothHeadset implements BluetoothProfile {
* @hide
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(trackingBug = 171933273)
public static final String ACTION_ACTIVE_DEVICE_CHANGED =
"android.bluetooth.headset.profile.action.ACTIVE_DEVICE_CHANGED";
@@ -566,6 +567,7 @@ public final class BluetoothHeadset implements BluetoothProfile {
* @return true if priority is set, false on error
* @hide
* @deprecated Replaced with {@link #setConnectionPolicy(BluetoothDevice, int)}
+ * @removed
*/
@Deprecated
@SystemApi
@@ -635,7 +637,7 @@ public final class BluetoothHeadset implements BluetoothProfile {
* @return priority of the device
* @hide
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@RequiresPermission(Manifest.permission.BLUETOOTH)
public int getPriority(BluetoothDevice device) {
if (VDBG) log("getPriority(" + device + ")");
@@ -681,6 +683,48 @@ public final class BluetoothHeadset implements BluetoothProfile {
}
/**
+ * Checks whether the headset supports some form of noise reduction
+ *
+ * @param device Bluetooth device
+ * @return true if echo cancellation and/or noise reduction is supported, false otherwise
+ */
+ @RequiresPermission(Manifest.permission.BLUETOOTH)
+ 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);
+ } catch (RemoteException e) {
+ Log.e(TAG, Log.getStackTraceString(new Throwable()));
+ }
+ }
+ if (service == null) Log.w(TAG, "Proxy not attached to service");
+ return false;
+ }
+
+ /**
+ * Checks whether the headset supports voice recognition
+ *
+ * @param device Bluetooth device
+ * @return true if voice recognition is supported, false otherwise
+ */
+ @RequiresPermission(Manifest.permission.BLUETOOTH)
+ 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);
+ } catch (RemoteException e) {
+ Log.e(TAG, Log.getStackTraceString(new Throwable()));
+ }
+ }
+ if (service == null) Log.w(TAG, "Proxy not attached to service");
+ return false;
+ }
+
+ /**
* Start Bluetooth voice recognition. This methods sends the voice
* recognition AT command to the headset and establishes the
* audio connection.
@@ -782,7 +826,7 @@ public final class BluetoothHeadset implements BluetoothProfile {
*
* @hide
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public int getAudioState(BluetoothDevice device) {
if (VDBG) log("getAudioState");
final IBluetoothHeadset service = mService;
@@ -1030,7 +1074,7 @@ public final class BluetoothHeadset implements BluetoothProfile {
*
* @hide
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public void phoneStateChanged(int numActive, int numHeld, int callState, String number,
int type, String name) {
final IBluetoothHeadset service = mService;
@@ -1129,7 +1173,7 @@ public final class BluetoothHeadset implements BluetoothProfile {
* @hide
*/
@RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN)
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(trackingBug = 171933273)
public boolean setActiveDevice(@Nullable BluetoothDevice device) {
if (DBG) {
Log.d(TAG, "setActiveDevice: " + device);
@@ -1155,7 +1199,7 @@ public final class BluetoothHeadset implements BluetoothProfile {
* is active.
* @hide
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(trackingBug = 171933273)
@Nullable
@RequiresPermission(Manifest.permission.BLUETOOTH)
public BluetoothDevice getActiveDevice() {
diff --git a/framework/java/android/bluetooth/BluetoothHeadsetClient.java b/framework/java/android/bluetooth/BluetoothHeadsetClient.java
index 85e0e08b19..e5b2a1e23c 100644
--- a/framework/java/android/bluetooth/BluetoothHeadsetClient.java
+++ b/framework/java/android/bluetooth/BluetoothHeadsetClient.java
@@ -19,10 +19,10 @@ package android.bluetooth;
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
-import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.os.Binder;
+import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
@@ -446,7 +446,7 @@ public final class BluetoothHeadsetClient implements BluetoothProfile {
*
* @hide
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public boolean connect(BluetoothDevice device) {
if (DBG) log("connect(" + device + ")");
final IBluetoothHeadsetClient service =
@@ -472,7 +472,7 @@ public final class BluetoothHeadsetClient implements BluetoothProfile {
*
* @hide
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public boolean disconnect(BluetoothDevice device) {
if (DBG) log("disconnect(" + device + ")");
final IBluetoothHeadsetClient service =
@@ -587,7 +587,6 @@ public final class BluetoothHeadsetClient implements BluetoothProfile {
* @return true if connectionPolicy is set, false on error
* @hide
*/
- @SystemApi
@RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
@ConnectionPolicy int connectionPolicy) {
@@ -637,7 +636,6 @@ public final class BluetoothHeadsetClient implements BluetoothProfile {
* @return connection policy of the device
* @hide
*/
- @SystemApi
@RequiresPermission(Manifest.permission.BLUETOOTH)
public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
if (VDBG) log("getConnectionPolicy(" + device + ")");
@@ -783,7 +781,7 @@ 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.
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public boolean acceptCall(BluetoothDevice device, int flag) {
if (DBG) log("acceptCall()");
final IBluetoothHeadsetClient service =
@@ -832,7 +830,7 @@ public final class BluetoothHeadsetClient implements BluetoothProfile {
* #EXTRA_AG_FEATURE_REJECT_CALL}. This method invocation will fail silently when feature is not
* supported.</p>
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public boolean rejectCall(BluetoothDevice device) {
if (DBG) log("rejectCall()");
final IBluetoothHeadsetClient service =
@@ -1017,7 +1015,7 @@ public final class BluetoothHeadsetClient implements BluetoothProfile {
*
* Note: This is an internal function and shouldn't be exposed
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public int getAudioState(BluetoothDevice device) {
if (VDBG) log("getAudioState");
final IBluetoothHeadsetClient service =
diff --git a/framework/java/android/bluetooth/BluetoothHeadsetClientCall.java b/framework/java/android/bluetooth/BluetoothHeadsetClientCall.java
index d1a096e605..219d1596fb 100644
--- a/framework/java/android/bluetooth/BluetoothHeadsetClientCall.java
+++ b/framework/java/android/bluetooth/BluetoothHeadsetClientCall.java
@@ -17,6 +17,7 @@
package android.bluetooth;
import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock;
@@ -144,7 +145,7 @@ public final class BluetoothHeadsetClientCall implements Parcelable {
*
* @return call id.
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public int getId() {
return mId;
}
@@ -164,7 +165,7 @@ public final class BluetoothHeadsetClientCall implements Parcelable {
*
* @return state of this particular phone call.
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public int getState() {
return mState;
}
@@ -174,7 +175,7 @@ public final class BluetoothHeadsetClientCall implements Parcelable {
*
* @return string representing phone number.
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public String getNumber() {
return mNumber;
}
@@ -193,7 +194,7 @@ public final class BluetoothHeadsetClientCall implements Parcelable {
*
* @return <code>true</code> if call is a multi party call, <code>false</code> otherwise.
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public boolean isMultiParty() {
return mMultiParty;
}
@@ -203,7 +204,7 @@ public final class BluetoothHeadsetClientCall implements Parcelable {
*
* @return <code>true</code> if its outgoing call, <code>false</code> otherwise.
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public boolean isOutgoing() {
return mOutgoing;
}
diff --git a/framework/java/android/bluetooth/BluetoothHearingAid.java b/framework/java/android/bluetooth/BluetoothHearingAid.java
index fa62a02499..ff78825e0f 100644
--- a/framework/java/android/bluetooth/BluetoothHearingAid.java
+++ b/framework/java/android/bluetooth/BluetoothHearingAid.java
@@ -26,6 +26,7 @@ import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.os.Binder;
+import android.os.Build;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
@@ -85,7 +86,7 @@ public final class BluetoothHearingAid implements BluetoothProfile {
*
* @hide
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_ACTIVE_DEVICE_CHANGED =
"android.bluetooth.hearingaid.profile.action.ACTIVE_DEVICE_CHANGED";
@@ -302,7 +303,7 @@ public final class BluetoothHearingAid implements BluetoothProfile {
* @return false on immediate error, true otherwise
* @hide
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public boolean setActiveDevice(@Nullable BluetoothDevice device) {
if (DBG) log("setActiveDevice(" + device + ")");
final IBluetoothHearingAid service = getService();
@@ -328,7 +329,7 @@ public final class BluetoothHearingAid implements BluetoothProfile {
* is not active, it will be null on that position. Returns empty list on error.
* @hide
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@RequiresPermission(Manifest.permission.BLUETOOTH)
public @NonNull List<BluetoothDevice> getActiveDevices() {
if (VDBG) log("getActiveDevices()");
diff --git a/framework/java/android/bluetooth/BluetoothLeAudio.java b/framework/java/android/bluetooth/BluetoothLeAudio.java
new file mode 100644
index 0000000000..3f00fa6f41
--- /dev/null
+++ b/framework/java/android/bluetooth/BluetoothLeAudio.java
@@ -0,0 +1,451 @@
+/*
+ * Copyright 2020 HIMSA II K/S - www.himsa.com.
+ * Represented by EHIMA - www.ehima.com
+ *
+ * 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.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.content.Context;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.CloseGuard;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class provides the public APIs to control the LeAudio profile.
+ *
+ * <p>BluetoothLeAudio is a proxy object for controlling the Bluetooth LE Audio
+ * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get
+ * the BluetoothLeAudio proxy object.
+ *
+ * <p> Android only supports one set of connected Bluetooth LeAudio device at a time. Each
+ * method is protected with its appropriate permission.
+ */
+public final class BluetoothLeAudio implements BluetoothProfile, AutoCloseable {
+ private static final String TAG = "BluetoothLeAudio";
+ private static final boolean DBG = false;
+ private static final boolean VDBG = false;
+
+ private CloseGuard mCloseGuard;
+
+ /**
+ * Intent used to broadcast the change in connection state of the LeAudio
+ * profile. Please note that in the binaural case, there will be two different LE devices for
+ * the left and right side and each device will have their own connection state changes.
+ *
+ * <p>This intent will have 3 extras:
+ * <ul>
+ * <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
+ * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li>
+ * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
+ * </ul>
+ *
+ * <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.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED =
+ "android.bluetooth.action.LE_AUDIO_CONNECTION_STATE_CHANGED";
+
+ /**
+ * Intent used to broadcast the selection of a connected device as active.
+ *
+ * <p>This intent will have one extra:
+ * <ul>
+ * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. It can
+ * be null if no device is active. </li>
+ * </ul>
+ *
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
+ * receive.
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED =
+ "android.bluetooth.action.LE_AUDIO_ACTIVE_DEVICE_CHANGED";
+
+ /**
+ * This represents an invalid group ID.
+ *
+ * @hide
+ */
+ public static final int GROUP_ID_INVALID = IBluetoothLeAudio.LE_AUDIO_GROUP_ID_INVALID;
+
+ private BluetoothAdapter mAdapter;
+ private final BluetoothProfileConnector<IBluetoothLeAudio> mProfileConnector =
+ new BluetoothProfileConnector(this, BluetoothProfile.LE_AUDIO, "BluetoothLeAudio",
+ IBluetoothLeAudio.class.getName()) {
+ @Override
+ public IBluetoothLeAudio getServiceInterface(IBinder service) {
+ return IBluetoothLeAudio.Stub.asInterface(Binder.allowBlocking(service));
+ }
+ };
+
+ /**
+ * Create a BluetoothLeAudio proxy object for interacting with the local
+ * Bluetooth LeAudio service.
+ */
+ /*package*/ BluetoothLeAudio(Context context, ServiceListener listener) {
+ mAdapter = BluetoothAdapter.getDefaultAdapter();
+ mProfileConnector.connect(context, listener);
+ mCloseGuard = new CloseGuard();
+ mCloseGuard.open("close");
+ }
+
+ /**
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ public void close() {
+ mProfileConnector.disconnect();
+ }
+
+ private IBluetoothLeAudio getService() {
+ return mProfileConnector.getService();
+ }
+
+ @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ protected void finalize() {
+ if (mCloseGuard != null) {
+ mCloseGuard.warnIfOpen();
+ }
+ close();
+ }
+
+ /**
+ * Initiate connection to a profile of the remote bluetooth device.
+ *
+ * <p> This API returns false in scenarios like the profile on the
+ * device is already connected or Bluetooth is not turned on.
+ * When this API returns true, it is guaranteed that
+ * connection state intent for the profile will be broadcasted with
+ * the state. Users can get the connection state of the profile
+ * from this intent.
+ *
+ *
+ * @param device Remote Bluetooth Device
+ * @return false on immediate error, true otherwise
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ 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);
+ }
+ if (service == null) Log.w(TAG, "Proxy not attached to service");
+ return false;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+ return false;
+ }
+ }
+
+ /**
+ * Initiate disconnection from a profile
+ *
+ * <p> This API will return false in scenarios like the profile on the
+ * Bluetooth device is not in connected state etc. When this API returns,
+ * true, it is guaranteed that the connection state change
+ * intent will be broadcasted with the state. Users can get the
+ * disconnection state of the profile from this intent.
+ *
+ * <p> If the disconnection is initiated by a remote device, the state
+ * will transition from {@link #STATE_CONNECTED} to
+ * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the
+ * host (local) device the state will transition from
+ * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to
+ * state {@link #STATE_DISCONNECTED}. The transition to
+ * {@link #STATE_DISCONNECTING} can be used to distinguish between the
+ * two scenarios.
+ *
+ *
+ * @param device Remote Bluetooth Device
+ * @return false on immediate error, true otherwise
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ 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);
+ }
+ if (service == null) Log.w(TAG, "Proxy not attached to service");
+ return false;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+ return false;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public @NonNull List<BluetoothDevice> getConnectedDevices() {
+ if (VDBG) log("getConnectedDevices()");
+ try {
+ final IBluetoothLeAudio service = getService();
+ if (service != null && mAdapter.isEnabled()) {
+ return service.getConnectedDevices();
+ }
+ if (service == null) Log.w(TAG, "Proxy not attached to service");
+ return new ArrayList<BluetoothDevice>();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+ return new ArrayList<BluetoothDevice>();
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ 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);
+ }
+ if (service == null) Log.w(TAG, "Proxy not attached to service");
+ return new ArrayList<BluetoothDevice>();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+ return new ArrayList<BluetoothDevice>();
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ @RequiresPermission(Manifest.permission.BLUETOOTH)
+ 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);
+ }
+ if (service == null) Log.w(TAG, "Proxy not attached to service");
+ return BluetoothProfile.STATE_DISCONNECTED;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+ return BluetoothProfile.STATE_DISCONNECTED;
+ }
+ }
+
+ /**
+ * Select a connected device as active.
+ *
+ * The active device selection is per profile. An active device's
+ * purpose is profile-specific. For example, LeAudio audio
+ * streaming is to the active LeAudio device. If a remote device
+ * is not connected, it cannot be selected as active.
+ *
+ * <p> This API returns false in scenarios like the profile on the
+ * device is not connected or Bluetooth is not turned on.
+ * When this API returns true, it is guaranteed that the
+ * {@link #ACTION_LEAUDIO_ACTIVE_DEVICE_CHANGED} intent will be broadcasted
+ * with the active device.
+ *
+ *
+ * @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
+ */
+ @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ 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);
+ return true;
+ }
+ if (service == null) Log.w(TAG, "Proxy not attached to service");
+ return false;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+ return false;
+ }
+ }
+
+ /**
+ * Get the connected LeAudio devices that are active
+ *
+ * @return the list of active devices. Returns empty list on error.
+ * @hide
+ */
+ @NonNull
+ @RequiresPermission(Manifest.permission.BLUETOOTH)
+ public List<BluetoothDevice> getActiveDevices() {
+ if (VDBG) log("getActiveDevices()");
+ try {
+ final IBluetoothLeAudio service = getService();
+ if (service != null && mAdapter.isEnabled()) {
+ return service.getActiveDevices();
+ }
+ if (service == null) Log.w(TAG, "Proxy not attached to service");
+ return new ArrayList<>();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+ return new ArrayList<>();
+ }
+ }
+
+ /**
+ * Get device group id. Devices with same group id belong to same group (i.e left and right
+ * earbud)
+ * @param device LE Audio capable device
+ * @return group id that this device currently belongs to
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.BLUETOOTH)
+ public int getGroupId(@NonNull BluetoothDevice device) {
+ if (VDBG) log("getGroupId()");
+ try {
+ final IBluetoothLeAudio service = getService();
+ if (service != null && mAdapter.isEnabled()) {
+ return service.getGroupId(device);
+ }
+ if (service == null) Log.w(TAG, "Proxy not attached to service");
+ return GROUP_ID_INVALID;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+ return GROUP_ID_INVALID;
+ }
+ }
+
+ /**
+ * Set connection policy of the profile
+ *
+ * <p> The device should already be paired.
+ * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED},
+ * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN}
+ *
+ * @param device Paired bluetooth device
+ * @param connectionPolicy is the connection policy to set to for this profile
+ * @return true if connectionPolicy is set, false on error
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
+ @ConnectionPolicy int connectionPolicy) {
+ if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
+ try {
+ final IBluetoothLeAudio service = getService();
+ if (service != null && mAdapter.isEnabled()
+ && isValidDevice(device)) {
+ if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+ && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+ return false;
+ }
+ return service.setConnectionPolicy(device, connectionPolicy);
+ }
+ if (service == null) Log.w(TAG, "Proxy not attached to service");
+ return false;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+ return false;
+ }
+ }
+
+ /**
+ * Get the connection policy of the profile.
+ *
+ * <p> The connection policy can be any of:
+ * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN},
+ * {@link #CONNECTION_POLICY_UNKNOWN}
+ *
+ * @param device Bluetooth device
+ * @return connection policy of the device
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ 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);
+ }
+ if (service == null) Log.w(TAG, "Proxy not attached to service");
+ return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+ return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+ }
+ }
+
+
+ /**
+ * Helper for converting a state to a string.
+ *
+ * For debug use only - strings are not internationalized.
+ *
+ * @hide
+ */
+ public static String stateToString(int state) {
+ switch (state) {
+ case STATE_DISCONNECTED:
+ return "disconnected";
+ case STATE_CONNECTING:
+ return "connecting";
+ case STATE_CONNECTED:
+ return "connected";
+ case STATE_DISCONNECTING:
+ return "disconnecting";
+ default:
+ return "<unknown state " + state + ">";
+ }
+ }
+
+ private boolean isValidDevice(@Nullable BluetoothDevice device) {
+ if (device == null) return false;
+
+ if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
+ return false;
+ }
+
+ private static void log(String msg) {
+ Log.d(TAG, msg);
+ }
+}
diff --git a/framework/java/android/bluetooth/BluetoothManager.java b/framework/java/android/bluetooth/BluetoothManager.java
index 3b4fe0a30b..d5c1c3e2d6 100644
--- a/framework/java/android/bluetooth/BluetoothManager.java
+++ b/framework/java/android/bluetooth/BluetoothManager.java
@@ -225,6 +225,24 @@ public final class BluetoothManager {
*
* @param context App context
* @param callback GATT server callback handler that will receive asynchronous callbacks.
+ * @param eatt_support idicates if server should use eatt channel for notifications.
+ * @return BluetoothGattServer instance
+ * @hide
+ */
+ public BluetoothGattServer openGattServer(Context context,
+ BluetoothGattServerCallback callback, boolean eatt_support) {
+ return (openGattServer(context, callback, BluetoothDevice.TRANSPORT_AUTO, eatt_support));
+ }
+
+ /**
+ * Open a GATT Server
+ * The callback is used to deliver results to Caller, such as connection status as well
+ * as the results of any other GATT server operations.
+ * The method returns a BluetoothGattServer instance. You can use BluetoothGattServer
+ * to conduct GATT server operations.
+ *
+ * @param context App context
+ * @param callback GATT server callback handler that will receive asynchronous callbacks.
* @param transport preferred transport for GATT connections to remote dual-mode devices {@link
* BluetoothDevice#TRANSPORT_AUTO} or {@link BluetoothDevice#TRANSPORT_BREDR} or {@link
* BluetoothDevice#TRANSPORT_LE}
@@ -233,6 +251,27 @@ public final class BluetoothManager {
*/
public BluetoothGattServer openGattServer(Context context,
BluetoothGattServerCallback callback, int transport) {
+ return (openGattServer(context, callback, transport, false));
+ }
+
+ /**
+ * Open a GATT Server
+ * The callback is used to deliver results to Caller, such as connection status as well
+ * as the results of any other GATT server operations.
+ * The method returns a BluetoothGattServer instance. You can use BluetoothGattServer
+ * to conduct GATT server operations.
+ *
+ * @param context App context
+ * @param callback GATT server callback handler that will receive asynchronous callbacks.
+ * @param transport preferred transport for GATT connections to remote dual-mode devices {@link
+ * BluetoothDevice#TRANSPORT_AUTO} or {@link BluetoothDevice#TRANSPORT_BREDR} or {@link
+ * BluetoothDevice#TRANSPORT_LE}
+ * @param eatt_support idicates if server should use eatt channel for notifications.
+ * @return BluetoothGattServer instance
+ * @hide
+ */
+ public BluetoothGattServer openGattServer(Context context,
+ BluetoothGattServerCallback callback, int transport, boolean eatt_support) {
if (context == null || callback == null) {
throw new IllegalArgumentException("null parameter: " + context + " " + callback);
}
@@ -248,7 +287,7 @@ public final class BluetoothManager {
return null;
}
BluetoothGattServer mGattServer = new BluetoothGattServer(iGatt, transport);
- Boolean regStatus = mGattServer.registerCallback(callback);
+ Boolean regStatus = mGattServer.registerCallback(callback, eatt_support);
return regStatus ? mGattServer : null;
} catch (RemoteException e) {
Log.e(TAG, "", e);
diff --git a/framework/java/android/bluetooth/BluetoothMap.java b/framework/java/android/bluetooth/BluetoothMap.java
index 14a71c4467..3554995400 100644
--- a/framework/java/android/bluetooth/BluetoothMap.java
+++ b/framework/java/android/bluetooth/BluetoothMap.java
@@ -24,6 +24,7 @@ import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.os.Binder;
+import android.os.Build;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.CloseGuard;
@@ -209,7 +210,7 @@ public final class BluetoothMap implements BluetoothProfile, AutoCloseable {
*
* @hide
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public boolean disconnect(BluetoothDevice device) {
if (DBG) log("disconnect(" + device + ")");
final IBluetoothMap service = getService();
diff --git a/framework/java/android/bluetooth/BluetoothMapClient.java b/framework/java/android/bluetooth/BluetoothMapClient.java
index 19240dc0bb..0312a2190a 100644
--- a/framework/java/android/bluetooth/BluetoothMapClient.java
+++ b/framework/java/android/bluetooth/BluetoothMapClient.java
@@ -18,6 +18,7 @@ package android.bluetooth;
import android.Manifest;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.app.PendingIntent;
@@ -25,11 +26,13 @@ import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.net.Uri;
import android.os.Binder;
+import android.os.Build;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.List;
/**
@@ -37,45 +40,118 @@ import java.util.List;
*
* @hide
*/
+@SystemApi
public final class BluetoothMapClient implements BluetoothProfile {
private static final String TAG = "BluetoothMapClient";
private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
private static final boolean VDBG = Log.isLoggable(TAG, Log.VERBOSE);
+ /** @hide */
public static final String ACTION_CONNECTION_STATE_CHANGED =
"android.bluetooth.mapmce.profile.action.CONNECTION_STATE_CHANGED";
+ /** @hide */
public static final String ACTION_MESSAGE_RECEIVED =
"android.bluetooth.mapmce.profile.action.MESSAGE_RECEIVED";
/* Actions to be used for pending intents */
+ /** @hide */
public static final String ACTION_MESSAGE_SENT_SUCCESSFULLY =
"android.bluetooth.mapmce.profile.action.MESSAGE_SENT_SUCCESSFULLY";
+ /** @hide */
public static final String ACTION_MESSAGE_DELIVERED_SUCCESSFULLY =
"android.bluetooth.mapmce.profile.action.MESSAGE_DELIVERED_SUCCESSFULLY";
- /* Extras used in ACTION_MESSAGE_RECEIVED intent.
- * NOTE: HANDLE is only valid for a single session with the device. */
+ /**
+ * Action to notify read status changed
+ *
+ * @hide
+ */
+ public static final String ACTION_MESSAGE_READ_STATUS_CHANGED =
+ "android.bluetooth.mapmce.profile.action.MESSAGE_READ_STATUS_CHANGED";
+
+ /**
+ * Action to notify deleted status changed
+ *
+ * @hide
+ */
+ public static final String ACTION_MESSAGE_DELETED_STATUS_CHANGED =
+ "android.bluetooth.mapmce.profile.action.MESSAGE_DELETED_STATUS_CHANGED";
+
+ /**
+ * Extras used in ACTION_MESSAGE_RECEIVED intent.
+ * NOTE: HANDLE is only valid for a single session with the device.
+ */
+ /** @hide */
public static final String EXTRA_MESSAGE_HANDLE =
"android.bluetooth.mapmce.profile.extra.MESSAGE_HANDLE";
+ /** @hide */
public static final String EXTRA_MESSAGE_TIMESTAMP =
"android.bluetooth.mapmce.profile.extra.MESSAGE_TIMESTAMP";
+ /** @hide */
public static final String EXTRA_MESSAGE_READ_STATUS =
"android.bluetooth.mapmce.profile.extra.MESSAGE_READ_STATUS";
+ /** @hide */
public static final String EXTRA_SENDER_CONTACT_URI =
"android.bluetooth.mapmce.profile.extra.SENDER_CONTACT_URI";
+ /** @hide */
public static final String EXTRA_SENDER_CONTACT_NAME =
"android.bluetooth.mapmce.profile.extra.SENDER_CONTACT_NAME";
- /** There was an error trying to obtain the state */
+ /**
+ * Used as a boolean extra in ACTION_MESSAGE_DELETED_STATUS_CHANGED
+ * Contains the MAP message deleted status
+ * Possible values are:
+ * true: deleted
+ * false: undeleted
+ *
+ * @hide
+ */
+ public static final String EXTRA_MESSAGE_DELETED_STATUS =
+ "android.bluetooth.mapmce.profile.extra.MESSAGE_DELETED_STATUS";
+
+ /**
+ * Extra used in ACTION_MESSAGE_READ_STATUS_CHANGED or ACTION_MESSAGE_DELETED_STATUS_CHANGED
+ * Possible values are:
+ * 0: failure
+ * 1: success
+ *
+ * @hide
+ */
+ public static final String EXTRA_RESULT_CODE =
+ "android.bluetooth.device.extra.RESULT_CODE";
+
+ /**
+ * There was an error trying to obtain the state
+ * @hide
+ */
public static final int STATE_ERROR = -1;
+ /** @hide */
public static final int RESULT_FAILURE = 0;
+ /** @hide */
public static final int RESULT_SUCCESS = 1;
- /** Connection canceled before completion. */
+ /**
+ * Connection canceled before completion.
+ * @hide
+ */
public static final int RESULT_CANCELED = 2;
-
+ /** @hide */
private static final int UPLOADING_FEATURE_BITMASK = 0x08;
+ /*
+ * UNREAD, READ, UNDELETED, DELETED are passed as parameters
+ * to setMessageStatus to indicate the messages new state.
+ */
+
+ /** @hide */
+ public static final int UNREAD = 0;
+ /** @hide */
+ public static final int READ = 1;
+ /** @hide */
+ public static final int UNDELETED = 2;
+ /** @hide */
+ public static final int DELETED = 3;
+
private BluetoothAdapter mAdapter;
private final BluetoothProfileConnector<IBluetoothMapClient> mProfileConnector =
new BluetoothProfileConnector(this, BluetoothProfile.MAP_CLIENT,
@@ -95,19 +171,12 @@ public final class BluetoothMapClient implements BluetoothProfile {
mProfileConnector.connect(context, listener);
}
- protected void finalize() throws Throwable {
- try {
- close();
- } finally {
- super.finalize();
- }
- }
-
/**
* Close the connection to the backing service.
* Other public functions of BluetoothMap will return default error
* results once close() has been called. Multiple invocations of close()
* are ok.
+ * @hide
*/
public void close() {
mProfileConnector.disconnect();
@@ -121,6 +190,7 @@ public final class BluetoothMapClient implements BluetoothProfile {
* Returns true if the specified Bluetooth device is connected.
* Returns false if not connected, or if this proxy object is not
* currently connected to the Map service.
+ * @hide
*/
public boolean isConnected(BluetoothDevice device) {
if (VDBG) Log.d(TAG, "isConnected(" + device + ")");
@@ -188,6 +258,7 @@ public final class BluetoothMapClient implements BluetoothProfile {
* Get the list of connected devices. Currently at most one.
*
* @return list of connected devices
+ * @hide
*/
@Override
public List<BluetoothDevice> getConnectedDevices() {
@@ -209,6 +280,7 @@ public final class BluetoothMapClient implements BluetoothProfile {
* Get the list of devices matching specified states. Currently at most one.
*
* @return list of matching devices
+ * @hide
*/
@Override
public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
@@ -230,6 +302,7 @@ public final class BluetoothMapClient implements BluetoothProfile {
* Get connection state of device
*
* @return device connection state
+ * @hide
*/
@Override
public int getConnectionState(BluetoothDevice device) {
@@ -276,7 +349,6 @@ public final class BluetoothMapClient implements BluetoothProfile {
* @return true if connectionPolicy is set, false on error
* @hide
*/
- @SystemApi
@RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
@ConnectionPolicy int connectionPolicy) {
@@ -325,7 +397,6 @@ public final class BluetoothMapClient implements BluetoothProfile {
* @return connection policy of the device
* @hide
*/
- @SystemApi
@RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
if (VDBG) Log.d(TAG, "getConnectionPolicy(" + device + ")");
@@ -348,13 +419,46 @@ public final class BluetoothMapClient implements BluetoothProfile {
* Send an SMS message to either the contacts primary number or the telephone number specified.
*
* @param device Bluetooth device
+ * @param contacts Uri Collection of the contacts
+ * @param message Message to be sent
+ * @param sentIntent intent issued when message is sent
+ * @param deliveredIntent intent issued when message is delivered
+ * @return true if the message is enqueued, false on error
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.SEND_SMS)
+ public boolean sendMessage(@NonNull BluetoothDevice device, @NonNull Collection<Uri> contacts,
+ @NonNull String message, @Nullable PendingIntent sentIntent,
+ @Nullable 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.toArray(new Uri[contacts.size()]),
+ message, sentIntent, deliveredIntent);
+ } catch (RemoteException e) {
+ Log.e(TAG, Log.getStackTraceString(new Throwable()));
+ return false;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Send a message.
+ *
+ * Send an SMS message to either the contacts primary number or the telephone number specified.
+ *
+ * @param device Bluetooth device
* @param contacts Uri[] of the contacts
* @param message Message to be sent
* @param sentIntent intent issued when message is sent
* @param deliveredIntent intent issued when message is delivered
* @return true if the message is enqueued, false on error
+ * @hide
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public boolean sendMessage(BluetoothDevice device, Uri[] contacts, String message,
PendingIntent sentIntent, PendingIntent deliveredIntent) {
if (DBG) Log.d(TAG, "sendMessage(" + device + ", " + contacts + ", " + message);
@@ -375,6 +479,7 @@ public final class BluetoothMapClient implements BluetoothProfile {
*
* @param device Bluetooth device
* @return true if the message is enqueued, false on error
+ * @hide
*/
public boolean getUnreadMessages(BluetoothDevice device) {
if (DBG) Log.d(TAG, "getUnreadMessages(" + device + ")");
@@ -396,6 +501,7 @@ public final class BluetoothMapClient implements BluetoothProfile {
* @param device The Bluetooth device to get this value for.
* @return Returns true if the Uploading bit value in SDP record's
* MapSupportedFeatures field is set. False is returned otherwise.
+ * @hide
*/
public boolean isUploadingSupported(BluetoothDevice device) {
final IBluetoothMapClient service = getService();
@@ -408,6 +514,38 @@ public final class BluetoothMapClient implements BluetoothProfile {
return false;
}
+ /**
+ * Set message status of message on MSE
+ * <p>
+ * When read status changed, the result will be published via
+ * {@link #ACTION_MESSAGE_READ_STATUS_CHANGED}
+ * When deleted status changed, the result will be published via
+ * {@link #ACTION_MESSAGE_DELETED_STATUS_CHANGED}
+ *
+ * @param device Bluetooth device
+ * @param handle message handle
+ * @param status <code>UNREAD</code> for "unread", <code>READ</code> for
+ * "read", <code>UNDELETED</code> for "undeleted", <code>DELETED</code> for
+ * "deleted", otherwise return error
+ * @return <code>true</code> if request has been sent, <code>false</code> on error
+ * @hide
+ */
+ @RequiresPermission(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);
+ } catch (RemoteException e) {
+ Log.e(TAG, Log.getStackTraceString(new Throwable()));
+ return false;
+ }
+ }
+ return false;
+ }
+
private boolean isEnabled() {
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
if (adapter != null && adapter.getState() == BluetoothAdapter.STATE_ON) return true;
diff --git a/framework/java/android/bluetooth/BluetoothPan.java b/framework/java/android/bluetooth/BluetoothPan.java
index a80f5b7f36..f06dad8232 100644
--- a/framework/java/android/bluetooth/BluetoothPan.java
+++ b/framework/java/android/bluetooth/BluetoothPan.java
@@ -27,6 +27,7 @@ import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.os.Binder;
+import android.os.Build;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
@@ -89,6 +90,33 @@ public final class BluetoothPan implements BluetoothProfile {
@SuppressLint("ActionValue")
public static final String EXTRA_LOCAL_ROLE = "android.bluetooth.pan.extra.LOCAL_ROLE";
+ /**
+ * Intent used to broadcast the change in tethering state of the Pan
+ * Profile
+ *
+ * <p>This intent will have 1 extra:
+ * <ul>
+ * <li> {@link #EXTRA_TETHERING_STATE} - The current state of Bluetooth
+ * tethering. </li>
+ * </ul>
+ *
+ * <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.
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_TETHERING_STATE_CHANGED =
+ "android.bluetooth.action.TETHERING_STATE_CHANGED";
+
+ /**
+ * Extra for {@link #ACTION_TETHERING_STATE_CHANGED} intent
+ * The tethering state of the PAN profile.
+ * It can be one of {@link #TETHERING_STATE_OFF} or {@link #TETHERING_STATE_ON}.
+ */
+ public static final String EXTRA_TETHERING_STATE =
+ "android.bluetooth.extra.TETHERING_STATE";
+
/** @hide */
@IntDef({PAN_ROLE_NONE, LOCAL_NAP_ROLE, LOCAL_PANU_ROLE})
@Retention(RetentionPolicy.SOURCE)
@@ -114,6 +142,14 @@ public final class BluetoothPan implements BluetoothProfile {
public static final int REMOTE_PANU_ROLE = 2;
+ /** @hide **/
+ @IntDef({TETHERING_STATE_OFF, TETHERING_STATE_ON})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface TetheringState{}
+
+ public static final int TETHERING_STATE_OFF = 1;
+
+ public static final int TETHERING_STATE_ON = 2;
/**
* Return codes for the connect and disconnect Bluez / Dbus calls.
*
@@ -199,7 +235,7 @@ public final class BluetoothPan implements BluetoothProfile {
* @return false on immediate error, true otherwise
* @hide
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public boolean connect(BluetoothDevice device) {
if (DBG) log("connect(" + device + ")");
final IBluetoothPan service = getService();
@@ -367,7 +403,7 @@ public final class BluetoothPan implements BluetoothProfile {
final IBluetoothPan service = getService();
if (service != null && isEnabled()) {
try {
- service.setBluetoothTethering(value, pkgName);
+ service.setBluetoothTethering(value, pkgName, null);
} 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 d58a893501..6e5c45f3d1 100644
--- a/framework/java/android/bluetooth/BluetoothPbap.java
+++ b/framework/java/android/bluetooth/BluetoothPbap.java
@@ -27,6 +27,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
+import android.os.Build;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -322,7 +323,7 @@ public class BluetoothPbap implements BluetoothProfile {
*
* @hide
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public boolean disconnect(BluetoothDevice device) {
log("disconnect()");
final IBluetoothPbap service = mService;
diff --git a/framework/java/android/bluetooth/BluetoothPbapClient.java b/framework/java/android/bluetooth/BluetoothPbapClient.java
index d3452ffb45..f356da18fc 100644
--- a/framework/java/android/bluetooth/BluetoothPbapClient.java
+++ b/framework/java/android/bluetooth/BluetoothPbapClient.java
@@ -19,7 +19,6 @@ package android.bluetooth;
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
-import android.annotation.SystemApi;
import android.content.Context;
import android.os.Binder;
import android.os.IBinder;
@@ -276,7 +275,6 @@ public final class BluetoothPbapClient implements BluetoothProfile {
* @return true if connectionPolicy is set, false on error
* @hide
*/
- @SystemApi
@RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
@ConnectionPolicy int connectionPolicy) {
@@ -329,7 +327,6 @@ public final class BluetoothPbapClient implements BluetoothProfile {
* @return connection policy of the device
* @hide
*/
- @SystemApi
@RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
if (VDBG) {
diff --git a/framework/java/android/bluetooth/BluetoothProfile.java b/framework/java/android/bluetooth/BluetoothProfile.java
index 7538df8bbe..201d6c495d 100644
--- a/framework/java/android/bluetooth/BluetoothProfile.java
+++ b/framework/java/android/bluetooth/BluetoothProfile.java
@@ -23,6 +23,7 @@ import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -185,6 +186,7 @@ public interface BluetoothProfile {
*
* @hide
*/
+ @SystemApi
int MAP_CLIENT = 18;
/**
@@ -206,12 +208,19 @@ public interface BluetoothProfile {
int HEARING_AID = 21;
/**
+ * LE Audio Device
+ *
+ * @hide
+ */
+ int LE_AUDIO = 22;
+
+ /**
* Max profile ID. This value should be updated whenever a new profile is added to match
* the largest value assigned to a profile.
*
* @hide
*/
- int MAX_PROFILE_ID = 21;
+ int MAX_PROFILE_ID = 22;
/**
* Default priority for devices that we try to auto-connect to and
@@ -219,7 +228,7 @@ public interface BluetoothProfile {
*
* @hide
**/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
int PRIORITY_AUTO_CONNECT = 1000;
/**
diff --git a/framework/java/android/bluetooth/BluetoothSap.java b/framework/java/android/bluetooth/BluetoothSap.java
index 6e0348158f..0d70dbdd84 100644
--- a/framework/java/android/bluetooth/BluetoothSap.java
+++ b/framework/java/android/bluetooth/BluetoothSap.java
@@ -18,10 +18,10 @@ package android.bluetooth;
import android.Manifest;
import android.annotation.RequiresPermission;
-import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.os.Binder;
+import android.os.Build;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
@@ -220,7 +220,7 @@ public final class BluetoothSap implements BluetoothProfile {
* @return false on error, true otherwise
* @hide
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public boolean disconnect(BluetoothDevice device) {
if (DBG) log("disconnect(" + device + ")");
final IBluetoothSap service = getService();
@@ -328,7 +328,6 @@ public final class BluetoothSap implements BluetoothProfile {
* @return true if connectionPolicy is set, false on error
* @hide
*/
- @SystemApi
@RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
public boolean setConnectionPolicy(BluetoothDevice device,
@ConnectionPolicy int connectionPolicy) {
@@ -377,7 +376,6 @@ public final class BluetoothSap implements BluetoothProfile {
* @return connection policy of the device
* @hide
*/
- @SystemApi
@RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
public @ConnectionPolicy int getConnectionPolicy(BluetoothDevice device) {
if (VDBG) log("getConnectionPolicy(" + device + ")");
diff --git a/framework/java/android/bluetooth/BluetoothServerSocket.java b/framework/java/android/bluetooth/BluetoothServerSocket.java
index 88c186c88a..5c1bcaf313 100644
--- a/framework/java/android/bluetooth/BluetoothServerSocket.java
+++ b/framework/java/android/bluetooth/BluetoothServerSocket.java
@@ -110,7 +110,7 @@ public final class BluetoothServerSocket implements Closeable {
* @param auth require the remote device to be authenticated
* @param encrypt require the connection to be encrypted
* @param port remote port
- * @param mitm enforce man-in-the-middle protection for authentication.
+ * @param mitm enforce person-in-the-middle protection for authentication.
* @param min16DigitPin enforce a minimum length of 16 digits for a sec mode 2 connection
* @throws IOException On error, for example Bluetooth not available, or insufficient
* privileges
diff --git a/framework/java/android/bluetooth/BluetoothSocket.java b/framework/java/android/bluetooth/BluetoothSocket.java
index f774369655..65381dbb23 100644
--- a/framework/java/android/bluetooth/BluetoothSocket.java
+++ b/framework/java/android/bluetooth/BluetoothSocket.java
@@ -18,6 +18,7 @@ package android.bluetooth;
import android.compat.annotation.UnsupportedAppUsage;
import android.net.LocalSocket;
+import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.os.ParcelUuid;
import android.os.RemoteException;
@@ -111,7 +112,7 @@ public final class BluetoothSocket implements Closeable {
public static final int TYPE_L2CAP_LE = 4;
/*package*/ static final int EBADFD = 77;
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
/*package*/ static final int EADDRINUSE = 98;
/*package*/ static final int SEC_FLAG_ENCRYPT = 1;
@@ -128,9 +129,12 @@ public final class BluetoothSocket implements Closeable {
private final BluetoothInputStream mInputStream;
private final BluetoothOutputStream mOutputStream;
private final ParcelUuid mUuid;
- private boolean mExcludeSdp = false; /* when true no SPP SDP record will be created */
- private boolean mAuthMitm = false; /* when true Man-in-the-middle protection will be enabled*/
- private boolean mMin16DigitPin = false; /* Minimum 16 digit pin for sec mode 2 connections */
+ /** when true no SPP SDP record will be created */
+ private boolean mExcludeSdp = false;
+ /** when true Person-in-the-middle protection will be enabled */
+ private boolean mAuthMitm = false;
+ /** Minimum 16 digit pin for sec mode 2 connections */
+ private boolean mMin16DigitPin = false;
@UnsupportedAppUsage(publicAlternatives = "Use {@link BluetoothSocket} public API instead.")
private ParcelFileDescriptor mPfd;
@UnsupportedAppUsage
@@ -190,7 +194,7 @@ public final class BluetoothSocket implements Closeable {
* @param device remote device that this socket can connect to
* @param port remote port
* @param uuid SDP uuid
- * @param mitm enforce man-in-the-middle protection.
+ * @param mitm enforce person-in-the-middle protection.
* @param min16DigitPin enforce a minimum length of 16 digits for a sec mode 2 connection
* @throws IOException On error, for example Bluetooth not available, or insufficient
* privileges
diff --git a/framework/java/android/bluetooth/BluetoothUuid.java b/framework/java/android/bluetooth/BluetoothUuid.java
index e274af1b5c..d82cf19e88 100644
--- a/framework/java/android/bluetooth/BluetoothUuid.java
+++ b/framework/java/android/bluetooth/BluetoothUuid.java
@@ -153,7 +153,22 @@ public final class BluetoothUuid {
@SystemApi
public static final ParcelUuid HEARING_AID =
ParcelUuid.fromString("0000FDF0-0000-1000-8000-00805f9b34fb");
-
+ /** Placeholder until specification is released
+ * @hide */
+ @NonNull
+ @SystemApi
+ public static final ParcelUuid LE_AUDIO =
+ ParcelUuid.fromString("EEEEEEEE-EEEE-EEEE-EEEE-EEEEEEEEEEEE");
+ /** @hide */
+ @NonNull
+ @SystemApi
+ public static final ParcelUuid DIP =
+ ParcelUuid.fromString("00001200-0000-1000-8000-00805F9B34FB");
+ /** @hide */
+ @NonNull
+ @SystemApi
+ public static final ParcelUuid VOLUME_CONTROL =
+ ParcelUuid.fromString("00001844-0000-1000-8000-00805F9B34FB");
/** @hide */
@NonNull
@SystemApi
diff --git a/framework/java/android/bluetooth/BufferConstraint.java b/framework/java/android/bluetooth/BufferConstraint.java
new file mode 100644
index 0000000000..cbffc788c3
--- /dev/null
+++ b/framework/java/android/bluetooth/BufferConstraint.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2020 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.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Stores a codec's constraints on buffering length in milliseconds.
+ *
+ * {@hide}
+ */
+@SystemApi
+public final class BufferConstraint implements Parcelable {
+
+ private static final String TAG = "BufferConstraint";
+ private int mDefaultMillis;
+ private int mMaxMillis;
+ private int mMinMillis;
+
+ public BufferConstraint(int defaultMillis, int maxMillis,
+ int minMillis) {
+ mDefaultMillis = defaultMillis;
+ mMaxMillis = maxMillis;
+ mMinMillis = minMillis;
+ }
+
+ BufferConstraint(Parcel in) {
+ mDefaultMillis = in.readInt();
+ mMaxMillis = in.readInt();
+ mMinMillis = in.readInt();
+ }
+
+ public static final @NonNull Parcelable.Creator<BufferConstraint> CREATOR =
+ new Parcelable.Creator<BufferConstraint>() {
+ public BufferConstraint createFromParcel(Parcel in) {
+ return new BufferConstraint(in);
+ }
+
+ public BufferConstraint[] newArray(int size) {
+ return new BufferConstraint[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeInt(mDefaultMillis);
+ out.writeInt(mMaxMillis);
+ out.writeInt(mMinMillis);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Get the default buffer millis
+ *
+ * @return default buffer millis
+ * @hide
+ */
+ @SystemApi
+ public int getDefaultMillis() {
+ return mDefaultMillis;
+ }
+
+ /**
+ * Get the maximum buffer millis
+ *
+ * @return maximum buffer millis
+ * @hide
+ */
+ @SystemApi
+ public int getMaxMillis() {
+ return mMaxMillis;
+ }
+
+ /**
+ * Get the minimum buffer millis
+ *
+ * @return minimum buffer millis
+ * @hide
+ */
+ @SystemApi
+ public int getMinMillis() {
+ return mMinMillis;
+ }
+}
diff --git a/framework/java/android/bluetooth/BufferConstraints.java b/framework/java/android/bluetooth/BufferConstraints.java
new file mode 100644
index 0000000000..97d97232b7
--- /dev/null
+++ b/framework/java/android/bluetooth/BufferConstraints.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2020 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.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+
+/**
+ * A parcelable collection of buffer constraints by codec type.
+ *
+ * {@hide}
+ */
+@SystemApi
+public final class BufferConstraints implements Parcelable {
+ public static final int BUFFER_CODEC_MAX_NUM = 32;
+
+ private static final String TAG = "BufferConstraints";
+
+ private Map<Integer, BufferConstraint> mBufferConstraints;
+ private List<BufferConstraint> mBufferConstraintList;
+
+ public BufferConstraints(@NonNull List<BufferConstraint>
+ bufferConstraintList) {
+
+ mBufferConstraintList = new ArrayList<BufferConstraint>(bufferConstraintList);
+ mBufferConstraints = new HashMap<Integer, BufferConstraint>();
+ for (int i = 0; i < BUFFER_CODEC_MAX_NUM; i++) {
+ mBufferConstraints.put(i, bufferConstraintList.get(i));
+ }
+ }
+
+ BufferConstraints(Parcel in) {
+ mBufferConstraintList = new ArrayList<BufferConstraint>();
+ mBufferConstraints = new HashMap<Integer, BufferConstraint>();
+ in.readList(mBufferConstraintList, BufferConstraint.class.getClassLoader());
+ for (int i = 0; i < mBufferConstraintList.size(); i++) {
+ mBufferConstraints.put(i, mBufferConstraintList.get(i));
+ }
+ }
+
+ public static final @NonNull Parcelable.Creator<BufferConstraints> CREATOR =
+ new Parcelable.Creator<BufferConstraints>() {
+ public BufferConstraints createFromParcel(Parcel in) {
+ return new BufferConstraints(in);
+ }
+
+ public BufferConstraints[] newArray(int size) {
+ return new BufferConstraints[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeList(mBufferConstraintList);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * Get the buffer constraints by codec type.
+ *
+ * @param codec Audio codec
+ * @return buffer constraints by codec type.
+ * @hide
+ */
+ @SystemApi
+ public @Nullable BufferConstraint forCodec(@BluetoothCodecConfig.SourceCodecType int codec) {
+ return mBufferConstraints.get(codec);
+ }
+}
diff --git a/framework/java/android/bluetooth/OWNERS b/framework/java/android/bluetooth/OWNERS
new file mode 100644
index 0000000000..3523ee0640
--- /dev/null
+++ b/framework/java/android/bluetooth/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 27441
+
+zachoverflow@google.com
+siyuanh@google.com
diff --git a/framework/java/android/bluetooth/OobData.java b/framework/java/android/bluetooth/OobData.java
index 0d0c6ab2ef..08d694eb93 100644
--- a/framework/java/android/bluetooth/OobData.java
+++ b/framework/java/android/bluetooth/OobData.java
@@ -1,4 +1,4 @@
-/*
+/**
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,88 +16,949 @@
package android.bluetooth;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
+import com.android.internal.util.Preconditions;
+
+import java.lang.IllegalArgumentException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* Out Of Band Data for Bluetooth device pairing.
*
* <p>This object represents optional data obtained from a remote device through
- * an out-of-band channel (eg. NFC).
+ * an out-of-band channel (eg. NFC, QR).
+ *
+ * <p>References:
+ * NFC AD Forum SSP 1.1 (AD)
+ * {@link https://members.nfc-forum.org//apps/group_public/download.php/24620/NFCForum-AD-BTSSP_1_1.pdf}
+ * Core Specification Supplement (CSS) V9
+ *
+ * <p>There are several BR/EDR Examples
+ *
+ * <p>Negotiated Handover:
+ * Bluetooth Carrier Configuration Record:
+ * - OOB Data Length
+ * - Device Address
+ * - Class of Device
+ * - Simple Pairing Hash C
+ * - Simple Pairing Randomizer R
+ * - Service Class UUID
+ * - Bluetooth Local Name
+ *
+ * <p>Static Handover:
+ * Bluetooth Carrier Configuration Record:
+ * - OOB Data Length
+ * - Device Address
+ * - Class of Device
+ * - Service Class UUID
+ * - Bluetooth Local Name
+ *
+ * <p>Simplified Tag Format for Single BT Carrier:
+ * Bluetooth OOB Data Record:
+ * - OOB Data Length
+ * - Device Address
+ * - Class of Device
+ * - Service Class UUID
+ * - Bluetooth Local Name
*
* @hide
*/
-public class OobData implements Parcelable {
- private byte[] mLeBluetoothDeviceAddress;
- private byte[] mSecurityManagerTk;
- private byte[] mLeSecureConnectionsConfirmation;
- private byte[] mLeSecureConnectionsRandom;
+@SystemApi
+public final class OobData implements Parcelable {
+
+ private static final String TAG = "OobData";
+ /** The {@link OobData#mClassicLength} may be. (AD 3.1.1) (CSS 1.6.2) @hide */
+ @SystemApi
+ public static final int OOB_LENGTH_OCTETS = 2;
+ /**
+ * The length for the {@link OobData#mDeviceAddressWithType}(6) and Address Type(1).
+ * (AD 3.1.2) (CSS 1.6.2)
+ * @hide
+ */
+ @SystemApi
+ public static final int DEVICE_ADDRESS_OCTETS = 7;
+ /** The Class of Device is 3 octets. (AD 3.1.3) (CSS 1.6.2) @hide */
+ @SystemApi
+ public static final int CLASS_OF_DEVICE_OCTETS = 3;
+ /** The Confirmation data must be 16 octets. (AD 3.2.2) (CSS 1.6.2) @hide */
+ @SystemApi
+ public static final int CONFIRMATION_OCTETS = 16;
+ /** The Randomizer data must be 16 octets. (AD 3.2.3) (CSS 1.6.2) @hide */
+ @SystemApi
+ public static final int RANDOMIZER_OCTETS = 16;
+ /** The LE Device Role length is 1 octet. (AD 3.3.2) (CSS 1.17) @hide */
+ @SystemApi
+ public static final int LE_DEVICE_ROLE_OCTETS = 1;
+ /** The {@link OobData#mLeTemporaryKey} length. (3.4.1) @hide */
+ @SystemApi
+ public static final int LE_TK_OCTETS = 16;
+ /** The {@link OobData#mLeAppearance} length. (3.4.1) @hide */
+ @SystemApi
+ public static final int LE_APPEARANCE_OCTETS = 2;
+ /** The {@link OobData#mLeFlags} length. (3.4.1) @hide */
+ @SystemApi
+ public static final int LE_DEVICE_FLAG_OCTETS = 1; // 1 octet to hold the 0-4 value.
+
+ // Le Roles
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(
+ prefix = { "LE_DEVICE_ROLE_" },
+ value = {
+ LE_DEVICE_ROLE_PERIPHERAL_ONLY,
+ LE_DEVICE_ROLE_CENTRAL_ONLY,
+ LE_DEVICE_ROLE_BOTH_PREFER_PERIPHERAL,
+ LE_DEVICE_ROLE_BOTH_PREFER_CENTRAL
+ }
+ )
+ public @interface LeRole {}
+
+ /** @hide */
+ @SystemApi
+ public static final int LE_DEVICE_ROLE_PERIPHERAL_ONLY = 0x00;
+ /** @hide */
+ @SystemApi
+ public static final int LE_DEVICE_ROLE_CENTRAL_ONLY = 0x01;
+ /** @hide */
+ @SystemApi
+ public static final int LE_DEVICE_ROLE_BOTH_PREFER_PERIPHERAL = 0x02;
+ /** @hide */
+ @SystemApi
+ public static final int LE_DEVICE_ROLE_BOTH_PREFER_CENTRAL = 0x03;
+
+ // Le Flags
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(
+ prefix = { "LE_FLAG_" },
+ value = {
+ LE_FLAG_LIMITED_DISCOVERY_MODE,
+ LE_FLAG_GENERAL_DISCOVERY_MODE,
+ LE_FLAG_BREDR_NOT_SUPPORTED,
+ LE_FLAG_SIMULTANEOUS_CONTROLLER,
+ LE_FLAG_SIMULTANEOUS_HOST
+ }
+ )
+ public @interface LeFlag {}
+
+ /** @hide */
+ @SystemApi
+ public static final int LE_FLAG_LIMITED_DISCOVERY_MODE = 0x00;
+ /** @hide */
+ @SystemApi
+ public static final int LE_FLAG_GENERAL_DISCOVERY_MODE = 0x01;
+ /** @hide */
+ @SystemApi
+ public static final int LE_FLAG_BREDR_NOT_SUPPORTED = 0x02;
+ /** @hide */
+ @SystemApi
+ public static final int LE_FLAG_SIMULTANEOUS_CONTROLLER = 0x03;
+ /** @hide */
+ @SystemApi
+ public static final int LE_FLAG_SIMULTANEOUS_HOST = 0x04;
+
+ /**
+ * Main creation method for creating a Classic version of {@link OobData}.
+ *
+ * <p>This object will allow the caller to call {@link ClassicBuilder#build()}
+ * to build the data object or add any option information to the builder.
+ *
+ * @param confirmationHash byte array consisting of {@link OobData#CONFIRMATION_OCTETS} octets
+ * of data. Data is derived from controller/host stack and is required for pairing OOB.
+ * @param classicLength byte array representing the length of data from 8-65535 across 2
+ * octets (0xXXXX).
+ * @param deviceAddressWithType byte array representing the Bluetooth Address of the device
+ * that owns the OOB data. (i.e. the originator) [6 octets]
+ *
+ * @return a Classic Builder instance with all the given data set or null.
+ *
+ * @throws IllegalArgumentException if any of the values fail to be set.
+ * @throws NullPointerException if any argument is null.
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ public static ClassicBuilder createClassicBuilder(@NonNull byte[] confirmationHash,
+ @NonNull byte[] classicLength, @NonNull byte[] deviceAddressWithType) {
+ return new ClassicBuilder(confirmationHash, classicLength, deviceAddressWithType);
+ }
+
+ /**
+ * Main creation method for creating a LE version of {@link OobData}.
+ *
+ * <p>This object will allow the caller to call {@link LeBuilder#build()}
+ * to build the data object or add any option information to the builder.
+ *
+ * @param deviceAddressWithType the LE device address plus the address type (7 octets);
+ * not null.
+ * @param leDeviceRole whether the device supports Peripheral, Central,
+ * Both including preference; not null. (1 octet)
+ * @param confirmationHash Array consisting of {@link OobData#CONFIRMATION_OCTETS} octets
+ * of data. Data is derived from controller/host stack and is
+ * required for pairing OOB.
+ *
+ * <p>Possible LE Device Role Values:
+ * 0x00 Only Peripheral supported
+ * 0x01 Only Central supported
+ * 0x02 Central & Peripheral supported; Peripheral Preferred
+ * 0x03 Only peripheral supported; Central Preferred
+ * 0x04 - 0xFF Reserved
+ *
+ * @return a LeBuilder instance with all the given data set or null.
+ *
+ * @throws IllegalArgumentException if any of the values fail to be set.
+ * @throws NullPointerException if any argument is null.
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ public static LeBuilder createLeBuilder(@NonNull byte[] confirmationHash,
+ @NonNull byte[] deviceAddressWithType, @LeRole int leDeviceRole) {
+ return new LeBuilder(confirmationHash, deviceAddressWithType, leDeviceRole);
+ }
+
+ /**
+ * Builds an {@link OobData} object and validates that the required combination
+ * of values are present to create the LE specific OobData type.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final class LeBuilder {
+
+ /**
+ * It is recommended that this Hash C is generated anew for each
+ * pairing.
+ *
+ * <p>It should be noted that on passive NFC this isn't possible as the data is static
+ * and immutable.
+ */
+ private byte[] mConfirmationHash = null;
+
+ /**
+ * Optional, but adds more validity to the pairing.
+ *
+ * <p>If not present a value of 0 is assumed.
+ */
+ private byte[] mRandomizerHash = new byte[] {
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ };
+
+ /**
+ * The Bluetooth Device user-friendly name presented over Bluetooth Technology.
+ *
+ * <p>This is the name that may be displayed to the device user as part of the UI.
+ */
+ private byte[] mDeviceName = null;
+
+ /**
+ * Sets the Bluetooth Device name to be used for UI purposes.
+ *
+ * <p>Optional attribute.
+ *
+ * @param deviceName byte array representing the name, may be 0 in length, not null.
+ *
+ * @return {@link OobData#ClassicBuilder}
+ *
+ * @throws NullPointerException if deviceName is null.
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ public LeBuilder setDeviceName(@NonNull byte[] deviceName) {
+ Preconditions.checkNotNull(deviceName);
+ this.mDeviceName = deviceName;
+ return this;
+ }
+
+ /**
+ * The Bluetooth Device Address is the address to which the OOB data belongs.
+ *
+ * <p>The length MUST be {@link OobData#DEVICE_ADDRESS_OCTETS} octets.
+ *
+ * <p> Address is encoded in Little Endian order.
+ *
+ * <p>e.g. 00:01:02:03:04:05 would be x05x04x03x02x01x00
+ */
+ private final byte[] mDeviceAddressWithType;
+
+ /**
+ * During an LE connection establishment, one must be in the Peripheral mode and the other
+ * in the Central role.
+ *
+ * <p>Possible Values:
+ * {@link LE_DEVICE_ROLE_PERIPHERAL_ONLY} Only Peripheral supported
+ * {@link LE_DEVICE_ROLE_CENTRAL_ONLY} Only Central supported
+ * {@link LE_DEVICE_ROLE_BOTH_PREFER_PERIPHERAL} Central & Peripheral supported;
+ * Peripheral Preferred
+ * {@link LE_DEVICE_ROLE_BOTH_PREFER_CENTRAL} Only peripheral supported; Central Preferred
+ * 0x04 - 0xFF Reserved
+ */
+ private final @LeRole int mLeDeviceRole;
+
+ /**
+ * Temporary key value from the Security Manager.
+ *
+ * <p> Must be {@link LE_TK_OCTETS} in size
+ */
+ private byte[] mLeTemporaryKey = null;
+
+ /**
+ * Defines the representation of the external appearance of the device.
+ *
+ * <p>For example, a mouse, remote control, or keyboard.
+ *
+ * <p>Used for visual on discovering device to represent icon/string/etc...
+ */
+ private byte[] mLeAppearance = null;
+
+ /**
+ * Contains which discoverable mode to use, BR/EDR support and capability.
+ *
+ * <p>Possible LE Flags:
+ * {@link LE_FLAG_LIMITED_DISCOVERY_MODE} LE Limited Discoverable Mode.
+ * {@link LE_FLAG_GENERAL_DISCOVERY_MODE} LE General Discoverable Mode.
+ * {@link LE_FLAG_BREDR_NOT_SUPPORTED} BR/EDR Not Supported. Bit 37 of
+ * LMP Feature Mask Definitions.
+ * {@link LE_FLAG_SIMULTANEOUS_CONTROLLER} Simultaneous LE and BR/EDR to
+ * Same Device Capable (Controller).
+ * Bit 49 of LMP Feature Mask Definitions.
+ * {@link LE_FLAG_SIMULTANEOUS_HOST} Simultaneous LE and BR/EDR to
+ * Same Device Capable (Host).
+ * Bit 55 of LMP Feature Mask Definitions.
+ * <b>0x05- 0x07 Reserved</b>
+ */
+ private @LeFlag int mLeFlags = LE_FLAG_GENERAL_DISCOVERY_MODE; // Invalid default
+
+ /**
+ * Constructing an OobData object for use with LE requires
+ * a LE Device Address and LE Device Role as well as the Confirmation
+ * and optionally, the Randomizer, however it is recommended to use.
+ *
+ * @param confirmationHash byte array consisting of {@link OobData#CONFIRMATION_OCTETS}
+ * octets of data. Data is derived from controller/host stack and is required for
+ * pairing OOB.
+ * @param deviceAddressWithType 7 bytes containing the 6 byte address with the 1 byte
+ * address type.
+ * @param leDeviceRole indicating device's role and preferences (Central or Peripheral)
+ *
+ * <p>Possible Values:
+ * {@link LE_DEVICE_ROLE_PERIPHERAL_ONLY} Only Peripheral supported
+ * {@link LE_DEVICE_ROLE_CENTRAL_ONLY} Only Central supported
+ * {@link LE_DEVICE_ROLE_BOTH_PREFER_PERIPHERAL} Central & Peripheral supported;
+ * Peripheral Preferred
+ * {@link LE_DEVICE_ROLE_BOTH_PREFER_CENTRAL} Only peripheral supported; Central Preferred
+ * 0x04 - 0xFF Reserved
+ *
+ * @throws IllegalArgumentException if deviceAddressWithType is not
+ * {@link LE_DEVICE_ADDRESS_OCTETS} octets
+ * @throws NullPointerException if any argument is null.
+ */
+ private LeBuilder(@NonNull byte[] confirmationHash, @NonNull byte[] deviceAddressWithType,
+ @LeRole int leDeviceRole) {
+ Preconditions.checkNotNull(confirmationHash);
+ Preconditions.checkNotNull(deviceAddressWithType);
+ if (confirmationHash.length != OobData.CONFIRMATION_OCTETS) {
+ throw new IllegalArgumentException("confirmationHash must be "
+ + OobData.CONFIRMATION_OCTETS + " octets in length.");
+ }
+ this.mConfirmationHash = confirmationHash;
+ if (deviceAddressWithType.length != OobData.DEVICE_ADDRESS_OCTETS) {
+ throw new IllegalArgumentException("confirmationHash must be "
+ + OobData.DEVICE_ADDRESS_OCTETS+ " octets in length.");
+ }
+ this.mDeviceAddressWithType = deviceAddressWithType;
+ if (leDeviceRole < LE_DEVICE_ROLE_PERIPHERAL_ONLY
+ || leDeviceRole > LE_DEVICE_ROLE_BOTH_PREFER_CENTRAL) {
+ throw new IllegalArgumentException("leDeviceRole must be a valid value.");
+ }
+ this.mLeDeviceRole = leDeviceRole;
+ }
+
+ /**
+ * Sets the Temporary Key value to be used by the LE Security Manager during
+ * LE pairing.
+ *
+ * @param leTemporaryKey byte array that shall be 16 bytes. Please see Bluetooth CSSv6,
+ * Part A 1.8 for a detailed description.
+ *
+ * @return {@link OobData#Builder}
+ *
+ * @throws IllegalArgumentException if the leTemporaryKey is an invalid format.
+ * @throws NullinterException if leTemporaryKey is null.
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ public LeBuilder setLeTemporaryKey(@NonNull byte[] leTemporaryKey) {
+ Preconditions.checkNotNull(leTemporaryKey);
+ if (leTemporaryKey.length != LE_TK_OCTETS) {
+ throw new IllegalArgumentException("leTemporaryKey must be "
+ + LE_TK_OCTETS + " octets in length.");
+ }
+ this.mLeTemporaryKey = leTemporaryKey;
+ return this;
+ }
+
+ /**
+ * @param randomizerHash byte array consisting of {@link OobData#RANDOMIZER_OCTETS} octets
+ * of data. Data is derived from controller/host stack and is required for pairing OOB.
+ * Also, randomizerHash may be all 0s or null in which case it becomes all 0s.
+ *
+ * @throws IllegalArgumentException if null or incorrect length randomizerHash was passed.
+ * @throws NullPointerException if randomizerHash is null.
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ public LeBuilder setRandomizerHash(@NonNull byte[] randomizerHash) {
+ Preconditions.checkNotNull(randomizerHash);
+ if (randomizerHash.length != OobData.RANDOMIZER_OCTETS) {
+ throw new IllegalArgumentException("randomizerHash must be "
+ + OobData.RANDOMIZER_OCTETS + " octets in length.");
+ }
+ this.mRandomizerHash = randomizerHash;
+ return this;
+ }
+
+ /**
+ * Sets the LE Flags necessary for the pairing scenario or discovery mode.
+ *
+ * @param leFlags enum value representing the 1 octet of data about discovery modes.
+ *
+ * <p>Possible LE Flags:
+ * {@link LE_FLAG_LIMITED_DISCOVERY_MODE} LE Limited Discoverable Mode.
+ * {@link LE_FLAG_GENERAL_DISCOVERY_MODE} LE General Discoverable Mode.
+ * {@link LE_FLAG_BREDR_NOT_SUPPORTED} BR/EDR Not Supported. Bit 37 of
+ * LMP Feature Mask Definitions.
+ * {@link LE_FLAG_SIMULTANEOUS_CONTROLLER} Simultaneous LE and BR/EDR to
+ * Same Device Capable (Controller) Bit 49 of LMP Feature Mask Definitions.
+ * {@link LE_FLAG_SIMULTANEOUS_HOST} Simultaneous LE and BR/EDR to
+ * Same Device Capable (Host).
+ * Bit 55 of LMP Feature Mask Definitions.
+ * 0x05- 0x07 Reserved
+ *
+ * @throws IllegalArgumentException for invalid flag
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ public LeBuilder setLeFlags(@LeFlag int leFlags) {
+ if (leFlags < LE_FLAG_LIMITED_DISCOVERY_MODE || leFlags > LE_FLAG_SIMULTANEOUS_HOST) {
+ throw new IllegalArgumentException("leFlags must be a valid value.");
+ }
+ this.mLeFlags = leFlags;
+ return this;
+ }
+
+ /**
+ * Validates and builds the {@link OobData} object for LE Security.
+ *
+ * @return {@link OobData} with given builder values
+ *
+ * @throws IllegalStateException if either of the 2 required fields were not set.
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ public OobData build() {
+ final OobData oob =
+ new OobData(this.mDeviceAddressWithType, this.mLeDeviceRole,
+ this.mConfirmationHash);
+
+ // If we have values, set them, otherwise use default
+ oob.mLeTemporaryKey =
+ (this.mLeTemporaryKey != null) ? this.mLeTemporaryKey : oob.mLeTemporaryKey;
+ oob.mLeAppearance = (this.mLeAppearance != null)
+ ? this.mLeAppearance : oob.mLeAppearance;
+ oob.mLeFlags = (this.mLeFlags != 0xF) ? this.mLeFlags : oob.mLeFlags;
+ oob.mDeviceName = (this.mDeviceName != null) ? this.mDeviceName : oob.mDeviceName;
+ oob.mRandomizerHash = this.mRandomizerHash;
+ return oob;
+ }
+ }
+
+ /**
+ * Builds an {@link OobData} object and validates that the required combination
+ * of values are present to create the Classic specific OobData type.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final class ClassicBuilder {
+ // Used by both Classic and LE
+ /**
+ * It is recommended that this Hash C is generated anew for each
+ * pairing.
+ *
+ * <p>It should be noted that on passive NFC this isn't possible as the data is static
+ * and immutable.
+ *
+ * @hide
+ */
+ private byte[] mConfirmationHash = null;
+
+ /**
+ * Optional, but adds more validity to the pairing.
+ *
+ * <p>If not present a value of 0 is assumed.
+ *
+ * @hide
+ */
+ private byte[] mRandomizerHash = new byte[] {
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ };
+
+ /**
+ * The Bluetooth Device user-friendly name presented over Bluetooth Technology.
+ *
+ * <p>This is the name that may be displayed to the device user as part of the UI.
+ *
+ * @hide
+ */
+ private byte[] mDeviceName = null;
+
+ /**
+ * This length value provides the absolute length of total OOB data block used for
+ * Bluetooth BR/EDR
+ *
+ * <p>OOB communication, which includes the length field itself and the Bluetooth
+ * Device Address.
+ *
+ * <p>The minimum length that may be represented in this field is 8.
+ *
+ * @hide
+ */
+ private final byte[] mClassicLength;
+
+ /**
+ * The Bluetooth Device Address is the address to which the OOB data belongs.
+ *
+ * <p>The length MUST be {@link OobData#DEVICE_ADDRESS_OCTETS} octets.
+ *
+ * <p> Address is encoded in Little Endian order.
+ *
+ * <p>e.g. 00:01:02:03:04:05 would be x05x04x03x02x01x00
+ *
+ * @hide
+ */
+ private final byte[] mDeviceAddressWithType;
+
+ /**
+ * Class of Device information is to be used to provide a graphical representation
+ * to the user as part of UI involving operations.
+ *
+ * <p>This is not to be used to determine a particular service can be used.
+ *
+ * <p>The length MUST be {@link OobData#CLASS_OF_DEVICE_OCTETS} octets.
+ *
+ * @hide
+ */
+ private byte[] mClassOfDevice = null;
+
+ /**
+ * @param confirmationHash byte array consisting of {@link OobData#CONFIRMATION_OCTETS}
+ * octets of data. Data is derived from controller/host stack and is required for pairing
+ * OOB.
+ * @param randomizerHash byte array consisting of {@link OobData#RANDOMIZER_OCTETS} octets
+ * of data. Data is derived from controller/host stack and is required
+ * for pairing OOB. Also, randomizerHash may be all 0s or null in which case
+ * it becomes all 0s.
+ * @param classicLength byte array representing the length of data from 8-65535 across 2
+ * octets (0xXXXX). Inclusive of this value in the length.
+ * @param deviceAddressWithType byte array representing the Bluetooth Address of the device
+ * that owns the OOB data. (i.e. the originator) [7 octets] this includes the Address Type
+ * as the last octet.
+ *
+ * @throws IllegalArgumentException if any value is not the correct length
+ * @throws NullPointerException if anything passed is null
+ *
+ * @hide
+ */
+ private ClassicBuilder(@NonNull byte[] confirmationHash, @NonNull byte[] classicLength,
+ @NonNull byte[] deviceAddressWithType) {
+ Preconditions.checkNotNull(confirmationHash);
+ Preconditions.checkNotNull(classicLength);
+ Preconditions.checkNotNull(deviceAddressWithType);
+ if (confirmationHash.length != OobData.CONFIRMATION_OCTETS) {
+ throw new IllegalArgumentException("confirmationHash must be "
+ + OobData.CONFIRMATION_OCTETS + " octets in length.");
+ }
+ this.mConfirmationHash = confirmationHash;
+ if (classicLength.length != OOB_LENGTH_OCTETS) {
+ throw new IllegalArgumentException("classicLength must be "
+ + OOB_LENGTH_OCTETS + " octets in length.");
+ }
+ this.mClassicLength = classicLength;
+ if (deviceAddressWithType.length != DEVICE_ADDRESS_OCTETS) {
+ throw new IllegalArgumentException("deviceAddressWithType must be "
+ + DEVICE_ADDRESS_OCTETS + " octets in length.");
+ }
+ this.mDeviceAddressWithType = deviceAddressWithType;
+ }
- public byte[] getLeBluetoothDeviceAddress() {
- return mLeBluetoothDeviceAddress;
+ /**
+ * @param randomizerHash byte array consisting of {@link OobData#RANDOMIZER_OCTETS} octets
+ * of data. Data is derived from controller/host stack and is required for pairing OOB.
+ * Also, randomizerHash may be all 0s or null in which case it becomes all 0s.
+ *
+ * @throws IllegalArgumentException if null or incorrect length randomizerHash was passed.
+ * @throws NullPointerException if randomizerHash is null.
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ public ClassicBuilder setRandomizerHash(@NonNull byte[] randomizerHash) {
+ Preconditions.checkNotNull(randomizerHash);
+ if (randomizerHash.length != OobData.RANDOMIZER_OCTETS) {
+ throw new IllegalArgumentException("randomizerHash must be "
+ + OobData.RANDOMIZER_OCTETS + " octets in length.");
+ }
+ this.mRandomizerHash = randomizerHash;
+ return this;
+ }
+
+ /**
+ * Sets the Bluetooth Device name to be used for UI purposes.
+ *
+ * <p>Optional attribute.
+ *
+ * @param deviceName byte array representing the name, may be 0 in length, not null.
+ *
+ * @return {@link OobData#ClassicBuilder}
+ *
+ * @throws NullPointerException if deviceName is null
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ public ClassicBuilder setDeviceName(@NonNull byte[] deviceName) {
+ Preconditions.checkNotNull(deviceName);
+ this.mDeviceName = deviceName;
+ return this;
+ }
+
+ /**
+ * Sets the Bluetooth Class of Device; used for UI purposes only.
+ *
+ * <p>Not an indicator of available services!
+ *
+ * <p>Optional attribute.
+ *
+ * @param classOfDevice byte array of {@link OobData#CLASS_OF_DEVICE_OCTETS} octets.
+ *
+ * @return {@link OobData#ClassicBuilder}
+ *
+ * @throws IllegalArgumentException if length is not equal to
+ * {@link OobData#CLASS_OF_DEVICE_OCTETS} octets.
+ * @throws NullPointerException if classOfDevice is null.
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ public ClassicBuilder setClassOfDevice(@NonNull byte[] classOfDevice) {
+ Preconditions.checkNotNull(classOfDevice);
+ if (classOfDevice.length != OobData.CLASS_OF_DEVICE_OCTETS) {
+ throw new IllegalArgumentException("classOfDevice must be "
+ + OobData.CLASS_OF_DEVICE_OCTETS + " octets in length.");
+ }
+ this.mClassOfDevice = classOfDevice;
+ return this;
+ }
+
+ /**
+ * Validates and builds the {@link OobDat object for Classic Security.
+ *
+ * @return {@link OobData} with previously given builder values.
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ public OobData build() {
+ final OobData oob =
+ new OobData(this.mClassicLength, this.mDeviceAddressWithType,
+ this.mConfirmationHash);
+ // If we have values, set them, otherwise use default
+ oob.mDeviceName = (this.mDeviceName != null) ? this.mDeviceName : oob.mDeviceName;
+ oob.mClassOfDevice = (this.mClassOfDevice != null)
+ ? this.mClassOfDevice : oob.mClassOfDevice;
+ oob.mRandomizerHash = this.mRandomizerHash;
+ return oob;
+ }
+ }
+
+ // Members (Defaults for Optionals must be set or Parceling fails on NPE)
+ // Both
+ private final byte[] mDeviceAddressWithType;
+ private final byte[] mConfirmationHash;
+ private byte[] mRandomizerHash = new byte[] {
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ };
+ // Default the name to "Bluetooth Device"
+ private byte[] mDeviceName = new byte[] {
+ // Bluetooth
+ 0x42, 0x6c, 0x75, 0x65, 0x74, 0x6f, 0x6f, 0x74, 0x68,
+ // <space>Device
+ 0x20, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65
+ };
+
+ // Classic
+ private final byte[] mClassicLength;
+ private byte[] mClassOfDevice = new byte[CLASS_OF_DEVICE_OCTETS];
+
+ // LE
+ private final @LeRole int mLeDeviceRole;
+ private byte[] mLeTemporaryKey = new byte[LE_TK_OCTETS];
+ private byte[] mLeAppearance = new byte[LE_APPEARANCE_OCTETS];
+ private @LeFlag int mLeFlags = LE_FLAG_LIMITED_DISCOVERY_MODE;
+
+ /**
+ * @return byte array representing the MAC address of a bluetooth device.
+ * The Address is 6 octets long with a 1 octet address type associated with the address.
+ *
+ * <p>For classic this will be 6 byte address plus the default of PUBLIC_ADDRESS Address Type.
+ * For LE there are more choices for Address Type.
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ public byte[] getDeviceAddressWithType() {
+ return mDeviceAddressWithType;
+ }
+
+ /**
+ * @return byte array representing the confirmationHash value
+ * which is used to confirm the identity to the controller.
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ public byte[] getConfirmationHash() {
+ return mConfirmationHash;
}
/**
- * Sets the LE Bluetooth Device Address value to be used during LE pairing.
- * The value shall be 7 bytes. Please see Bluetooth CSSv6, Part A 1.16 for
- * a detailed description.
+ * @return byte array representing the randomizerHash value
+ * which is used to verify the identity of the controller.
+ *
+ * @hide
*/
- public void setLeBluetoothDeviceAddress(byte[] leBluetoothDeviceAddress) {
- mLeBluetoothDeviceAddress = leBluetoothDeviceAddress;
+ @NonNull
+ @SystemApi
+ public byte[] getRandomizerHash() {
+ return mRandomizerHash;
}
- public byte[] getSecurityManagerTk() {
- return mSecurityManagerTk;
+ /**
+ * @return Device Name used for displaying name in UI.
+ *
+ * <p>Also, this will be populated with the LE Local Name if the data is for LE.
+ *
+ * @hide
+ */
+ @Nullable
+ @SystemApi
+ public byte[] getDeviceName() {
+ return mDeviceName;
+ }
+
+ /**
+ * @return byte array representing the oob data length which is the length
+ * of all of the data including these octets.
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ public byte[] getClassicLength() {
+ return mClassicLength;
+ }
+
+ /**
+ * @return byte array representing the class of device for UI display.
+ *
+ * <p>Does not indicate services available; for display only.
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ public byte[] getClassOfDevice() {
+ return mClassOfDevice;
}
/**
- * Sets the Temporary Key value to be used by the LE Security Manager during
- * LE pairing. The value shall be 16 bytes. Please see Bluetooth CSSv6,
- * Part A 1.8 for a detailed description.
+ * @return Temporary Key used for LE pairing.
+ *
+ * @hide
*/
- public void setSecurityManagerTk(byte[] securityManagerTk) {
- mSecurityManagerTk = securityManagerTk;
+ @Nullable
+ @SystemApi
+ public byte[] getLeTemporaryKey() {
+ return mLeTemporaryKey;
}
- public byte[] getLeSecureConnectionsConfirmation() {
- return mLeSecureConnectionsConfirmation;
+ /**
+ * @return Appearance used for LE pairing. For use in UI situations
+ * when determining what sort of icons or text to display regarding
+ * the device.
+ *
+ * @hide
+ */
+ @Nullable
+ @SystemApi
+ public byte[] getLeAppearance() {
+ return mLeTemporaryKey;
}
- public void setLeSecureConnectionsConfirmation(byte[] leSecureConnectionsConfirmation) {
- mLeSecureConnectionsConfirmation = leSecureConnectionsConfirmation;
+ /**
+ * @return Flags used to determing discoverable mode to use, BR/EDR Support, and Capability.
+ *
+ * <p>Possible LE Flags:
+ * {@link LE_FLAG_LIMITED_DISCOVERY_MODE} LE Limited Discoverable Mode.
+ * {@link LE_FLAG_GENERAL_DISCOVERY_MODE} LE General Discoverable Mode.
+ * {@link LE_FLAG_BREDR_NOT_SUPPORTED} BR/EDR Not Supported. Bit 37 of
+ * LMP Feature Mask Definitions.
+ * {@link LE_FLAG_SIMULTANEOUS_CONTROLLER} Simultaneous LE and BR/EDR to
+ * Same Device Capable (Controller).
+ * Bit 49 of LMP Feature Mask Definitions.
+ * {@link LE_FLAG_SIMULTANEOUS_HOST} Simultaneous LE and BR/EDR to
+ * Same Device Capable (Host).
+ * Bit 55 of LMP Feature Mask Definitions.
+ * <b>0x05- 0x07 Reserved</b>
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ @LeFlag
+ public int getLeFlags() {
+ return mLeFlags;
}
- public byte[] getLeSecureConnectionsRandom() {
- return mLeSecureConnectionsRandom;
+ /**
+ * @return the supported and preferred roles of the LE device.
+ *
+ * <p>Possible Values:
+ * {@link LE_DEVICE_ROLE_PERIPHERAL_ONLY} Only Peripheral supported
+ * {@link LE_DEVICE_ROLE_CENTRAL_ONLY} Only Central supported
+ * {@link LE_DEVICE_ROLE_BOTH_PREFER_PERIPHERAL} Central & Peripheral supported;
+ * Peripheral Preferred
+ * {@link LE_DEVICE_ROLE_BOTH_PREFER_CENTRAL} Only peripheral supported; Central Preferred
+ * 0x04 - 0xFF Reserved
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ @LeRole
+ public int getLeDeviceRole() {
+ return mLeDeviceRole;
}
- public void setLeSecureConnectionsRandom(byte[] leSecureConnectionsRandom) {
- mLeSecureConnectionsRandom = leSecureConnectionsRandom;
+ /**
+ * Classic Security Constructor
+ */
+ private OobData(@NonNull byte[] classicLength, @NonNull byte[] deviceAddressWithType,
+ @NonNull byte[] confirmationHash) {
+ mClassicLength = classicLength;
+ mDeviceAddressWithType = deviceAddressWithType;
+ mConfirmationHash = confirmationHash;
+ mLeDeviceRole = -1; // Satisfy final
}
- public OobData() {
+ /**
+ * LE Security Constructor
+ */
+ private OobData(@NonNull byte[] deviceAddressWithType, @LeRole int leDeviceRole,
+ @NonNull byte[] confirmationHash) {
+ mDeviceAddressWithType = deviceAddressWithType;
+ mLeDeviceRole = leDeviceRole;
+ mConfirmationHash = confirmationHash;
+ mClassicLength = new byte[OOB_LENGTH_OCTETS]; // Satisfy final
}
private OobData(Parcel in) {
- mLeBluetoothDeviceAddress = in.createByteArray();
- mSecurityManagerTk = in.createByteArray();
- mLeSecureConnectionsConfirmation = in.createByteArray();
- mLeSecureConnectionsRandom = in.createByteArray();
+ // Both
+ mDeviceAddressWithType = in.createByteArray();
+ mConfirmationHash = in.createByteArray();
+ mRandomizerHash = in.createByteArray();
+ mDeviceName = in.createByteArray();
+
+ // Classic
+ mClassicLength = in.createByteArray();
+ mClassOfDevice = in.createByteArray();
+
+ // LE
+ mLeDeviceRole = in.readInt();
+ mLeTemporaryKey = in.createByteArray();
+ mLeAppearance = in.createByteArray();
+ mLeFlags = in.readInt();
}
+ /**
+ * @hide
+ */
@Override
public int describeContents() {
return 0;
}
+ /**
+ * @hide
+ */
@Override
- public void writeToParcel(Parcel out, int flags) {
- out.writeByteArray(mLeBluetoothDeviceAddress);
- out.writeByteArray(mSecurityManagerTk);
- out.writeByteArray(mLeSecureConnectionsConfirmation);
- out.writeByteArray(mLeSecureConnectionsRandom);
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ // Both
+ // Required
+ out.writeByteArray(mDeviceAddressWithType);
+ // Required
+ out.writeByteArray(mConfirmationHash);
+ // Optional
+ out.writeByteArray(mRandomizerHash);
+ // Optional
+ out.writeByteArray(mDeviceName);
+
+ // Classic
+ // Required
+ out.writeByteArray(mClassicLength);
+ // Optional
+ out.writeByteArray(mClassOfDevice);
+
+ // LE
+ // Required
+ out.writeInt(mLeDeviceRole);
+ // Required
+ out.writeByteArray(mLeTemporaryKey);
+ // Optional
+ out.writeByteArray(mLeAppearance);
+ // Optional
+ out.writeInt(mLeFlags);
}
+ // For Parcelable
public static final @android.annotation.NonNull Parcelable.Creator<OobData> CREATOR =
new Parcelable.Creator<OobData>() {
public OobData createFromParcel(Parcel in) {
@@ -108,4 +969,47 @@ public class OobData implements Parcelable {
return new OobData[size];
}
};
+
+ /**
+ * @return a {@link String} representation of the OobData object.
+ *
+ * @hide
+ */
+ @Override
+ @NonNull
+ public String toString() {
+ return "OobData: \n\t"
+ // Both
+ + "Device Address With Type: " + toHexString(mDeviceAddressWithType) + "\n\t"
+ + "Confirmation: " + toHexString(mConfirmationHash) + "\n\t"
+ + "Randomizer: " + toHexString(mRandomizerHash) + "\n\t"
+ + "Device Name: " + toHexString(mDeviceName) + "\n\t"
+ // Classic
+ + "OobData Length: " + toHexString(mClassicLength) + "\n\t"
+ + "Class of Device: " + toHexString(mClassOfDevice) + "\n\t"
+ // LE
+ + "LE Device Role: " + toHexString(mLeDeviceRole) + "\n\t"
+ + "LE Temporary Key: " + toHexString(mLeTemporaryKey) + "\n\t"
+ + "LE Appearance: " + toHexString(mLeAppearance) + "\n\t"
+ + "LE Flags: " + toHexString(mLeFlags) + "\n\t";
+ }
+
+ @NonNull
+ private String toHexString(@NonNull int b) {
+ return toHexString(new byte[] {(byte) b});
+ }
+
+ @NonNull
+ private String toHexString(@NonNull byte b) {
+ return toHexString(new byte[] {b});
+ }
+
+ @NonNull
+ private String toHexString(@NonNull byte[] array) {
+ StringBuilder builder = new StringBuilder(array.length * 2);
+ for (byte b: array) {
+ builder.append(String.format("%02x", b));
+ }
+ return builder.toString();
+ }
}
diff --git a/framework/java/android/bluetooth/SdpDipRecord.java b/framework/java/android/bluetooth/SdpDipRecord.java
new file mode 100644
index 0000000000..84b0eef059
--- /dev/null
+++ b/framework/java/android/bluetooth/SdpDipRecord.java
@@ -0,0 +1,104 @@
+/*
+* Copyright (C) 2015 Samsung System LSI
+* 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 java.util.Arrays;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Data representation of a Object Push Profile Server side SDP record.
+ */
+/** @hide */
+public class SdpDipRecord implements Parcelable {
+ private final int mSpecificationId;
+ private final int mVendorId;
+ private final int mVendorIdSource;
+ private final int mProductId;
+ private final int mVersion;
+ private final boolean mPrimaryRecord;
+
+ public SdpDipRecord(int specificationId,
+ int vendorId, int vendorIdSource,
+ int productId, int version,
+ boolean primaryRecord) {
+ super();
+ this.mSpecificationId = specificationId;
+ this.mVendorId = vendorId;
+ this.mVendorIdSource = vendorIdSource;
+ this.mProductId = productId;
+ this.mVersion = version;
+ this.mPrimaryRecord = primaryRecord;
+ }
+
+ public SdpDipRecord(Parcel in) {
+ this.mSpecificationId = in.readInt();
+ this.mVendorId = in.readInt();
+ this.mVendorIdSource = in.readInt();
+ this.mProductId = in.readInt();
+ this.mVersion = in.readInt();
+ this.mPrimaryRecord = in.readBoolean();
+ }
+
+ public int getSpecificationId() {
+ return mSpecificationId;
+ }
+
+ public int getVendorId() {
+ return mVendorId;
+ }
+
+ public int getVendorIdSource() {
+ return mVendorIdSource;
+ }
+
+ public int getProductId() {
+ return mProductId;
+ }
+
+ public int getVersion() {
+ return mVersion;
+ }
+
+ public boolean getPrimaryRecord() {
+ return mPrimaryRecord;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mSpecificationId);
+ dest.writeInt(mVendorId);
+ dest.writeInt(mVendorIdSource);
+ dest.writeInt(mProductId);
+ dest.writeInt(mVersion);
+ dest.writeBoolean(mPrimaryRecord);
+ }
+
+ @Override
+ public int describeContents() {
+ /* No special objects */
+ return 0;
+ }
+
+ public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
+ public SdpDipRecord createFromParcel(Parcel in) {
+ return new SdpDipRecord(in);
+ }
+ public SdpDipRecord[] newArray(int size) {
+ return new SdpDipRecord[size];
+ }
+ };
+}
diff --git a/framework/java/android/bluetooth/le/AdvertiseData.java b/framework/java/android/bluetooth/le/AdvertiseData.java
index 5fd8258376..fa7ac2b27c 100644
--- a/framework/java/android/bluetooth/le/AdvertiseData.java
+++ b/framework/java/android/bluetooth/le/AdvertiseData.java
@@ -16,6 +16,7 @@
package android.bluetooth.le;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Parcel;
import android.os.ParcelUuid;
@@ -43,17 +44,22 @@ public final class AdvertiseData implements Parcelable {
@Nullable
private final List<ParcelUuid> mServiceUuids;
+ @NonNull
+ private final List<ParcelUuid> mServiceSolicitationUuids;
+
private final SparseArray<byte[]> mManufacturerSpecificData;
private final Map<ParcelUuid, byte[]> mServiceData;
private final boolean mIncludeTxPowerLevel;
private final boolean mIncludeDeviceName;
private AdvertiseData(List<ParcelUuid> serviceUuids,
+ List<ParcelUuid> serviceSolicitationUuids,
SparseArray<byte[]> manufacturerData,
Map<ParcelUuid, byte[]> serviceData,
boolean includeTxPowerLevel,
boolean includeDeviceName) {
mServiceUuids = serviceUuids;
+ mServiceSolicitationUuids = serviceSolicitationUuids;
mManufacturerSpecificData = manufacturerData;
mServiceData = serviceData;
mIncludeTxPowerLevel = includeTxPowerLevel;
@@ -69,6 +75,14 @@ public final class AdvertiseData implements Parcelable {
}
/**
+ * Returns a list of service solicitation UUIDs within the advertisement that we invite to connect.
+ */
+ @NonNull
+ public List<ParcelUuid> getServiceSolicitationUuids() {
+ return mServiceSolicitationUuids;
+ }
+
+ /**
* Returns an array of manufacturer Id and the corresponding manufacturer specific data. The
* manufacturer id is a non-negative number assigned by Bluetooth SIG.
*/
@@ -102,8 +116,8 @@ public final class AdvertiseData implements Parcelable {
*/
@Override
public int hashCode() {
- return Objects.hash(mServiceUuids, mManufacturerSpecificData, mServiceData,
- mIncludeDeviceName, mIncludeTxPowerLevel);
+ return Objects.hash(mServiceUuids, mServiceSolicitationUuids, mManufacturerSpecificData,
+ mServiceData, mIncludeDeviceName, mIncludeTxPowerLevel);
}
/**
@@ -119,6 +133,7 @@ public final class AdvertiseData implements Parcelable {
}
AdvertiseData other = (AdvertiseData) obj;
return Objects.equals(mServiceUuids, other.mServiceUuids)
+ && Objects.equals(mServiceSolicitationUuids, other.mServiceSolicitationUuids)
&& BluetoothLeUtils.equals(mManufacturerSpecificData,
other.mManufacturerSpecificData)
&& BluetoothLeUtils.equals(mServiceData, other.mServiceData)
@@ -128,7 +143,8 @@ public final class AdvertiseData implements Parcelable {
@Override
public String toString() {
- return "AdvertiseData [mServiceUuids=" + mServiceUuids + ", mManufacturerSpecificData="
+ return "AdvertiseData [mServiceUuids=" + mServiceUuids + ", mServiceSolicitationUuids="
+ + mServiceSolicitationUuids + ", mManufacturerSpecificData="
+ BluetoothLeUtils.toString(mManufacturerSpecificData) + ", mServiceData="
+ BluetoothLeUtils.toString(mServiceData)
+ ", mIncludeTxPowerLevel=" + mIncludeTxPowerLevel + ", mIncludeDeviceName="
@@ -143,6 +159,8 @@ public final class AdvertiseData implements Parcelable {
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeTypedArray(mServiceUuids.toArray(new ParcelUuid[mServiceUuids.size()]), flags);
+ dest.writeTypedArray(mServiceSolicitationUuids.toArray(
+ new ParcelUuid[mServiceSolicitationUuids.size()]), flags);
// mManufacturerSpecificData could not be null.
dest.writeInt(mManufacturerSpecificData.size());
@@ -174,6 +192,11 @@ public final class AdvertiseData implements Parcelable {
builder.addServiceUuid(uuid);
}
+ ArrayList<ParcelUuid> solicitationUuids = in.createTypedArrayList(ParcelUuid.CREATOR);
+ for (ParcelUuid uuid : solicitationUuids) {
+ builder.addServiceSolicitationUuid(uuid);
+ }
+
int manufacturerSize = in.readInt();
for (int i = 0; i < manufacturerSize; ++i) {
int manufacturerId = in.readInt();
@@ -198,6 +221,8 @@ public final class AdvertiseData implements Parcelable {
public static final class Builder {
@Nullable
private List<ParcelUuid> mServiceUuids = new ArrayList<ParcelUuid>();
+ @NonNull
+ private List<ParcelUuid> mServiceSolicitationUuids = new ArrayList<ParcelUuid>();
private SparseArray<byte[]> mManufacturerSpecificData = new SparseArray<byte[]>();
private Map<ParcelUuid, byte[]> mServiceData = new ArrayMap<ParcelUuid, byte[]>();
private boolean mIncludeTxPowerLevel;
@@ -207,17 +232,31 @@ public final class AdvertiseData implements Parcelable {
* Add a service UUID to advertise data.
*
* @param serviceUuid A service UUID to be advertised.
- * @throws IllegalArgumentException If the {@code serviceUuids} are null.
+ * @throws IllegalArgumentException If the {@code serviceUuid} is null.
*/
public Builder addServiceUuid(ParcelUuid serviceUuid) {
if (serviceUuid == null) {
- throw new IllegalArgumentException("serivceUuids are null");
+ throw new IllegalArgumentException("serviceUuid is null");
}
mServiceUuids.add(serviceUuid);
return this;
}
/**
+ * Add a service solicitation UUID to advertise data.
+ *
+ * @param serviceSolicitationUuid A service solicitation UUID to be advertised.
+ * @throws IllegalArgumentException If the {@code serviceSolicitationUuid} is null.
+ */
+ @NonNull
+ public Builder addServiceSolicitationUuid(@NonNull ParcelUuid serviceSolicitationUuid) {
+ if (serviceSolicitationUuid == null) {
+ throw new IllegalArgumentException("serviceSolicitationUuid is null");
+ }
+ mServiceSolicitationUuids.add(serviceSolicitationUuid);
+ return this;
+ }
+ /**
* Add service data to advertise data.
*
* @param serviceDataUuid 16-bit UUID of the service the data is associated with
@@ -279,8 +318,9 @@ public final class AdvertiseData implements Parcelable {
* Build the {@link AdvertiseData}.
*/
public AdvertiseData build() {
- return new AdvertiseData(mServiceUuids, mManufacturerSpecificData, mServiceData,
- mIncludeTxPowerLevel, mIncludeDeviceName);
+ return new AdvertiseData(mServiceUuids, mServiceSolicitationUuids,
+ mManufacturerSpecificData, mServiceData, mIncludeTxPowerLevel,
+ mIncludeDeviceName);
}
}
}
diff --git a/framework/java/android/bluetooth/le/BluetoothLeAdvertiser.java b/framework/java/android/bluetooth/le/BluetoothLeAdvertiser.java
index 13c5ff6909..5f166f4a41 100644
--- a/framework/java/android/bluetooth/le/BluetoothLeAdvertiser.java
+++ b/framework/java/android/bluetooth/le/BluetoothLeAdvertiser.java
@@ -507,6 +507,33 @@ public final class BluetoothLeAdvertiser {
+ num128BitUuids * BluetoothUuid.UUID_BYTES_128_BIT;
}
}
+ if (data.getServiceSolicitationUuids() != null) {
+ int num16BitUuids = 0;
+ int num32BitUuids = 0;
+ int num128BitUuids = 0;
+ for (ParcelUuid uuid : data.getServiceSolicitationUuids()) {
+ if (BluetoothUuid.is16BitUuid(uuid)) {
+ ++num16BitUuids;
+ } else if (BluetoothUuid.is32BitUuid(uuid)) {
+ ++num32BitUuids;
+ } else {
+ ++num128BitUuids;
+ }
+ }
+ // 16 bit service uuids are grouped into one field when doing advertising.
+ if (num16BitUuids != 0) {
+ size += OVERHEAD_BYTES_PER_FIELD + num16BitUuids * BluetoothUuid.UUID_BYTES_16_BIT;
+ }
+ // 32 bit service uuids are grouped into one field when doing advertising.
+ if (num32BitUuids != 0) {
+ size += OVERHEAD_BYTES_PER_FIELD + num32BitUuids * BluetoothUuid.UUID_BYTES_32_BIT;
+ }
+ // 128 bit service uuids are grouped into one field when doing advertising.
+ if (num128BitUuids != 0) {
+ size += OVERHEAD_BYTES_PER_FIELD
+ + num128BitUuids * BluetoothUuid.UUID_BYTES_128_BIT;
+ }
+ }
for (ParcelUuid uuid : data.getServiceData().keySet()) {
int uuidLen = BluetoothUuid.uuidToBytes(uuid).length;
size += OVERHEAD_BYTES_PER_FIELD + uuidLen
diff --git a/framework/java/android/bluetooth/le/BluetoothLeScanner.java b/framework/java/android/bluetooth/le/BluetoothLeScanner.java
index 9a17346334..2888fbd8a3 100644
--- a/framework/java/android/bluetooth/le/BluetoothLeScanner.java
+++ b/framework/java/android/bluetooth/le/BluetoothLeScanner.java
@@ -110,8 +110,9 @@ public final class BluetoothLeScanner {
* off to save power. Scanning is resumed when screen is turned on again. To avoid this, use
* {@link #startScan(List, ScanSettings, ScanCallback)} with desired {@link ScanFilter}.
* <p>
- * An app must hold
- * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION} or
+ * An app must have
+ * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION} permission
+ * in order to get results. An App targeting Android Q or later must have
* {@link android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION} permission
* in order to get results.
*
@@ -129,8 +130,9 @@ public final class BluetoothLeScanner {
* resumed when screen is turned on again. To avoid this, do filetered scanning by
* using proper {@link ScanFilter}.
* <p>
- * An app must hold
- * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION} or
+ * An app must have
+ * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION} permission
+ * in order to get results. An App targeting Android Q or later must have
* {@link android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION} permission
* in order to get results.
*
@@ -150,8 +152,9 @@ public final class BluetoothLeScanner {
* the PendingIntent. Use this method of scanning if your process is not always running and it
* should be started when scan results are available.
* <p>
- * An app must hold
- * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION} or
+ * An app must have
+ * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION} permission
+ * in order to get results. An App targeting Android Q or later must have
* {@link android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION} permission
* in order to get results.
* <p>
diff --git a/framework/java/android/bluetooth/le/OWNERS b/framework/java/android/bluetooth/le/OWNERS
new file mode 100644
index 0000000000..3523ee0640
--- /dev/null
+++ b/framework/java/android/bluetooth/le/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 27441
+
+zachoverflow@google.com
+siyuanh@google.com
diff --git a/framework/java/android/bluetooth/le/PeriodicAdvertisingParameters.java b/framework/java/android/bluetooth/le/PeriodicAdvertisingParameters.java
index e3a130c4b4..4e64dbed70 100644
--- a/framework/java/android/bluetooth/le/PeriodicAdvertisingParameters.java
+++ b/framework/java/android/bluetooth/le/PeriodicAdvertisingParameters.java
@@ -22,7 +22,7 @@ import android.os.Parcelable;
/**
* The {@link PeriodicAdvertisingParameters} provide a way to adjust periodic
* advertising preferences for each Bluetooth LE advertising set. Use {@link
- * AdvertisingSetParameters.Builder} to create an instance of this class.
+ * PeriodicAdvertisingParameters.Builder} to create an instance of this class.
*/
public final class PeriodicAdvertisingParameters implements Parcelable {
diff --git a/framework/java/android/bluetooth/le/ScanFilter.java b/framework/java/android/bluetooth/le/ScanFilter.java
index 7511fd051e..3c20dcac8c 100644
--- a/framework/java/android/bluetooth/le/ScanFilter.java
+++ b/framework/java/android/bluetooth/le/ScanFilter.java
@@ -16,15 +16,19 @@
package android.bluetooth.le;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothDevice.AddressType;
import android.os.Parcel;
import android.os.ParcelUuid;
import android.os.Parcelable;
import com.android.internal.util.BitUtils;
+import com.android.internal.util.Preconditions;
import java.util.Arrays;
import java.util.List;
@@ -53,6 +57,11 @@ public final class ScanFilter implements Parcelable {
@Nullable
private final String mDeviceAddress;
+ private final @AddressType int mAddressType;
+
+ @Nullable
+ private final byte[] mIrk;
+
@Nullable
private final ParcelUuid mServiceUuid;
@Nullable
@@ -79,12 +88,12 @@ public final class ScanFilter implements Parcelable {
/** @hide */
public static final ScanFilter EMPTY = new ScanFilter.Builder().build();
-
private ScanFilter(String name, String deviceAddress, ParcelUuid uuid,
ParcelUuid uuidMask, ParcelUuid solicitationUuid,
ParcelUuid solicitationUuidMask, ParcelUuid serviceDataUuid,
byte[] serviceData, byte[] serviceDataMask,
- int manufacturerId, byte[] manufacturerData, byte[] manufacturerDataMask) {
+ int manufacturerId, byte[] manufacturerData, byte[] manufacturerDataMask,
+ @AddressType int addressType, @Nullable byte[] irk) {
mDeviceName = name;
mServiceUuid = uuid;
mServiceUuidMask = uuidMask;
@@ -97,6 +106,8 @@ public final class ScanFilter implements Parcelable {
mManufacturerId = manufacturerId;
mManufacturerData = manufacturerData;
mManufacturerDataMask = manufacturerDataMask;
+ mAddressType = addressType;
+ mIrk = irk;
}
@Override
@@ -280,6 +291,23 @@ public final class ScanFilter implements Parcelable {
return mDeviceAddress;
}
+ /**
+ * @hide
+ */
+ @SystemApi
+ public @AddressType int getAddressType() {
+ return mAddressType;
+ }
+
+ /**
+ * @hide
+ */
+ @SystemApi
+ @Nullable
+ public byte[] getIrk() {
+ return mIrk;
+ }
+
@Nullable
public byte[] getServiceData() {
return mServiceData;
@@ -516,8 +544,16 @@ public final class ScanFilter implements Parcelable {
*/
public static final class Builder {
+ /**
+ * @hide
+ */
+ @SystemApi
+ public static final int LEN_IRK_OCTETS = 16;
+
private String mDeviceName;
private String mDeviceAddress;
+ private @AddressType int mAddressType = BluetoothDevice.ADDRESS_TYPE_PUBLIC;
+ private byte[] mIrk;
private ParcelUuid mServiceUuid;
private ParcelUuid mUuidMask;
@@ -546,14 +582,130 @@ public final class ScanFilter implements Parcelable {
*
* @param deviceAddress The device Bluetooth address for the filter. It needs to be in the
* format of "01:02:03:AB:CD:EF". The device address can be validated using {@link
- * BluetoothAdapter#checkBluetoothAddress}.
+ * BluetoothAdapter#checkBluetoothAddress}. The @AddressType is defaulted to {@link
+ * BluetoothDevice#ADDRESS_TYPE_PUBLIC}
* @throws IllegalArgumentException If the {@code deviceAddress} is invalid.
*/
public Builder setDeviceAddress(String deviceAddress) {
- if (deviceAddress != null && !BluetoothAdapter.checkBluetoothAddress(deviceAddress)) {
+ return setDeviceAddress(deviceAddress, BluetoothDevice.ADDRESS_TYPE_PUBLIC);
+ }
+
+ /**
+ * Set filter on Address with AddressType
+ *
+ * <p>This key is used to resolve a private address from a public address.
+ *
+ * @param deviceAddress The device Bluetooth address for the filter. It needs to be in the
+ * format of "01:02:03:AB:CD:EF". The device address can be validated using {@link
+ * BluetoothAdapter#checkBluetoothAddress}. May be any type of address.
+ * @param addressType indication of the type of address
+ * e.g. {@link BluetoothDevice#ADDRESS_TYPE_PUBLIC}
+ * or {@link BluetoothDevice#ADDRESS_TYPE_RANDOM}
+ *
+ * @throws IllegalArgumentException If the {@code deviceAddress} is invalid.
+ * @throws IllegalArgumentException If the {@code addressType} is invalid length
+ * @throws NullPointerException if {@code deviceAddress} is null.
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ public Builder setDeviceAddress(@NonNull String deviceAddress,
+ @AddressType int addressType) {
+ return setDeviceAddressInternal(deviceAddress, addressType, null);
+ }
+
+ /**
+ * Set filter on Address with AddressType and the Identity Resolving Key (IRK).
+ *
+ * <p>The IRK is used to resolve a {@link BluetoothDevice#ADDRESS_TYPE_PUBLIC} from
+ * a PRIVATE_ADDRESS type.
+ *
+ * @param deviceAddress The device Bluetooth address for the filter. It needs to be in the
+ * format of "01:02:03:AB:CD:EF". The device address can be validated using {@link
+ * BluetoothAdapter#checkBluetoothAddress}. This Address type must only be PUBLIC OR RANDOM
+ * STATIC.
+ * @param addressType indication of the type of address
+ * e.g. {@link BluetoothDevice#ADDRESS_TYPE_PUBLIC}
+ * or {@link BluetoothDevice#ADDRESS_TYPE_RANDOM}
+ * @param irk non-null byte array representing the Identity Resolving Key
+ *
+ * @throws IllegalArgumentException If the {@code deviceAddress} is invalid.
+ * @throws IllegalArgumentException if the {@code irk} is invalid length.
+ * @throws IllegalArgumentException If the {@code addressType} is invalid length or is not
+ * PUBLIC or RANDOM STATIC when an IRK is present.
+ * @throws NullPointerException if {@code deviceAddress} or {@code irk} is null.
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi
+ public Builder setDeviceAddress(@NonNull String deviceAddress,
+ @AddressType int addressType,
+ @NonNull byte[] irk) {
+ Preconditions.checkNotNull(irk);
+ if (irk.length != LEN_IRK_OCTETS) {
+ throw new IllegalArgumentException("'irk' is invalid length!");
+ }
+ return setDeviceAddressInternal(deviceAddress, addressType, irk);
+ }
+
+ /**
+ * Set filter on Address with AddressType and the Identity Resolving Key (IRK).
+ *
+ * <p>Internal setter for the device address
+ *
+ * @param deviceAddress The device Bluetooth address for the filter. It needs to be in the
+ * format of "01:02:03:AB:CD:EF". The device address can be validated using {@link
+ * BluetoothAdapter#checkBluetoothAddress}.
+ * @param addressType indication of the type of address
+ * e.g. {@link BluetoothDevice#ADDRESS_TYPE_PUBLIC}
+ * @param irk non-null byte array representing the Identity Resolving Address; nullable
+ * internally.
+ *
+ * @throws IllegalArgumentException If the {@code deviceAddress} is invalid.
+ * @throws IllegalArgumentException If the {@code addressType} is invalid length.
+ * @throws NullPointerException if {@code deviceAddress} is null.
+ *
+ * @hide
+ */
+ @NonNull
+ private Builder setDeviceAddressInternal(@NonNull String deviceAddress,
+ @AddressType int addressType,
+ @Nullable byte[] irk) {
+
+ // Make sure our deviceAddress is valid!
+ Preconditions.checkNotNull(deviceAddress);
+ if (!BluetoothAdapter.checkBluetoothAddress(deviceAddress)) {
throw new IllegalArgumentException("invalid device address " + deviceAddress);
}
+
+ // Verify type range
+ if (addressType < BluetoothDevice.ADDRESS_TYPE_PUBLIC
+ || addressType > BluetoothDevice.ADDRESS_TYPE_RANDOM) {
+ throw new IllegalArgumentException("'addressType' is invalid!");
+ }
+
+ // IRK can only be used for a PUBLIC or RANDOM (STATIC) Address.
+ if (addressType == BluetoothDevice.ADDRESS_TYPE_RANDOM) {
+ // Don't want a bad combination of address and irk!
+ if (irk != null) {
+ // Since there are 3 possible RANDOM subtypes we must check to make sure
+ // the correct type of address is used.
+ if (!BluetoothAdapter.isAddressRandomStatic(deviceAddress)) {
+ throw new IllegalArgumentException(
+ "Invalid combination: IRK requires either a PUBLIC or "
+ + "RANDOM (STATIC) Address");
+ }
+ }
+ }
+
+ // PUBLIC doesn't require extra work
+ // Without an IRK any address may be accepted
+
mDeviceAddress = deviceAddress;
+ mAddressType = addressType;
+ mIrk = irk;
return this;
}
@@ -727,7 +879,8 @@ public final class ScanFilter implements Parcelable {
mServiceUuid, mUuidMask, mServiceSolicitationUuid,
mServiceSolicitationUuidMask,
mServiceDataUuid, mServiceData, mServiceDataMask,
- mManufacturerId, mManufacturerData, mManufacturerDataMask);
+ mManufacturerId, mManufacturerData, mManufacturerDataMask,
+ mAddressType, mIrk);
}
}
}
diff --git a/framework/java/android/bluetooth/le/ScanSettings.java b/framework/java/android/bluetooth/le/ScanSettings.java
index 504118ec5d..368d1eecad 100644
--- a/framework/java/android/bluetooth/le/ScanSettings.java
+++ b/framework/java/android/bluetooth/le/ScanSettings.java
@@ -52,6 +52,16 @@ public final class ScanSettings implements Parcelable {
public static final int SCAN_MODE_LOW_LATENCY = 2;
/**
+ * Perform Bluetooth LE scan in ambient discovery mode. This mode has lower duty cycle and more
+ * aggressive scan interval than balanced mode that provides a good trade-off between scan
+ * latency and power consumption.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int SCAN_MODE_AMBIENT_DISCOVERY = 3;
+
+ /**
* Trigger a callback for every Bluetooth advertisement found that matches the filter criteria.
* If no filter is active, all advertisement packets are reported.
*/
@@ -276,10 +286,17 @@ public final class ScanSettings implements Parcelable {
* @throws IllegalArgumentException If the {@code scanMode} is invalid.
*/
public Builder setScanMode(int scanMode) {
- if (scanMode < SCAN_MODE_OPPORTUNISTIC || scanMode > SCAN_MODE_LOW_LATENCY) {
- throw new IllegalArgumentException("invalid scan mode " + scanMode);
+ switch (scanMode) {
+ case SCAN_MODE_OPPORTUNISTIC:
+ case SCAN_MODE_LOW_POWER:
+ case SCAN_MODE_BALANCED:
+ case SCAN_MODE_LOW_LATENCY:
+ case SCAN_MODE_AMBIENT_DISCOVERY:
+ mScanMode = scanMode;
+ break;
+ default:
+ throw new IllegalArgumentException("invalid scan mode " + scanMode);
}
- mScanMode = scanMode;
return this;
}
diff --git a/framework/tests/Android.bp b/framework/tests/Android.bp
index 4b6f9db33a..a2e4dff8f8 100644
--- a/framework/tests/Android.bp
+++ b/framework/tests/Android.bp
@@ -1,3 +1,12 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
android_test {
name: "BluetoothTests",
// Include all test java files.
diff --git a/framework/tests/AndroidManifest.xml b/framework/tests/AndroidManifest.xml
index 7f9d874935..6849a90f50 100644
--- a/framework/tests/AndroidManifest.xml
+++ b/framework/tests/AndroidManifest.xml
@@ -15,14 +15,18 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.bluetooth.tests" >
+ package="com.android.bluetooth.tests"
+ android:sharedUserId="android.uid.bluetooth" >
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
+ <uses-permission android:name="android.permission.BLUETOOTH_PRIVILEGED" />
<uses-permission android:name="android.permission.BROADCAST_STICKY" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.LOCAL_MAC_ADDRESS" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
+ <uses-permission android:name="android.permission.RECEIVE_SMS" />
+ <uses-permission android:name="android.permission.READ_SMS"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
diff --git a/framework/tests/OWNERS b/framework/tests/OWNERS
new file mode 100644
index 0000000000..98bb877162
--- /dev/null
+++ b/framework/tests/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/bluetooth/OWNERS
diff --git a/framework/tests/src/android/bluetooth/BluetoothStressTest.java b/framework/tests/src/android/bluetooth/BluetoothStressTest.java
index 4b32ceae06..89dbe3f75b 100644
--- a/framework/tests/src/android/bluetooth/BluetoothStressTest.java
+++ b/framework/tests/src/android/bluetooth/BluetoothStressTest.java
@@ -360,6 +360,30 @@ public class BluetoothStressTest extends InstrumentationTestCase {
mTestUtils.unpair(mAdapter, device);
}
+ /* Make sure there is at least 1 unread message in the last week on remote device */
+ public void testMceSetMessageStatus() {
+ int iterations = BluetoothTestRunner.sMceSetMessageStatusIterations;
+ if (iterations == 0) {
+ return;
+ }
+
+ BluetoothDevice device = mAdapter.getRemoteDevice(BluetoothTestRunner.sDeviceAddress);
+ mTestUtils.enable(mAdapter);
+ mTestUtils.connectProfile(mAdapter, device, BluetoothProfile.MAP_CLIENT, null);
+ mTestUtils.mceGetUnreadMessage(mAdapter, device);
+
+ for (int i = 0; i < iterations; i++) {
+ mTestUtils.mceSetMessageStatus(mAdapter, device, BluetoothMapClient.READ);
+ mTestUtils.mceSetMessageStatus(mAdapter, device, BluetoothMapClient.UNREAD);
+ }
+
+ /**
+ * It is hard to find device to support set undeleted status, so just
+ * set deleted in 1 iteration
+ **/
+ mTestUtils.mceSetMessageStatus(mAdapter, device, BluetoothMapClient.DELETED);
+ }
+
private void sleep(long time) {
try {
Thread.sleep(time);
diff --git a/framework/tests/src/android/bluetooth/BluetoothTestRunner.java b/framework/tests/src/android/bluetooth/BluetoothTestRunner.java
index 56e691d8c2..d19c2c3e7e 100644
--- a/framework/tests/src/android/bluetooth/BluetoothTestRunner.java
+++ b/framework/tests/src/android/bluetooth/BluetoothTestRunner.java
@@ -40,6 +40,7 @@ import android.util.Log;
* [-e connect_input_iterations <iterations>] \
* [-e connect_pan_iterations <iterations>] \
* [-e start_stop_sco_iterations <iterations>] \
+ * [-e mce_set_message_status_iterations <iterations>] \
* [-e pair_address <address>] \
* [-e headset_address <address>] \
* [-e a2dp_address <address>] \
@@ -64,6 +65,7 @@ public class BluetoothTestRunner extends InstrumentationTestRunner {
public static int sConnectInputIterations = 100;
public static int sConnectPanIterations = 100;
public static int sStartStopScoIterations = 100;
+ public static int sMceSetMessageStatusIterations = 100;
public static String sDeviceAddress = "";
public static byte[] sDevicePairPin = {'1', '2', '3', '4'};
@@ -173,6 +175,15 @@ public class BluetoothTestRunner extends InstrumentationTestRunner {
}
}
+ val = arguments.getString("mce_set_message_status_iterations");
+ if (val != null) {
+ try {
+ sMceSetMessageStatusIterations = Integer.parseInt(val);
+ } catch (NumberFormatException e) {
+ // Invalid argument, fall back to default value
+ }
+ }
+
val = arguments.getString("device_address");
if (val != null) {
sDeviceAddress = val;
diff --git a/framework/tests/src/android/bluetooth/BluetoothTestUtils.java b/framework/tests/src/android/bluetooth/BluetoothTestUtils.java
index ed613c36b8..409025bc67 100644
--- a/framework/tests/src/android/bluetooth/BluetoothTestUtils.java
+++ b/framework/tests/src/android/bluetooth/BluetoothTestUtils.java
@@ -56,6 +56,10 @@ public class BluetoothTestUtils extends Assert {
private static final int CONNECT_PROXY_TIMEOUT = 5000;
/** Time between polls in ms. */
private static final int POLL_TIME = 100;
+ /** Timeout to get map message in ms. */
+ private static final int GET_UNREAD_MESSAGE_TIMEOUT = 10000;
+ /** Timeout to set map message status in ms. */
+ private static final int SET_MESSAGE_STATUS_TIMEOUT = 2000;
private abstract class FlagReceiver extends BroadcastReceiver {
private int mExpectedFlags = 0;
@@ -98,6 +102,8 @@ public class BluetoothTestUtils extends Assert {
private static final int STATE_TURNING_ON_FLAG = 1 << 6;
private static final int STATE_ON_FLAG = 1 << 7;
private static final int STATE_TURNING_OFF_FLAG = 1 << 8;
+ private static final int STATE_GET_MESSAGE_FINISHED_FLAG = 1 << 9;
+ private static final int STATE_SET_MESSAGE_STATUS_FINISHED_FLAG = 1 << 10;
public BluetoothReceiver(int expectedFlags) {
super(expectedFlags);
@@ -231,6 +237,9 @@ public class BluetoothTestUtils extends Assert {
case BluetoothProfile.PAN:
mConnectionAction = BluetoothPan.ACTION_CONNECTION_STATE_CHANGED;
break;
+ case BluetoothProfile.MAP_CLIENT:
+ mConnectionAction = BluetoothMapClient.ACTION_CONNECTION_STATE_CHANGED;
+ break;
default:
mConnectionAction = null;
}
@@ -308,6 +317,34 @@ public class BluetoothTestUtils extends Assert {
}
}
+
+ private class MceSetMessageStatusReceiver extends FlagReceiver {
+ private static final int MESSAGE_RECEIVED_FLAG = 1;
+ private static final int STATUS_CHANGED_FLAG = 1 << 1;
+
+ public MceSetMessageStatusReceiver(int expectedFlags) {
+ super(expectedFlags);
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (BluetoothMapClient.ACTION_MESSAGE_RECEIVED.equals(intent.getAction())) {
+ String handle = intent.getStringExtra(BluetoothMapClient.EXTRA_MESSAGE_HANDLE);
+ assertNotNull(handle);
+ setFiredFlag(MESSAGE_RECEIVED_FLAG);
+ mMsgHandle = handle;
+ } else if (BluetoothMapClient.ACTION_MESSAGE_DELETED_STATUS_CHANGED.equals(intent.getAction())) {
+ int result = intent.getIntExtra(BluetoothMapClient.EXTRA_RESULT_CODE, BluetoothMapClient.RESULT_FAILURE);
+ assertEquals(result, BluetoothMapClient.RESULT_SUCCESS);
+ setFiredFlag(STATUS_CHANGED_FLAG);
+ } else if (BluetoothMapClient.ACTION_MESSAGE_READ_STATUS_CHANGED.equals(intent.getAction())) {
+ int result = intent.getIntExtra(BluetoothMapClient.EXTRA_RESULT_CODE, BluetoothMapClient.RESULT_FAILURE);
+ assertEquals(result, BluetoothMapClient.RESULT_SUCCESS);
+ setFiredFlag(STATUS_CHANGED_FLAG);
+ }
+ }
+ }
+
private BluetoothProfile.ServiceListener mServiceListener =
new BluetoothProfile.ServiceListener() {
@Override
@@ -326,6 +363,9 @@ public class BluetoothTestUtils extends Assert {
case BluetoothProfile.PAN:
mPan = (BluetoothPan) proxy;
break;
+ case BluetoothProfile.MAP_CLIENT:
+ mMce = (BluetoothMapClient) proxy;
+ break;
}
}
}
@@ -346,6 +386,9 @@ public class BluetoothTestUtils extends Assert {
case BluetoothProfile.PAN:
mPan = null;
break;
+ case BluetoothProfile.MAP_CLIENT:
+ mMce = null;
+ break;
}
}
}
@@ -362,6 +405,8 @@ public class BluetoothTestUtils extends Assert {
private BluetoothHeadset mHeadset = null;
private BluetoothHidHost mInput = null;
private BluetoothPan mPan = null;
+ private BluetoothMapClient mMce = null;
+ private String mMsgHandle = null;
/**
* Creates a utility instance for testing Bluetooth.
@@ -898,7 +943,7 @@ public class BluetoothTestUtils extends Assert {
* @param adapter The BT adapter.
* @param device The remote device.
* @param profile The profile to connect. One of {@link BluetoothProfile#A2DP},
- * {@link BluetoothProfile#HEADSET}, or {@link BluetoothProfile#HID_HOST}.
+ * {@link BluetoothProfile#HEADSET}, {@link BluetoothProfile#HID_HOST} or {@link BluetoothProfile#MAP_CLIENT}..
* @param methodName The method name to printed in the logs. If null, will be
* "connectProfile(profile=&lt;profile&gt;, device=&lt;device&gt;)"
*/
@@ -941,6 +986,8 @@ public class BluetoothTestUtils extends Assert {
assertTrue(((BluetoothHeadset)proxy).connect(device));
} else if (profile == BluetoothProfile.HID_HOST) {
assertTrue(((BluetoothHidHost)proxy).connect(device));
+ } else if (profile == BluetoothProfile.MAP_CLIENT) {
+ assertTrue(((BluetoothMapClient)proxy).connect(device));
}
break;
default:
@@ -1016,6 +1063,8 @@ public class BluetoothTestUtils extends Assert {
assertTrue(((BluetoothHeadset)proxy).disconnect(device));
} else if (profile == BluetoothProfile.HID_HOST) {
assertTrue(((BluetoothHidHost)proxy).disconnect(device));
+ } else if (profile == BluetoothProfile.MAP_CLIENT) {
+ assertTrue(((BluetoothMapClient)proxy).disconnect(device));
}
break;
case BluetoothProfile.STATE_DISCONNECTED:
@@ -1373,6 +1422,89 @@ public class BluetoothTestUtils extends Assert {
}
}
+ public void mceGetUnreadMessage(BluetoothAdapter adapter, BluetoothDevice device) {
+ int mask;
+ String methodName = "getUnreadMessage";
+
+ if (!adapter.isEnabled()) {
+ fail(String.format("%s bluetooth not enabled", methodName));
+ }
+
+ if (!adapter.getBondedDevices().contains(device)) {
+ fail(String.format("%s device not paired", methodName));
+ }
+
+ mMce = (BluetoothMapClient) connectProxy(adapter, BluetoothProfile.MAP_CLIENT);
+ assertNotNull(mMce);
+
+ if (mMce.getConnectionState(device) != BluetoothProfile.STATE_CONNECTED) {
+ fail(String.format("%s device is not connected", methodName));
+ }
+
+ mMsgHandle = null;
+ mask = MceSetMessageStatusReceiver.MESSAGE_RECEIVED_FLAG;
+ MceSetMessageStatusReceiver receiver = getMceSetMessageStatusReceiver(device, mask);
+ assertTrue(mMce.getUnreadMessages(device));
+
+ long s = System.currentTimeMillis();
+ while (System.currentTimeMillis() - s < GET_UNREAD_MESSAGE_TIMEOUT) {
+ if ((receiver.getFiredFlags() & mask) == mask) {
+ writeOutput(String.format("%s completed", methodName));
+ removeReceiver(receiver);
+ return;
+ }
+ sleep(POLL_TIME);
+ }
+ int firedFlags = receiver.getFiredFlags();
+ removeReceiver(receiver);
+ fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%s)",
+ methodName, mMce.getConnectionState(device), BluetoothMapClient.STATE_CONNECTED, firedFlags, mask));
+ }
+
+ /**
+ * Set a message to read/unread/deleted/undeleted
+ */
+ public void mceSetMessageStatus(BluetoothAdapter adapter, BluetoothDevice device, int status) {
+ int mask;
+ String methodName = "setMessageStatus";
+
+ if (!adapter.isEnabled()) {
+ fail(String.format("%s bluetooth not enabled", methodName));
+ }
+
+ if (!adapter.getBondedDevices().contains(device)) {
+ fail(String.format("%s device not paired", methodName));
+ }
+
+ mMce = (BluetoothMapClient) connectProxy(adapter, BluetoothProfile.MAP_CLIENT);
+ assertNotNull(mMce);
+
+ if (mMce.getConnectionState(device) != BluetoothProfile.STATE_CONNECTED) {
+ fail(String.format("%s device is not connected", methodName));
+ }
+
+ assertNotNull(mMsgHandle);
+ mask = MceSetMessageStatusReceiver.STATUS_CHANGED_FLAG;
+ MceSetMessageStatusReceiver receiver = getMceSetMessageStatusReceiver(device, mask);
+
+ assertTrue(mMce.setMessageStatus(device, mMsgHandle, status));
+
+ long s = System.currentTimeMillis();
+ while (System.currentTimeMillis() - s < SET_MESSAGE_STATUS_TIMEOUT) {
+ if ((receiver.getFiredFlags() & mask) == mask) {
+ writeOutput(String.format("%s completed", methodName));
+ removeReceiver(receiver);
+ return;
+ }
+ sleep(POLL_TIME);
+ }
+
+ int firedFlags = receiver.getFiredFlags();
+ removeReceiver(receiver);
+ fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%s)",
+ methodName, mMce.getConnectionState(device), BluetoothPan.STATE_CONNECTED, firedFlags, mask));
+ }
+
private void addReceiver(BroadcastReceiver receiver, String[] actions) {
IntentFilter filter = new IntentFilter();
for (String action: actions) {
@@ -1408,7 +1540,8 @@ public class BluetoothTestUtils extends Assert {
String[] actions = {
BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED,
BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED,
- BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED};
+ BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED,
+ BluetoothMapClient.ACTION_CONNECTION_STATE_CHANGED};
ConnectProfileReceiver receiver = new ConnectProfileReceiver(device, profile,
expectedFlags);
addReceiver(receiver, actions);
@@ -1430,6 +1563,16 @@ public class BluetoothTestUtils extends Assert {
return receiver;
}
+ private MceSetMessageStatusReceiver getMceSetMessageStatusReceiver(BluetoothDevice device,
+ int expectedFlags) {
+ String[] actions = {BluetoothMapClient.ACTION_MESSAGE_RECEIVED,
+ BluetoothMapClient.ACTION_MESSAGE_READ_STATUS_CHANGED,
+ BluetoothMapClient.ACTION_MESSAGE_DELETED_STATUS_CHANGED};
+ MceSetMessageStatusReceiver receiver = new MceSetMessageStatusReceiver(expectedFlags);
+ addReceiver(receiver, actions);
+ return receiver;
+ }
+
private void removeReceiver(BroadcastReceiver receiver) {
mContext.unregisterReceiver(receiver);
mReceivers.remove(receiver);
@@ -1456,6 +1599,10 @@ public class BluetoothTestUtils extends Assert {
if (mPan != null) {
return mPan;
}
+ case BluetoothProfile.MAP_CLIENT:
+ if (mMce != null) {
+ return mMce;
+ }
break;
default:
return null;
@@ -1483,6 +1630,11 @@ public class BluetoothTestUtils extends Assert {
sleep(POLL_TIME);
}
return mPan;
+ case BluetoothProfile.MAP_CLIENT:
+ while (mMce == null && System.currentTimeMillis() - s < CONNECT_PROXY_TIMEOUT) {
+ sleep(POLL_TIME);
+ }
+ return mMce;
default:
return null;
}
diff --git a/service/java/com/android/server/bluetooth/BluetoothAirplaneModeListener.java b/service/java/com/android/server/bluetooth/BluetoothAirplaneModeListener.java
index 0b2cc88909..aa56da5773 100644
--- a/service/java/com/android/server/bluetooth/BluetoothAirplaneModeListener.java
+++ b/service/java/com/android/server/bluetooth/BluetoothAirplaneModeListener.java
@@ -16,22 +16,14 @@
package com.android.server;
-import android.bluetooth.BluetoothA2dp;
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothHearingAid;
-import android.bluetooth.BluetoothProfile;
-import android.bluetooth.BluetoothProfile.ServiceListener;
import android.content.Context;
-import android.content.res.Resources;
import android.database.ContentObserver;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.provider.Settings;
import android.util.Log;
-import android.widget.Toast;
-import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
/**
@@ -53,7 +45,7 @@ class BluetoothAirplaneModeListener {
private final BluetoothManagerService mBluetoothManager;
private final BluetoothAirplaneModeHandler mHandler;
- private AirplaneModeHelper mAirplaneHelper;
+ private BluetoothModeChangeHelper mAirplaneHelper;
@VisibleForTesting int mToastCount = 0;
@@ -97,7 +89,7 @@ class BluetoothAirplaneModeListener {
* Call after boot complete
*/
@VisibleForTesting
- void start(AirplaneModeHelper helper) {
+ void start(BluetoothModeChangeHelper helper) {
Log.i(TAG, "start");
mAirplaneHelper = helper;
mToastCount = mAirplaneHelper.getSettingsInt(TOAST_COUNT);
@@ -143,118 +135,4 @@ class BluetoothAirplaneModeListener {
}
return true;
}
-
- /**
- * Helper class that handles callout and callback methods without
- * complex logic.
- */
- @VisibleForTesting
- public static class AirplaneModeHelper {
- private volatile BluetoothA2dp mA2dp;
- private volatile BluetoothHearingAid mHearingAid;
- private final BluetoothAdapter mAdapter;
- private final Context mContext;
-
- AirplaneModeHelper(Context context) {
- mAdapter = BluetoothAdapter.getDefaultAdapter();
- mContext = context;
-
- mAdapter.getProfileProxy(mContext, mProfileServiceListener, BluetoothProfile.A2DP);
- mAdapter.getProfileProxy(mContext, mProfileServiceListener,
- BluetoothProfile.HEARING_AID);
- }
-
- private final ServiceListener mProfileServiceListener = new ServiceListener() {
- @Override
- public void onServiceConnected(int profile, BluetoothProfile proxy) {
- // Setup Bluetooth profile proxies
- switch (profile) {
- case BluetoothProfile.A2DP:
- mA2dp = (BluetoothA2dp) proxy;
- break;
- case BluetoothProfile.HEARING_AID:
- mHearingAid = (BluetoothHearingAid) proxy;
- break;
- default:
- break;
- }
- }
-
- @Override
- public void onServiceDisconnected(int profile) {
- // Clear Bluetooth profile proxies
- switch (profile) {
- case BluetoothProfile.A2DP:
- mA2dp = null;
- break;
- case BluetoothProfile.HEARING_AID:
- mHearingAid = null;
- break;
- default:
- break;
- }
- }
- };
-
- @VisibleForTesting
- public boolean isA2dpOrHearingAidConnected() {
- return isA2dpConnected() || isHearingAidConnected();
- }
-
- @VisibleForTesting
- public boolean isBluetoothOn() {
- final BluetoothAdapter adapter = mAdapter;
- if (adapter == null) {
- return false;
- }
- return adapter.getLeState() == BluetoothAdapter.STATE_ON;
- }
-
- @VisibleForTesting
- public boolean isAirplaneModeOn() {
- return Settings.Global.getInt(mContext.getContentResolver(),
- Settings.Global.AIRPLANE_MODE_ON, 0) == 1;
- }
-
- @VisibleForTesting
- public void onAirplaneModeChanged(BluetoothManagerService managerService) {
- managerService.onAirplaneModeChanged();
- }
-
- @VisibleForTesting
- public int getSettingsInt(String name) {
- return Settings.Global.getInt(mContext.getContentResolver(),
- name, 0);
- }
-
- @VisibleForTesting
- public void setSettingsInt(String name, int value) {
- Settings.Global.putInt(mContext.getContentResolver(),
- name, value);
- }
-
- @VisibleForTesting
- public void showToastMessage() {
- Resources r = mContext.getResources();
- final CharSequence text = r.getString(
- R.string.bluetooth_airplane_mode_toast, 0);
- Toast.makeText(mContext, text, Toast.LENGTH_LONG).show();
- }
-
- private boolean isA2dpConnected() {
- final BluetoothA2dp a2dp = mA2dp;
- if (a2dp == null) {
- return false;
- }
- return a2dp.getConnectedDevices().size() > 0;
- }
-
- private boolean isHearingAidConnected() {
- final BluetoothHearingAid hearingAid = mHearingAid;
- if (hearingAid == null) {
- return false;
- }
- return hearingAid.getConnectedDevices().size() > 0;
- }
- };
}
diff --git a/service/java/com/android/server/bluetooth/BluetoothDeviceConfigListener.java b/service/java/com/android/server/bluetooth/BluetoothDeviceConfigListener.java
new file mode 100644
index 0000000000..611a37de70
--- /dev/null
+++ b/service/java/com/android/server/bluetooth/BluetoothDeviceConfigListener.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.provider.DeviceConfig;
+import android.util.Slog;
+
+import java.util.ArrayList;
+
+/**
+ * The BluetoothDeviceConfigListener handles system device config change callback and checks
+ * whether we need to inform BluetoothManagerService on this change.
+ *
+ * The information of device config change would not be passed to the BluetoothManagerService
+ * when Bluetooth is on and Bluetooth is in one of the following situations:
+ * 1. Bluetooth A2DP is connected.
+ * 2. Bluetooth Hearing Aid profile is connected.
+ */
+class BluetoothDeviceConfigListener {
+ private static final String TAG = "BluetoothDeviceConfigListener";
+
+ private final BluetoothManagerService mService;
+ private final boolean mLogDebug;
+
+ BluetoothDeviceConfigListener(BluetoothManagerService service, boolean logDebug) {
+ mService = service;
+ mLogDebug = logDebug;
+ DeviceConfig.addOnPropertiesChangedListener(
+ DeviceConfig.NAMESPACE_BLUETOOTH,
+ (Runnable r) -> r.run(),
+ mDeviceConfigChangedListener);
+ }
+
+ private final DeviceConfig.OnPropertiesChangedListener mDeviceConfigChangedListener =
+ new DeviceConfig.OnPropertiesChangedListener() {
+ @Override
+ public void onPropertiesChanged(DeviceConfig.Properties properties) {
+ if (!properties.getNamespace().equals(DeviceConfig.NAMESPACE_BLUETOOTH)) {
+ return;
+ }
+ if (mLogDebug) {
+ ArrayList<String> flags = new ArrayList<>();
+ for (String name : properties.getKeyset()) {
+ flags.add(name + "='" + properties.getString(name, "") + "'");
+ }
+ Slog.d(TAG, "onPropertiesChanged: " + String.join(",", flags));
+ }
+ boolean foundInit = false;
+ for (String name : properties.getKeyset()) {
+ if (name.startsWith("INIT_")) {
+ foundInit = true;
+ break;
+ }
+ }
+ if (!foundInit) {
+ return;
+ }
+ mService.onInitFlagsChanged();
+ }
+ };
+
+}
diff --git a/service/java/com/android/server/bluetooth/BluetoothManagerService.java b/service/java/com/android/server/bluetooth/BluetoothManagerService.java
index 9ce7cf27f9..992ef2657a 100644
--- a/service/java/com/android/server/bluetooth/BluetoothManagerService.java
+++ b/service/java/com/android/server/bluetooth/BluetoothManagerService.java
@@ -23,7 +23,9 @@ import android.Manifest;
import android.app.ActivityManager;
import android.app.AppGlobals;
import android.app.AppOpsManager;
+import android.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothHearingAid;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothProtoEnums;
import android.bluetooth.IBluetooth;
@@ -116,6 +118,8 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
private static final int ADD_PROXY_DELAY_MS = 100;
// Delay for retrying enable and disable in msec
private static final int ENABLE_DISABLE_DELAY_MS = 300;
+ private static final int DELAY_BEFORE_RESTART_DUE_TO_INIT_FLAGS_CHANGED_MS = 300;
+ private static final int DELAY_FOR_RETRY_INIT_FLAG_CHECK_MS = 86400;
private static final int MESSAGE_ENABLE = 1;
private static final int MESSAGE_DISABLE = 2;
@@ -135,6 +139,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
private static final int MESSAGE_ADD_PROXY_DELAYED = 400;
private static final int MESSAGE_BIND_PROFILE_SERVICE = 401;
private static final int MESSAGE_RESTORE_USER_SETTING = 500;
+ private static final int MESSAGE_INIT_FLAGS_CHANGED = 600;
private static final int RESTORE_SETTING_TO_ON = 1;
private static final int RESTORE_SETTING_TO_OFF = 0;
@@ -174,8 +179,12 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
private int mWaitForEnableRetry;
private int mWaitForDisableRetry;
+ private BluetoothModeChangeHelper mBluetoothModeChangeHelper;
+
private BluetoothAirplaneModeListener mBluetoothAirplaneModeListener;
+ private BluetoothDeviceConfigListener mBluetoothDeviceConfigListener;
+
// used inside handler thread
private boolean mQuietEnable = false;
private boolean mEnable;
@@ -280,6 +289,14 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
}
};
+ @VisibleForTesting
+ public void onInitFlagsChanged() {
+ mHandler.removeMessages(MESSAGE_INIT_FLAGS_CHANGED);
+ mHandler.sendEmptyMessageDelayed(
+ MESSAGE_INIT_FLAGS_CHANGED,
+ DELAY_BEFORE_RESTART_DUE_TO_INIT_FLAGS_CHANGED_MS);
+ }
+
public boolean onFactoryReset() {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
"Need BLUETOOTH_PRIVILEGED permission");
@@ -390,7 +407,8 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
if (BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED.equals(action)) {
String newName = intent.getStringExtra(BluetoothAdapter.EXTRA_LOCAL_NAME);
if (DBG) {
- Slog.d(TAG, "Bluetooth Adapter name changed to " + newName);
+ Slog.d(TAG, "Bluetooth Adapter name changed to " + newName + " by "
+ + mContext.getPackageName());
}
if (newName != null) {
storeNameAndAddress(newName, null);
@@ -428,6 +446,16 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
mHandler.sendMessage(msg);
}
}
+ } else if (BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED.equals(action)
+ || BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
+ final int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE,
+ BluetoothProfile.STATE_CONNECTED);
+ if (mHandler.hasMessages(MESSAGE_INIT_FLAGS_CHANGED)
+ && state == BluetoothProfile.STATE_DISCONNECTED
+ && !mBluetoothModeChangeHelper.isA2dpOrHearingAidConnected()) {
+ Slog.i(TAG, "Device disconnected, reactivating pending flag changes");
+ onInitFlagsChanged();
+ }
}
}
};
@@ -478,6 +506,8 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
filter.addAction(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED);
filter.addAction(BluetoothAdapter.ACTION_BLUETOOTH_ADDRESS_CHANGED);
filter.addAction(Intent.ACTION_SETTING_RESTORED);
+ filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
+ filter.addAction(BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED);
filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
mContext.registerReceiver(mReceiver, filter);
@@ -781,6 +811,35 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
return enabledProfiles;
}
+ private boolean isDeviceProvisioned() {
+ return Settings.Global.getInt(mContentResolver, Settings.Global.DEVICE_PROVISIONED,
+ 0) != 0;
+ }
+
+ // Monitor change of BLE scan only mode settings.
+ private void registerForProvisioningStateChange() {
+ ContentObserver contentObserver = new ContentObserver(null) {
+ @Override
+ public void onChange(boolean selfChange) {
+ if (!isDeviceProvisioned()) {
+ if (DBG) {
+ Slog.d(TAG, "DEVICE_PROVISIONED setting changed, but device is not "
+ + "provisioned");
+ }
+ return;
+ }
+ if (mHandler.hasMessages(MESSAGE_INIT_FLAGS_CHANGED)) {
+ Slog.i(TAG, "Device provisioned, reactivating pending flag changes");
+ onInitFlagsChanged();
+ }
+ }
+ };
+
+ mContentResolver.registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED), false,
+ contentObserver);
+ }
+
// Monitor change of BLE scan only mode settings.
private void registerForBleScanModeChange() {
ContentObserver contentObserver = new ContentObserver(null) {
@@ -1341,10 +1400,13 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
Message getMsg = mHandler.obtainMessage(MESSAGE_GET_NAME_AND_ADDRESS);
mHandler.sendMessage(getMsg);
}
+
+ mBluetoothModeChangeHelper = new BluetoothModeChangeHelper(mContext);
if (mBluetoothAirplaneModeListener != null) {
- mBluetoothAirplaneModeListener.start(
- new BluetoothAirplaneModeListener.AirplaneModeHelper(mContext));
+ mBluetoothAirplaneModeListener.start(mBluetoothModeChangeHelper);
}
+ registerForProvisioningStateChange();
+ mBluetoothDeviceConfigListener = new BluetoothDeviceConfigListener(this, DBG);
}
/**
@@ -2153,80 +2215,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
/* disable and enable BT when detect a user switch */
if (mBluetooth != null && isEnabled()) {
- try {
- mBluetoothLock.readLock().lock();
- if (mBluetooth != null) {
- mBluetooth.unregisterCallback(mBluetoothCallback);
- }
- } catch (RemoteException re) {
- Slog.e(TAG, "Unable to unregister", re);
- } finally {
- mBluetoothLock.readLock().unlock();
- }
-
- if (mState == BluetoothAdapter.STATE_TURNING_OFF) {
- // MESSAGE_USER_SWITCHED happened right after MESSAGE_ENABLE
- bluetoothStateChangeHandler(mState, BluetoothAdapter.STATE_OFF);
- mState = BluetoothAdapter.STATE_OFF;
- }
- if (mState == BluetoothAdapter.STATE_OFF) {
- bluetoothStateChangeHandler(mState, BluetoothAdapter.STATE_TURNING_ON);
- mState = BluetoothAdapter.STATE_TURNING_ON;
- }
-
- waitForState(Set.of(BluetoothAdapter.STATE_ON));
-
- if (mState == BluetoothAdapter.STATE_TURNING_ON) {
- bluetoothStateChangeHandler(mState, BluetoothAdapter.STATE_ON);
- }
-
- unbindAllBluetoothProfileServices();
- // disable
- addActiveLog(BluetoothProtoEnums.ENABLE_DISABLE_REASON_USER_SWITCH,
- mContext.getPackageName(), false);
- handleDisable();
- // Pbap service need receive STATE_TURNING_OFF intent to close
- bluetoothStateChangeHandler(BluetoothAdapter.STATE_ON,
- BluetoothAdapter.STATE_TURNING_OFF);
-
- boolean didDisableTimeout =
- !waitForState(Set.of(BluetoothAdapter.STATE_OFF));
-
- bluetoothStateChangeHandler(BluetoothAdapter.STATE_TURNING_OFF,
- BluetoothAdapter.STATE_OFF);
- sendBluetoothServiceDownCallback();
-
- try {
- mBluetoothLock.writeLock().lock();
- if (mBluetooth != null) {
- mBluetooth = null;
- // Unbind
- mContext.unbindService(mConnection);
- }
- mBluetoothGatt = null;
- } finally {
- mBluetoothLock.writeLock().unlock();
- }
-
- //
- // If disabling Bluetooth times out, wait for an
- // additional amount of time to ensure the process is
- // shut down completely before attempting to restart.
- //
- if (didDisableTimeout) {
- SystemClock.sleep(3000);
- } else {
- SystemClock.sleep(100);
- }
-
- mHandler.removeMessages(MESSAGE_BLUETOOTH_STATE_CHANGE);
- mState = BluetoothAdapter.STATE_OFF;
- // enable
- addActiveLog(BluetoothProtoEnums.ENABLE_DISABLE_REASON_USER_SWITCH,
- mContext.getPackageName(), true);
- // mEnable flag could have been reset on disableBLE. Reenable it.
- mEnable = true;
- handleEnable(mQuietEnable);
+ restartForReason(BluetoothProtoEnums.ENABLE_DISABLE_REASON_USER_SWITCH);
} else if (mBinding || mBluetooth != null) {
Message userMsg = mHandler.obtainMessage(MESSAGE_USER_SWITCHED);
userMsg.arg2 = 1 + msg.arg2;
@@ -2253,8 +2242,114 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
}
handleEnable(mQuietEnable);
}
+ break;
+ }
+ case MESSAGE_INIT_FLAGS_CHANGED: {
+ if (DBG) {
+ Slog.d(TAG, "MESSAGE_INIT_FLAGS_CHANGED");
+ }
+ mHandler.removeMessages(MESSAGE_INIT_FLAGS_CHANGED);
+ if (mBluetoothModeChangeHelper.isA2dpOrHearingAidConnected()) {
+ Slog.i(TAG, "Delaying MESSAGE_INIT_FLAGS_CHANGED by "
+ + DELAY_FOR_RETRY_INIT_FLAG_CHECK_MS
+ + " ms due to existing connections");
+ mHandler.sendEmptyMessageDelayed(
+ MESSAGE_INIT_FLAGS_CHANGED,
+ DELAY_FOR_RETRY_INIT_FLAG_CHECK_MS);
+ break;
+ }
+ if (!isDeviceProvisioned()) {
+ Slog.i(TAG, "Delaying MESSAGE_INIT_FLAGS_CHANGED by "
+ + DELAY_FOR_RETRY_INIT_FLAG_CHECK_MS
+ + "ms because device is not provisioned");
+ mHandler.sendEmptyMessageDelayed(
+ MESSAGE_INIT_FLAGS_CHANGED,
+ DELAY_FOR_RETRY_INIT_FLAG_CHECK_MS);
+ break;
+ }
+ if (mBluetooth != null && isEnabled()) {
+ Slog.i(TAG, "Restarting Bluetooth due to init flag change");
+ restartForReason(
+ BluetoothProtoEnums.ENABLE_DISABLE_REASON_INIT_FLAGS_CHANGED);
+ }
+ break;
+ }
+ }
+ }
+
+ private void restartForReason(int reason) {
+ try {
+ mBluetoothLock.readLock().lock();
+ if (mBluetooth != null) {
+ mBluetooth.unregisterCallback(mBluetoothCallback);
}
+ } catch (RemoteException re) {
+ Slog.e(TAG, "Unable to unregister", re);
+ } finally {
+ mBluetoothLock.readLock().unlock();
+ }
+
+ if (mState == BluetoothAdapter.STATE_TURNING_OFF) {
+ // MESSAGE_USER_SWITCHED happened right after MESSAGE_ENABLE
+ bluetoothStateChangeHandler(mState, BluetoothAdapter.STATE_OFF);
+ mState = BluetoothAdapter.STATE_OFF;
}
+ if (mState == BluetoothAdapter.STATE_OFF) {
+ bluetoothStateChangeHandler(mState, BluetoothAdapter.STATE_TURNING_ON);
+ mState = BluetoothAdapter.STATE_TURNING_ON;
+ }
+
+ waitForState(Set.of(BluetoothAdapter.STATE_ON));
+
+ if (mState == BluetoothAdapter.STATE_TURNING_ON) {
+ bluetoothStateChangeHandler(mState, BluetoothAdapter.STATE_ON);
+ }
+
+ unbindAllBluetoothProfileServices();
+ // disable
+ addActiveLog(reason, mContext.getPackageName(), false);
+ handleDisable();
+ // Pbap service need receive STATE_TURNING_OFF intent to close
+ bluetoothStateChangeHandler(BluetoothAdapter.STATE_ON,
+ BluetoothAdapter.STATE_TURNING_OFF);
+
+ boolean didDisableTimeout =
+ !waitForState(Set.of(BluetoothAdapter.STATE_OFF));
+
+ bluetoothStateChangeHandler(BluetoothAdapter.STATE_TURNING_OFF,
+ BluetoothAdapter.STATE_OFF);
+ sendBluetoothServiceDownCallback();
+
+ try {
+ mBluetoothLock.writeLock().lock();
+ if (mBluetooth != null) {
+ mBluetooth = null;
+ // Unbind
+ mContext.unbindService(mConnection);
+ }
+ mBluetoothGatt = null;
+ } finally {
+ mBluetoothLock.writeLock().unlock();
+ }
+
+ //
+ // If disabling Bluetooth times out, wait for an
+ // additional amount of time to ensure the process is
+ // shut down completely before attempting to restart.
+ //
+ if (didDisableTimeout) {
+ SystemClock.sleep(3000);
+ } else {
+ SystemClock.sleep(100);
+ }
+
+ mHandler.removeMessages(MESSAGE_BLUETOOTH_STATE_CHANGE);
+ mState = BluetoothAdapter.STATE_OFF;
+ // enable
+ addActiveLog(reason, mContext.getPackageName(), true);
+ // mEnable flag could have been reset on disableBLE. Reenable it.
+ mEnable = true;
+ handleEnable(mQuietEnable);
}
}
@@ -2716,6 +2811,8 @@ class BluetoothManagerService extends IBluetoothManager.Stub {
return "RESTORE_USER_SETTING";
case BluetoothProtoEnums.ENABLE_DISABLE_REASON_FACTORY_RESET:
return "FACTORY_RESET";
+ case BluetoothProtoEnums.ENABLE_DISABLE_REASON_INIT_FLAGS_CHANGED:
+ return "INIT_FLAGS_CHANGED";
case BluetoothProtoEnums.ENABLE_DISABLE_REASON_UNSPECIFIED:
default: return "UNKNOWN[" + reason + "]";
}
diff --git a/service/java/com/android/server/bluetooth/BluetoothModeChangeHelper.java b/service/java/com/android/server/bluetooth/BluetoothModeChangeHelper.java
new file mode 100644
index 0000000000..242fa848c2
--- /dev/null
+++ b/service/java/com/android/server/bluetooth/BluetoothModeChangeHelper.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothHearingAid;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothProfile.ServiceListener;
+import android.content.Context;
+import android.content.res.Resources;
+import android.provider.Settings;
+import android.widget.Toast;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Helper class that handles callout and callback methods without
+ * complex logic.
+ */
+public class BluetoothModeChangeHelper {
+ private volatile BluetoothA2dp mA2dp;
+ private volatile BluetoothHearingAid mHearingAid;
+ private final BluetoothAdapter mAdapter;
+ private final Context mContext;
+
+ BluetoothModeChangeHelper(Context context) {
+ mAdapter = BluetoothAdapter.getDefaultAdapter();
+ mContext = context;
+
+ mAdapter.getProfileProxy(mContext, mProfileServiceListener, BluetoothProfile.A2DP);
+ mAdapter.getProfileProxy(mContext, mProfileServiceListener,
+ BluetoothProfile.HEARING_AID);
+ }
+
+ private final ServiceListener mProfileServiceListener = new ServiceListener() {
+ @Override
+ public void onServiceConnected(int profile, BluetoothProfile proxy) {
+ // Setup Bluetooth profile proxies
+ switch (profile) {
+ case BluetoothProfile.A2DP:
+ mA2dp = (BluetoothA2dp) proxy;
+ break;
+ case BluetoothProfile.HEARING_AID:
+ mHearingAid = (BluetoothHearingAid) proxy;
+ break;
+ default:
+ break;
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(int profile) {
+ // Clear Bluetooth profile proxies
+ switch (profile) {
+ case BluetoothProfile.A2DP:
+ mA2dp = null;
+ break;
+ case BluetoothProfile.HEARING_AID:
+ mHearingAid = null;
+ break;
+ default:
+ break;
+ }
+ }
+ };
+
+ @VisibleForTesting
+ public boolean isA2dpOrHearingAidConnected() {
+ return isA2dpConnected() || isHearingAidConnected();
+ }
+
+ @VisibleForTesting
+ public boolean isBluetoothOn() {
+ final BluetoothAdapter adapter = mAdapter;
+ if (adapter == null) {
+ return false;
+ }
+ return adapter.getLeState() == BluetoothAdapter.STATE_ON;
+ }
+
+ @VisibleForTesting
+ public boolean isAirplaneModeOn() {
+ return Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.AIRPLANE_MODE_ON, 0) == 1;
+ }
+
+ @VisibleForTesting
+ public void onAirplaneModeChanged(BluetoothManagerService managerService) {
+ managerService.onAirplaneModeChanged();
+ }
+
+ @VisibleForTesting
+ public int getSettingsInt(String name) {
+ return Settings.Global.getInt(mContext.getContentResolver(),
+ name, 0);
+ }
+
+ @VisibleForTesting
+ public void setSettingsInt(String name, int value) {
+ Settings.Global.putInt(mContext.getContentResolver(),
+ name, value);
+ }
+
+ @VisibleForTesting
+ public void showToastMessage() {
+ Resources r = mContext.getResources();
+ final CharSequence text = r.getString(
+ R.string.bluetooth_airplane_mode_toast, 0);
+ Toast.makeText(mContext, text, Toast.LENGTH_LONG).show();
+ }
+
+ private boolean isA2dpConnected() {
+ final BluetoothA2dp a2dp = mA2dp;
+ if (a2dp == null) {
+ return false;
+ }
+ return a2dp.getConnectedDevices().size() > 0;
+ }
+
+ private boolean isHearingAidConnected() {
+ final BluetoothHearingAid hearingAid = mHearingAid;
+ if (hearingAid == null) {
+ return false;
+ }
+ return hearingAid.getConnectedDevices().size() > 0;
+ }
+}
diff --git a/service/tests/src/com/android/server/BluetoothAirplaneModeListenerTest.java b/service/tests/src/com/android/server/BluetoothAirplaneModeListenerTest.java
index 968a402ff3..3ace3f4c79 100644
--- a/service/tests/src/com/android/server/BluetoothAirplaneModeListenerTest.java
+++ b/service/tests/src/com/android/server/BluetoothAirplaneModeListenerTest.java
@@ -27,8 +27,6 @@ import androidx.test.InstrumentationRegistry;
import androidx.test.filters.MediumTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.server.BluetoothAirplaneModeListener.AirplaneModeHelper;
-
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
@@ -41,7 +39,7 @@ public class BluetoothAirplaneModeListenerTest {
private Context mContext;
private BluetoothAirplaneModeListener mBluetoothAirplaneModeListener;
private BluetoothAdapter mBluetoothAdapter;
- private AirplaneModeHelper mHelper;
+ private BluetoothModeChangeHelper mHelper;
@Mock BluetoothManagerService mBluetoothManagerService;
@@ -49,7 +47,7 @@ public class BluetoothAirplaneModeListenerTest {
public void setUp() throws Exception {
mContext = InstrumentationRegistry.getTargetContext();
- mHelper = mock(AirplaneModeHelper.class);
+ mHelper = mock(BluetoothModeChangeHelper.class);
when(mHelper.getSettingsInt(BluetoothAirplaneModeListener.TOAST_COUNT))
.thenReturn(BluetoothAirplaneModeListener.MAX_TOAST_COUNT);
doNothing().when(mHelper).setSettingsInt(anyString(), anyInt());