/* * Copyright (C) 2013 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.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.RequiresNoPermission; import android.annotation.RequiresPermission; import android.annotation.SuppressLint; import android.bluetooth.BluetoothGattCharacteristic.WriteType; import android.bluetooth.annotations.RequiresBluetoothConnectPermission; import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; import android.compat.annotation.UnsupportedAppUsage; import android.content.AttributionSource; import android.os.Build; import android.os.Handler; import android.os.IBinder; import android.os.ParcelUuid; import android.os.RemoteException; import android.util.Log; import com.android.bluetooth.flags.Flags; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; import java.util.UUID; /** * Public API for the Bluetooth GATT Profile. * *
This class provides Bluetooth GATT functionality to enable communication with Bluetooth Smart * or Smart Ready devices. * *
To connect to a remote peripheral device, create a {@link BluetoothGattCallback} and call
* {@link BluetoothDevice#connectGatt} to get a instance of this class. GATT capable devices can be
* discovered using the Bluetooth device discovery or BLE scan process.
*/
public final class BluetoothGatt implements BluetoothProfile {
private static final String TAG = "BluetoothGatt";
private static final boolean DBG = true;
private static final boolean VDBG = false;
@UnsupportedAppUsage private IBluetoothGatt mService;
@UnsupportedAppUsage private volatile BluetoothGattCallback mCallback;
private Handler mHandler;
@UnsupportedAppUsage private int mClientIf;
private BluetoothDevice mDevice;
@UnsupportedAppUsage private boolean mAutoConnect;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private int mAuthRetryState;
private int mConnState;
private final Object mStateLock = new Object();
private final Object mDeviceBusyLock = new Object();
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private Boolean mDeviceBusy = false;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
private int mTransport;
private int mPhy;
private boolean mOpportunistic;
private final AttributionSource mAttributionSource;
private static final int AUTH_RETRY_STATE_IDLE = 0;
private static final int AUTH_RETRY_STATE_MITM = 2;
private static final int CONN_STATE_IDLE = 0;
private static final int CONN_STATE_CONNECTING = 1;
private static final int CONN_STATE_CONNECTED = 2;
private static final int CONN_STATE_CLOSED = 4;
private static final int WRITE_CHARACTERISTIC_MAX_RETRIES = 5;
private static final int WRITE_CHARACTERISTIC_TIME_TO_WAIT = 10; // milliseconds
// Max length of an attribute value, defined in gatt_api.h
private static final int GATT_MAX_ATTR_LEN = 512;
private List Application should call this method as early as possible after it is done with this GATT
* client.
*/
@RequiresBluetoothConnectPermission
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public void close() {
if (DBG) Log.d(TAG, "close()");
unregisterApp();
mConnState = CONN_STATE_CLOSED;
mAuthRetryState = AUTH_RETRY_STATE_IDLE;
}
/**
* Returns a service by UUID, instance and type.
*
* @hide
*/
/*package*/ BluetoothGattService getService(BluetoothDevice device, UUID uuid, int instanceId) {
for (BluetoothGattService svc : mServices) {
if (svc.getDevice().equals(device)
&& svc.getInstanceId() == instanceId
&& svc.getUuid().equals(uuid)) {
return svc;
}
}
return null;
}
/**
* Returns a characteristic with id equal to instanceId.
*
* @hide
*/
/*package*/ BluetoothGattCharacteristic getCharacteristicById(
BluetoothDevice device, int instanceId) {
for (BluetoothGattService svc : mServices) {
for (BluetoothGattCharacteristic charac : svc.getCharacteristics()) {
if (charac.getInstanceId() == instanceId) {
return charac;
}
}
}
return null;
}
/**
* Returns a descriptor with id equal to instanceId.
*
* @hide
*/
/*package*/ BluetoothGattDescriptor getDescriptorById(BluetoothDevice device, int instanceId) {
for (BluetoothGattService svc : mServices) {
for (BluetoothGattCharacteristic charac : svc.getCharacteristics()) {
for (BluetoothGattDescriptor desc : charac.getDescriptors()) {
if (desc.getInstanceId() == instanceId) {
return desc;
}
}
}
}
return null;
}
/**
* Queue the runnable on a {@link Handler} provided by the user, or execute the runnable
* immediately if no Handler was provided.
*/
private void runOrQueueCallback(final Runnable cb) {
if (mHandler == null) {
try {
cb.run();
} catch (Exception ex) {
Log.w(TAG, "Unhandled exception in callback", ex);
}
} else {
mHandler.post(cb);
}
}
/**
* Register an application callback to start using GATT.
*
* This is an asynchronous call. If registration is successful, client connection will be
* initiated.
*
* @param callback GATT callback handler that will receive asynchronous callbacks.
* @return If true, the callback will be called to notify success or failure, false on immediate
* error
*/
@RequiresLegacyBluetoothPermission
@RequiresBluetoothConnectPermission
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
private boolean registerApp(BluetoothGattCallback callback, Handler handler) {
return registerApp(callback, handler, false);
}
/**
* Register an application callback to start using GATT.
*
* This is an asynchronous call. If registration is successful, client connection will be
* initiated.
*
* @param callback GATT callback handler that will receive asynchronous callbacks.
* @param eattSupport indicate to allow for eatt support
* @return If true, the callback will be called to notify success or failure, false on immediate
* error
* @hide
*/
@RequiresLegacyBluetoothPermission
@RequiresBluetoothConnectPermission
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
private boolean registerApp(
BluetoothGattCallback callback, Handler handler, boolean eattSupport) {
if (DBG) Log.d(TAG, "registerApp()");
if (mService == null) return false;
mCallback = callback;
mHandler = handler;
UUID uuid = UUID.randomUUID();
if (DBG) Log.d(TAG, "registerApp() - UUID=" + uuid);
try {
mService.registerClient(
new ParcelUuid(uuid), mBluetoothGattCallback, eattSupport, mAttributionSource);
} catch (RemoteException e) {
Log.e(TAG, "", e);
return false;
}
return true;
}
/** Unregister the current application and callbacks. */
@UnsupportedAppUsage
@RequiresBluetoothConnectPermission
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
private void unregisterApp() {
if (mService == null || mClientIf == 0) return;
if (DBG) Log.d(TAG, "unregisterApp() - mClientIf=" + mClientIf);
try {
mCallback = null;
mService.unregisterClient(mClientIf, mAttributionSource);
mClientIf = 0;
} catch (RemoteException e) {
Log.e(TAG, "", e);
}
}
/**
* Initiate a connection to a Bluetooth GATT capable device.
*
* The connection may not be established right away, but will be completed when the remote
* device is available. A {@link BluetoothGattCallback#onConnectionStateChange} callback will be
* invoked when the connection state changes as a result of this function.
*
* The autoConnect parameter determines whether to actively connect to the remote device, or
* rather passively scan and finalize the connection when the remote device is in
* range/available. Generally, the first ever connection to a device should be direct
* (autoConnect set to false) and subsequent connections to known devices should be invoked with
* the autoConnect parameter set to true.
*
* @param autoConnect Whether to directly connect to the remote device (false) or to
* automatically connect as soon as the remote device becomes available (true).
* @return true, if the connection attempt was initiated successfully
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@RequiresLegacyBluetoothPermission
@RequiresBluetoothConnectPermission
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
/*package*/ boolean connect(
Boolean autoConnect, BluetoothGattCallback callback, Handler handler) {
if (DBG) {
Log.d(TAG, "connect() - device: " + mDevice + ", auto: " + autoConnect);
}
synchronized (mStateLock) {
if (mConnState != CONN_STATE_IDLE) {
throw new IllegalStateException("Not idle");
}
mConnState = CONN_STATE_CONNECTING;
}
mAutoConnect = autoConnect;
if (!registerApp(callback, handler)) {
synchronized (mStateLock) {
mConnState = CONN_STATE_IDLE;
}
Log.e(TAG, "Failed to register callback");
return false;
}
// The connection will continue in the onClientRegistered callback
return true;
}
/**
* Disconnects an established connection, or cancels a connection attempt currently in progress.
*/
@RequiresLegacyBluetoothPermission
@RequiresBluetoothConnectPermission
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public void disconnect() {
if (DBG) Log.d(TAG, "cancelOpen() - device: " + mDevice);
if (mService == null || mClientIf == 0) return;
try {
mService.clientDisconnect(mClientIf, mDevice.getAddress(), mAttributionSource);
} catch (RemoteException e) {
Log.e(TAG, "", e);
}
}
/**
* Connect back to remote device.
*
* This method is used to re-connect to a remote device after the connection has been
* dropped. If the device is not in range, the re-connection will be triggered once the device
* is back in range.
*
* @return true, if the connection attempt was initiated successfully
*/
@RequiresBluetoothConnectPermission
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean connect() {
try {
if (DBG) {
Log.d(TAG, "connect(void) - device: " + mDevice + ", auto=" + mAutoConnect);
}
// autoConnect is inverse of "isDirect"
mService.clientConnect(
mClientIf,
mDevice.getAddress(),
mDevice.getAddressType(),
!mAutoConnect,
mTransport,
mOpportunistic,
mPhy,
mAttributionSource);
return true;
} catch (RemoteException e) {
Log.e(TAG, "", e);
return false;
}
}
/**
* Set the preferred connection PHY for this app. Please note that this is just a
* recommendation, whether the PHY change will happen depends on other applications preferences,
* local and remote controller capabilities. Controller can override these settings.
*
* {@link BluetoothGattCallback#onPhyUpdate} will be triggered as a result of this call, even
* if no PHY change happens. It is also triggered when remote device updates the PHY.
*
* @param txPhy preferred transmitter PHY. Bitwise OR of any of {@link
* BluetoothDevice#PHY_LE_1M_MASK}, {@link BluetoothDevice#PHY_LE_2M_MASK}, and {@link
* BluetoothDevice#PHY_LE_CODED_MASK}.
* @param rxPhy preferred receiver PHY. Bitwise OR of any of {@link
* BluetoothDevice#PHY_LE_1M_MASK}, {@link BluetoothDevice#PHY_LE_2M_MASK}, and {@link
* BluetoothDevice#PHY_LE_CODED_MASK}.
* @param phyOptions preferred coding to use when transmitting on the LE Coded PHY. Can be one
* of {@link BluetoothDevice#PHY_OPTION_NO_PREFERRED}, {@link BluetoothDevice#PHY_OPTION_S2}
* or {@link BluetoothDevice#PHY_OPTION_S8}
*/
@RequiresBluetoothConnectPermission
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public void setPreferredPhy(int txPhy, int rxPhy, int phyOptions) {
try {
mService.clientSetPreferredPhy(
mClientIf, mDevice.getAddress(), txPhy, rxPhy, phyOptions, mAttributionSource);
} catch (RemoteException e) {
Log.e(TAG, "", e);
}
}
/**
* Read the current transmitter PHY and receiver PHY of the connection. The values are returned
* in {@link BluetoothGattCallback#onPhyRead}
*/
@RequiresBluetoothConnectPermission
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public void readPhy() {
try {
mService.clientReadPhy(mClientIf, mDevice.getAddress(), mAttributionSource);
} catch (RemoteException e) {
Log.e(TAG, "", e);
}
}
/**
* Return the remote bluetooth device this GATT client targets to
*
* @return remote bluetooth device
*/
@RequiresNoPermission
public BluetoothDevice getDevice() {
return mDevice;
}
/**
* Discovers services offered by a remote device as well as their characteristics and
* descriptors.
*
* This is an asynchronous operation. Once service discovery is completed, the {@link
* BluetoothGattCallback#onServicesDiscovered} callback is triggered. If the discovery was
* successful, the remote services can be retrieved using the {@link #getServices} function.
*
* @return true, if the remote service discovery has been started
*/
@RequiresLegacyBluetoothPermission
@RequiresBluetoothConnectPermission
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean discoverServices() {
if (DBG) Log.d(TAG, "discoverServices() - device: " + mDevice);
if (mService == null || mClientIf == 0) return false;
mServices.clear();
try {
mService.discoverServices(mClientIf, mDevice.getAddress(), mAttributionSource);
} catch (RemoteException e) {
Log.e(TAG, "", e);
return false;
}
return true;
}
/**
* Discovers a service by UUID. This is exposed only for passing PTS tests. It should never be
* used by real applications. The service is not searched for characteristics and descriptors,
* or returned in any callback.
*
* @return true, if the remote service discovery has been started
* @hide
*/
@RequiresLegacyBluetoothPermission
@RequiresBluetoothConnectPermission
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean discoverServiceByUuid(UUID uuid) {
if (DBG) Log.d(TAG, "discoverServiceByUuid() - device: " + mDevice);
if (mService == null || mClientIf == 0) return false;
mServices.clear();
try {
mService.discoverServiceByUuid(
mClientIf, mDevice.getAddress(), new ParcelUuid(uuid), mAttributionSource);
} catch (RemoteException e) {
Log.e(TAG, "", e);
return false;
}
return true;
}
/**
* Returns a list of GATT services offered by the remote device.
*
* This function requires that service discovery has been completed for the given device.
*
* @return List of services on the remote device. Returns an empty list if service discovery has
* not yet been performed.
*/
@RequiresLegacyBluetoothPermission
@RequiresNoPermission
public List This function requires that service discovery has been completed for the given device.
*
* If multiple instances of the same service (as identified by UUID) exist, the first
* instance of the service is returned.
*
* @param uuid UUID of the requested service
* @return BluetoothGattService if supported, or null if the requested service is not offered by
* the remote device.
*/
@RequiresLegacyBluetoothPermission
@RequiresNoPermission
public BluetoothGattService getService(UUID uuid) {
for (BluetoothGattService service : mServices) {
if (service.getDevice().equals(mDevice) && service.getUuid().equals(uuid)) {
return service;
}
}
return null;
}
/**
* Reads the requested characteristic from the associated remote device.
*
* This is an asynchronous operation. The result of the read operation is reported by the
* {@link BluetoothGattCallback#onCharacteristicRead(BluetoothGatt, BluetoothGattCharacteristic,
* byte[], int)} callback.
*
* @param characteristic Characteristic to read from the remote device
* @return true, if the read operation was initiated successfully
*/
@RequiresLegacyBluetoothPermission
@RequiresBluetoothConnectPermission
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean readCharacteristic(BluetoothGattCharacteristic characteristic) {
if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_READ) == 0) {
return false;
}
if (VDBG) Log.d(TAG, "readCharacteristic() - uuid: " + characteristic.getUuid());
if (mService == null || mClientIf == 0) return false;
BluetoothGattService service = characteristic.getService();
if (service == null) return false;
BluetoothDevice device = service.getDevice();
if (device == null) return false;
synchronized (mDeviceBusyLock) {
if (mDeviceBusy) return false;
mDeviceBusy = true;
}
try {
mService.readCharacteristic(
mClientIf,
device.getAddress(),
characteristic.getInstanceId(),
AUTHENTICATION_NONE,
mAttributionSource);
} catch (RemoteException e) {
Log.e(TAG, "", e);
synchronized (mDeviceBusyLock) {
mDeviceBusy = false;
}
return false;
}
return true;
}
/**
* Reads the characteristic using its UUID from the associated remote device.
*
* This is an asynchronous operation. The result of the read operation is reported by the
* {@link BluetoothGattCallback#onCharacteristicRead(BluetoothGatt, BluetoothGattCharacteristic,
* byte[], int)} callback.
*
* @param uuid UUID of characteristic to read from the remote device
* @return true, if the read operation was initiated successfully
* @hide
*/
@RequiresLegacyBluetoothPermission
@RequiresBluetoothConnectPermission
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean readUsingCharacteristicUuid(UUID uuid, int startHandle, int endHandle) {
if (VDBG) Log.d(TAG, "readUsingCharacteristicUuid() - uuid: " + uuid);
if (mService == null || mClientIf == 0) return false;
synchronized (mDeviceBusyLock) {
if (mDeviceBusy) return false;
mDeviceBusy = true;
}
try {
mService.readUsingCharacteristicUuid(
mClientIf,
mDevice.getAddress(),
new ParcelUuid(uuid),
startHandle,
endHandle,
AUTHENTICATION_NONE,
mAttributionSource);
} catch (RemoteException e) {
Log.e(TAG, "", e);
synchronized (mDeviceBusyLock) {
mDeviceBusy = false;
}
return false;
}
return true;
}
/**
* Writes a given characteristic and its values to the associated remote device.
*
* Once the write operation has been completed, the {@link
* BluetoothGattCallback#onCharacteristicWrite} callback is invoked, reporting the result of the
* operation.
*
* @param characteristic Characteristic to write on the remote device
* @return true, if the write operation was initiated successfully
* @throws IllegalArgumentException if characteristic or its value are null
* @deprecated Use {@link BluetoothGatt#writeCharacteristic(BluetoothGattCharacteristic, byte[],
* int)} as this is not memory safe because it relies on a {@link
* BluetoothGattCharacteristic} object whose underlying fields are subject to change outside
* this method.
*/
@Deprecated
@RequiresLegacyBluetoothPermission
@RequiresBluetoothConnectPermission
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean writeCharacteristic(BluetoothGattCharacteristic characteristic) {
try {
return writeCharacteristic(
characteristic,
characteristic.getValue(),
characteristic.getWriteType())
== BluetoothStatusCodes.SUCCESS;
} catch (Exception e) {
return false;
}
}
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(
value = {
BluetoothStatusCodes.SUCCESS,
BluetoothStatusCodes.ERROR_MISSING_BLUETOOTH_CONNECT_PERMISSION,
BluetoothStatusCodes.ERROR_DEVICE_NOT_CONNECTED,
BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND,
BluetoothStatusCodes.ERROR_GATT_WRITE_NOT_ALLOWED,
BluetoothStatusCodes.ERROR_GATT_WRITE_REQUEST_BUSY,
BluetoothStatusCodes.ERROR_UNKNOWN
})
public @interface WriteOperationReturnValues {}
/**
* Writes a given characteristic and its values to the associated remote device.
*
* Once the write operation has been completed, the {@link
* BluetoothGattCallback#onCharacteristicWrite} callback is invoked, reporting the result of the
* operation.
*
* @param characteristic Characteristic to write on the remote device
* @return whether the characteristic was successfully written to
* @throws IllegalArgumentException if characteristic or value are null
*/
@RequiresBluetoothConnectPermission
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@WriteOperationReturnValues
public int writeCharacteristic(
@NonNull BluetoothGattCharacteristic characteristic,
@NonNull byte[] value,
@WriteType int writeType) {
if (characteristic == null) {
throw new IllegalArgumentException("characteristic must not be null");
}
if (value == null) {
throw new IllegalArgumentException("value must not be null");
}
if (value.length > GATT_MAX_ATTR_LEN) {
throw new IllegalArgumentException(
"value should not be longer than max length of an attribute value");
}
if (VDBG) Log.d(TAG, "writeCharacteristic() - uuid: " + characteristic.getUuid());
if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_WRITE) == 0
&& (characteristic.getProperties()
& BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE)
== 0) {
return BluetoothStatusCodes.ERROR_GATT_WRITE_NOT_ALLOWED;
}
if (mService == null || mClientIf == 0) {
return BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND;
}
BluetoothGattService service = characteristic.getService();
if (service == null) {
throw new IllegalArgumentException("Characteristic must have a non-null service");
}
BluetoothDevice device = service.getDevice();
if (device == null) {
throw new IllegalArgumentException("Service must have a non-null device");
}
synchronized (mDeviceBusyLock) {
if (mDeviceBusy) {
return BluetoothStatusCodes.ERROR_GATT_WRITE_REQUEST_BUSY;
}
mDeviceBusy = true;
}
int requestStatus = BluetoothStatusCodes.ERROR_UNKNOWN;
try {
for (int i = 0; i < WRITE_CHARACTERISTIC_MAX_RETRIES; i++) {
requestStatus =
mService.writeCharacteristic(
mClientIf,
device.getAddress(),
characteristic.getInstanceId(),
writeType,
AUTHENTICATION_NONE,
value,
mAttributionSource);
if (requestStatus != BluetoothStatusCodes.ERROR_GATT_WRITE_REQUEST_BUSY) {
break;
}
try {
Thread.sleep(WRITE_CHARACTERISTIC_TIME_TO_WAIT);
} catch (InterruptedException e) {
Log.e(TAG, "", e);
}
}
} catch (RemoteException e) {
Log.e(TAG, "", e);
synchronized (mDeviceBusyLock) {
mDeviceBusy = false;
}
throw e.rethrowAsRuntimeException();
}
if (Flags.gattFixDeviceBusy()) {
if (requestStatus != BluetoothStatusCodes.SUCCESS) {
synchronized (mDeviceBusyLock) {
mDeviceBusy = false;
}
}
}
return requestStatus;
}
/**
* Reads the value for a given descriptor from the associated remote device.
*
* Once the read operation has been completed, the {@link
* BluetoothGattCallback#onDescriptorRead} callback is triggered, signaling the result of the
* operation.
*
* @param descriptor Descriptor value to read from the remote device
* @return true, if the read operation was initiated successfully
*/
@RequiresLegacyBluetoothPermission
@RequiresBluetoothConnectPermission
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean readDescriptor(BluetoothGattDescriptor descriptor) {
if (VDBG) Log.d(TAG, "readDescriptor() - uuid: " + descriptor.getUuid());
if (mService == null || mClientIf == 0) return false;
BluetoothGattCharacteristic characteristic = descriptor.getCharacteristic();
if (characteristic == null) return false;
BluetoothGattService service = characteristic.getService();
if (service == null) return false;
BluetoothDevice device = service.getDevice();
if (device == null) return false;
synchronized (mDeviceBusyLock) {
if (mDeviceBusy) return false;
mDeviceBusy = true;
}
try {
mService.readDescriptor(
mClientIf,
device.getAddress(),
descriptor.getInstanceId(),
AUTHENTICATION_NONE,
mAttributionSource);
} catch (RemoteException e) {
Log.e(TAG, "", e);
synchronized (mDeviceBusyLock) {
mDeviceBusy = false;
}
return false;
}
return true;
}
/**
* Write the value of a given descriptor to the associated remote device.
*
* A {@link BluetoothGattCallback#onDescriptorWrite} callback is triggered to report the
* result of the write operation.
*
* @param descriptor Descriptor to write to the associated remote device
* @return true, if the write operation was initiated successfully
* @throws IllegalArgumentException if descriptor or its value are null
* @deprecated Use {@link BluetoothGatt#writeDescriptor(BluetoothGattDescriptor, byte[])} as
* this is not memory safe because it relies on a {@link BluetoothGattDescriptor} object
* whose underlying fields are subject to change outside this method.
*/
@Deprecated
@RequiresLegacyBluetoothPermission
@RequiresBluetoothConnectPermission
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean writeDescriptor(BluetoothGattDescriptor descriptor) {
try {
return writeDescriptor(descriptor, descriptor.getValue())
== BluetoothStatusCodes.SUCCESS;
} catch (Exception e) {
return false;
}
}
/**
* Write the value of a given descriptor to the associated remote device.
*
* A {@link BluetoothGattCallback#onDescriptorWrite} callback is triggered to report the
* result of the write operation.
*
* @param descriptor Descriptor to write to the associated remote device
* @return true, if the write operation was initiated successfully
* @throws IllegalArgumentException if descriptor or value are null
*/
@RequiresBluetoothConnectPermission
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@WriteOperationReturnValues
public int writeDescriptor(@NonNull BluetoothGattDescriptor descriptor, @NonNull byte[] value) {
if (descriptor == null) {
throw new IllegalArgumentException("descriptor must not be null");
}
if (value == null) {
throw new IllegalArgumentException("value must not be null");
}
if (VDBG) Log.d(TAG, "writeDescriptor() - uuid: " + descriptor.getUuid());
if (mService == null || mClientIf == 0) {
return BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND;
}
BluetoothGattCharacteristic characteristic = descriptor.getCharacteristic();
if (characteristic == null) {
throw new IllegalArgumentException("Descriptor must have a non-null characteristic");
}
BluetoothGattService service = characteristic.getService();
if (service == null) {
throw new IllegalArgumentException("Characteristic must have a non-null service");
}
BluetoothDevice device = service.getDevice();
if (device == null) {
throw new IllegalArgumentException("Service must have a non-null device");
}
synchronized (mDeviceBusyLock) {
if (mDeviceBusy) return BluetoothStatusCodes.ERROR_GATT_WRITE_REQUEST_BUSY;
mDeviceBusy = true;
}
try {
return mService.writeDescriptor(
mClientIf,
device.getAddress(),
descriptor.getInstanceId(),
AUTHENTICATION_NONE,
value,
mAttributionSource);
} catch (RemoteException e) {
Log.e(TAG, "", e);
synchronized (mDeviceBusyLock) {
mDeviceBusy = false;
}
throw e.rethrowAsRuntimeException();
}
}
/**
* Initiates a reliable write transaction for a given remote device.
*
* Once a reliable write transaction has been initiated, all calls to {@link
* #writeCharacteristic} are sent to the remote device for verification and queued up for atomic
* execution. The application will receive a {@link BluetoothGattCallback#onCharacteristicWrite}
* callback in response to every {@link #writeCharacteristic(BluetoothGattCharacteristic,
* byte[], int)} call and is responsible for verifying if the value has been transmitted
* accurately.
*
* After all characteristics have been queued up and verified, {@link #executeReliableWrite}
* will execute all writes. If a characteristic was not written correctly, calling {@link
* #abortReliableWrite} will cancel the current transaction without committing any values on the
* remote device.
*
* @return true, if the reliable write transaction has been initiated
*/
@RequiresLegacyBluetoothPermission
@RequiresBluetoothConnectPermission
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean beginReliableWrite() {
if (VDBG) Log.d(TAG, "beginReliableWrite() - device: " + mDevice);
if (mService == null || mClientIf == 0) return false;
try {
mService.beginReliableWrite(mClientIf, mDevice.getAddress(), mAttributionSource);
} catch (RemoteException e) {
Log.e(TAG, "", e);
return false;
}
return true;
}
/**
* Executes a reliable write transaction for a given remote device.
*
* This function will commit all queued up characteristic write operations for a given remote
* device.
*
* A {@link BluetoothGattCallback#onReliableWriteCompleted} callback is invoked to indicate
* whether the transaction has been executed correctly.
*
* @return true, if the request to execute the transaction has been sent
*/
@RequiresLegacyBluetoothPermission
@RequiresBluetoothConnectPermission
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean executeReliableWrite() {
if (VDBG) Log.d(TAG, "executeReliableWrite() - device: " + mDevice);
if (mService == null || mClientIf == 0) return false;
synchronized (mDeviceBusyLock) {
if (mDeviceBusy) return false;
mDeviceBusy = true;
}
try {
mService.endReliableWrite(mClientIf, mDevice.getAddress(), true, mAttributionSource);
} catch (RemoteException e) {
Log.e(TAG, "", e);
synchronized (mDeviceBusyLock) {
mDeviceBusy = false;
}
return false;
}
return true;
}
/**
* Cancels a reliable write transaction for a given device.
*
* Calling this function will discard all queued characteristic write operations for a given
* remote device.
*/
@RequiresLegacyBluetoothPermission
@RequiresBluetoothConnectPermission
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public void abortReliableWrite() {
if (VDBG) Log.d(TAG, "abortReliableWrite() - device: " + mDevice);
if (mService == null || mClientIf == 0) return;
try {
mService.endReliableWrite(mClientIf, mDevice.getAddress(), false, mAttributionSource);
} catch (RemoteException e) {
Log.e(TAG, "", e);
}
}
/**
* @deprecated Use {@link #abortReliableWrite()}
*/
@Deprecated
@RequiresBluetoothConnectPermission
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public void abortReliableWrite(BluetoothDevice mDevice) {
abortReliableWrite();
}
/**
* Enable or disable notifications/indications for a given characteristic.
*
* Once notifications are enabled for a characteristic, a {@link
* BluetoothGattCallback#onCharacteristicChanged(BluetoothGatt, BluetoothGattCharacteristic,
* byte[])} callback will be triggered if the remote device indicates that the given
* characteristic has changed.
*
* @param characteristic The characteristic for which to enable notifications
* @param enable Set to true to enable notifications/indications
* @return true, if the requested notification status was set successfully
*/
@RequiresLegacyBluetoothPermission
@RequiresBluetoothConnectPermission
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean setCharacteristicNotification(
BluetoothGattCharacteristic characteristic, boolean enable) {
if (DBG) {
Log.d(
TAG,
"setCharacteristicNotification() - uuid: "
+ characteristic.getUuid()
+ " enable: "
+ enable);
}
if (mService == null || mClientIf == 0) return false;
BluetoothGattService service = characteristic.getService();
if (service == null) return false;
BluetoothDevice device = service.getDevice();
if (device == null) return false;
try {
mService.registerForNotification(
mClientIf,
device.getAddress(),
characteristic.getInstanceId(),
enable,
mAttributionSource);
} catch (RemoteException e) {
Log.e(TAG, "", e);
return false;
}
return true;
}
/**
* Clears the internal cache and forces a refresh of the services from the remote device.
*
* @hide
*/
@UnsupportedAppUsage
@RequiresBluetoothConnectPermission
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean refresh() {
if (DBG) Log.d(TAG, "refresh() - device: " + mDevice);
if (mService == null || mClientIf == 0) return false;
try {
mService.refreshDevice(mClientIf, mDevice.getAddress(), mAttributionSource);
} catch (RemoteException e) {
Log.e(TAG, "", e);
return false;
}
return true;
}
/**
* Read the RSSI for a connected remote device.
*
* The {@link BluetoothGattCallback#onReadRemoteRssi} callback will be invoked when the RSSI
* value has been read.
*
* @return true, if the RSSI value has been requested successfully
*/
@RequiresLegacyBluetoothPermission
@RequiresBluetoothConnectPermission
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean readRemoteRssi() {
if (DBG) Log.d(TAG, "readRssi() - device: " + mDevice);
if (mService == null || mClientIf == 0) return false;
try {
mService.readRemoteRssi(mClientIf, mDevice.getAddress(), mAttributionSource);
} catch (RemoteException e) {
Log.e(TAG, "", e);
return false;
}
return true;
}
/**
* Request an MTU size used for a given connection. Please note that starting from Android 14,
* the Android Bluetooth stack requests the BLE ATT MTU to 517 bytes when the first GATT client
* requests an MTU, and disregards all subsequent MTU requests. Check out MTU is set to 517 for
* the first GATT client requesting an MTU for more information.
*
* When performing a write request operation (write without response), the data sent is
* truncated to the MTU size. This function may be used to request a larger MTU size to be able
* to send more data at once.
*
* A {@link BluetoothGattCallback#onMtuChanged} callback will indicate whether this operation
* was successful.
*
* @return true, if the new MTU value has been requested successfully
*/
@RequiresLegacyBluetoothPermission
@RequiresBluetoothConnectPermission
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean requestMtu(int mtu) {
if (DBG) {
Log.d(TAG, "configureMTU() - device: " + mDevice + " mtu: " + mtu);
}
if (mService == null || mClientIf == 0) return false;
try {
mService.configureMTU(mClientIf, mDevice.getAddress(), mtu, mAttributionSource);
} catch (RemoteException e) {
Log.e(TAG, "", e);
return false;
}
return true;
}
/**
* Request a connection parameter update.
*
* This function will send a connection parameter update request to the remote device.
*
* @param connectionPriority Request a specific connection priority. Must be one of {@link
* BluetoothGatt#CONNECTION_PRIORITY_BALANCED}, {@link
* BluetoothGatt#CONNECTION_PRIORITY_HIGH} {@link
* BluetoothGatt#CONNECTION_PRIORITY_LOW_POWER}, or {@link
* BluetoothGatt#CONNECTION_PRIORITY_DCK}.
* @throws IllegalArgumentException If the parameters are outside of their specified range.
*/
@RequiresBluetoothConnectPermission
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean requestConnectionPriority(int connectionPriority) {
if (connectionPriority < CONNECTION_PRIORITY_BALANCED
|| connectionPriority > CONNECTION_PRIORITY_DCK) {
throw new IllegalArgumentException("connectionPriority not within valid range");
}
if (DBG) Log.d(TAG, "requestConnectionPriority() - params: " + connectionPriority);
if (mService == null || mClientIf == 0) return false;
try {
mService.connectionParameterUpdate(
mClientIf, mDevice.getAddress(), connectionPriority, mAttributionSource);
} catch (RemoteException e) {
Log.e(TAG, "", e);
return false;
}
return true;
}
/**
* Request an LE connection parameter update.
*
* This function will send an LE connection parameters update request to the remote device.
*
* @return true, if the request is send to the Bluetooth stack.
* @hide
*/
@RequiresBluetoothConnectPermission
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean requestLeConnectionUpdate(
int minConnectionInterval,
int maxConnectionInterval,
int slaveLatency,
int supervisionTimeout,
int minConnectionEventLen,
int maxConnectionEventLen) {
if (DBG) {
Log.d(
TAG,
"requestLeConnectionUpdate() - min=("
+ minConnectionInterval
+ ")"
+ (1.25 * minConnectionInterval)
+ "msec, max=("
+ maxConnectionInterval
+ ")"
+ (1.25 * maxConnectionInterval)
+ "msec, latency="
+ slaveLatency
+ ", timeout="
+ supervisionTimeout
+ "msec"
+ ", min_ce="
+ minConnectionEventLen
+ ", max_ce="
+ maxConnectionEventLen);
}
if (mService == null || mClientIf == 0) return false;
try {
mService.leConnectionUpdate(
mClientIf,
mDevice.getAddress(),
minConnectionInterval,
maxConnectionInterval,
slaveLatency,
supervisionTimeout,
minConnectionEventLen,
maxConnectionEventLen,
mAttributionSource);
} catch (RemoteException e) {
Log.e(TAG, "", e);
return false;
}
return true;
}
/**
* Request LE subrate mode.
*
* This function will send a LE subrate request to the remote device.
*
* @param subrateMode Request a specific subrate mode.
* @throws IllegalArgumentException If the parameters are outside of their specified range.
* @return true, if the request is send to the Bluetooth stack.
* @hide
*/
@RequiresBluetoothConnectPermission
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean requestSubrateMode(@SubrateRequestMode int subrateMode) {
if (subrateMode < SUBRATE_REQUEST_MODE_BALANCED
|| subrateMode > SUBRATE_REQUEST_MODE_LOW_POWER) {
throw new IllegalArgumentException("Subrate Mode not within valid range");
}
if (DBG) {
Log.d(TAG, "requestsubrateMode() - subrateMode: " + subrateMode);
}
if (mService == null || mClientIf == 0) {
return false;
}
try {
mService.subrateModeRequest(
mClientIf, mDevice.getAddress(), subrateMode, mAttributionSource);
} catch (RemoteException e) {
Log.e(TAG, "", e);
return false;
}
return true;
}
/**
* Request a LE subrate request.
*
* This function will send a LE subrate request to the remote device.
*
* @return true, if the request is send to the Bluetooth stack.
* @hide
*/
@RequiresBluetoothConnectPermission
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
public boolean bleSubrateRequest(
int subrateMin,
int subrateMax,
int maxLatency,
int contNumber,
int supervisionTimeout) {
if (DBG) {
Log.d(
TAG,
"bleSubrateRequest() - subrateMin="
+ subrateMin
+ " subrateMax="
+ (subrateMax)
+ " maxLatency= "
+ maxLatency
+ "contNumber="
+ contNumber
+ " supervisionTimeout="
+ supervisionTimeout);
}
if (mService == null || mClientIf == 0) {
return false;
}
try {
mService.leSubrateRequest(
mClientIf,
mDevice.getAddress(),
subrateMin,
subrateMax,
maxLatency,
contNumber,
supervisionTimeout,
mAttributionSource);
} catch (RemoteException e) {
Log.e(TAG, "", e);
return false;
}
return true;
}
/**
* @deprecated Not supported - please use {@link BluetoothManager#getConnectedDevices(int)} with
* {@link BluetoothProfile#GATT} as argument
* @throws UnsupportedOperationException on every call
*/
@Override
@RequiresNoPermission
@Deprecated
public int getConnectionState(BluetoothDevice device) {
throw new UnsupportedOperationException("Use BluetoothManager#getConnectionState instead.");
}
/**
* @deprecated Not supported - please use {@link BluetoothManager#getConnectedDevices(int)} with
* {@link BluetoothProfile#GATT} as argument
* @throws UnsupportedOperationException on every call
*/
@Override
@RequiresNoPermission
@Deprecated
public List