diff options
Diffstat (limited to 'framework/java/android')
42 files changed, 3059 insertions, 263 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; } |
