diff options
author | TreeHugger Robot <treehugger-gerrit@google.com> | 2022-01-18 23:59:58 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2022-01-18 23:59:58 +0000 |
commit | d15d8266b900c549d2eed52c56f82d7c3c3792c8 (patch) | |
tree | 0b7c4c70453c091e24e37758ff1b1fd49bf20eb3 /framework/java/android/bluetooth/BluetoothGatt.java | |
parent | 7d69d903a5ee30dac4abc82e056f2b8ec0278708 (diff) | |
parent | aa6d8f71ae61c0e57b35258c00d28fcba5154f09 (diff) |
Merge changes from topic "migrate-sc-bt" into sc-v2-dev-plus-aosp
* changes:
Migrating BT files into packages/modules/Bluetooth
Merge history of Bluetooth API from frameworks/base
Diffstat (limited to 'framework/java/android/bluetooth/BluetoothGatt.java')
-rw-r--r-- | framework/java/android/bluetooth/BluetoothGatt.java | 1848 |
1 files changed, 1848 insertions, 0 deletions
diff --git a/framework/java/android/bluetooth/BluetoothGatt.java b/framework/java/android/bluetooth/BluetoothGatt.java new file mode 100644 index 0000000000..b531829d29 --- /dev/null +++ b/framework/java/android/bluetooth/BluetoothGatt.java @@ -0,0 +1,1848 @@ +/* + * 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.IntDef; +import android.annotation.NonNull; +import android.annotation.RequiresNoPermission; +import android.annotation.RequiresPermission; +import android.annotation.SuppressLint; +import android.bluetooth.annotations.RequiresBluetoothConnectPermission; +import android.bluetooth.annotations.RequiresLegacyBluetoothPermission; +import android.compat.annotation.UnsupportedAppUsage; +import android.content.AttributionSource; +import android.os.Build; +import android.os.Handler; +import android.os.ParcelUuid; +import android.os.RemoteException; +import android.util.Log; + +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. + * + * <p>This class provides Bluetooth GATT functionality to enable communication + * with Bluetooth Smart or Smart Ready devices. + * + * <p>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_NO_MITM = 1; + 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_DISCONNECTING = 3; + 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 + + private List<BluetoothGattService> mServices; + + /** A GATT operation completed successfully */ + public static final int GATT_SUCCESS = 0; + + /** GATT read operation is not permitted */ + public static final int GATT_READ_NOT_PERMITTED = 0x2; + + /** GATT write operation is not permitted */ + public static final int GATT_WRITE_NOT_PERMITTED = 0x3; + + /** Insufficient authentication for a given operation */ + public static final int GATT_INSUFFICIENT_AUTHENTICATION = 0x5; + + /** The given request is not supported */ + public static final int GATT_REQUEST_NOT_SUPPORTED = 0x6; + + /** Insufficient encryption for a given operation */ + public static final int GATT_INSUFFICIENT_ENCRYPTION = 0xf; + + /** A read or write operation was requested with an invalid offset */ + public static final int GATT_INVALID_OFFSET = 0x7; + + /** Insufficient authorization for a given operation */ + public static final int GATT_INSUFFICIENT_AUTHORIZATION = 0x8; + + /** A write operation exceeds the maximum length of the attribute */ + public static final int GATT_INVALID_ATTRIBUTE_LENGTH = 0xd; + + /** A remote device connection is congested. */ + public static final int GATT_CONNECTION_CONGESTED = 0x8f; + + /** A GATT operation failed, errors other than the above */ + public static final int GATT_FAILURE = 0x101; + + /** + * Connection parameter update - Use the connection parameters recommended by the + * Bluetooth SIG. This is the default value if no connection parameter update + * is requested. + */ + public static final int CONNECTION_PRIORITY_BALANCED = 0; + + /** + * Connection parameter update - Request a high priority, low latency connection. + * An application should only request high priority connection parameters to transfer large + * amounts of data over LE quickly. Once the transfer is complete, the application should + * request {@link BluetoothGatt#CONNECTION_PRIORITY_BALANCED} connection parameters to reduce + * energy use. + */ + public static final int CONNECTION_PRIORITY_HIGH = 1; + + /** Connection parameter update - Request low power, reduced data rate connection parameters. */ + public static final int CONNECTION_PRIORITY_LOW_POWER = 2; + + /** + * No authentication required. + * + * @hide + */ + /*package*/ static final int AUTHENTICATION_NONE = 0; + + /** + * Authentication requested; no person-in-the-middle protection required. + * + * @hide + */ + /*package*/ static final int AUTHENTICATION_NO_MITM = 1; + + /** + * Authentication with person-in-the-middle protection requested. + * + * @hide + */ + /*package*/ static final int AUTHENTICATION_MITM = 2; + + /** + * Bluetooth GATT callbacks. Overrides the default BluetoothGattCallback implementation. + */ + @SuppressLint("AndroidFrameworkBluetoothPermission") + private final IBluetoothGattCallback mBluetoothGattCallback = + new IBluetoothGattCallback.Stub() { + /** + * Application interface registered - app is ready to go + * @hide + */ + @Override + @SuppressLint("AndroidFrameworkRequiresPermission") + public void onClientRegistered(int status, int clientIf) { + if (DBG) { + Log.d(TAG, "onClientRegistered() - status=" + status + + " clientIf=" + clientIf); + } + if (VDBG) { + synchronized (mStateLock) { + if (mConnState != CONN_STATE_CONNECTING) { + Log.e(TAG, "Bad connection state: " + mConnState); + } + } + } + mClientIf = clientIf; + if (status != GATT_SUCCESS) { + runOrQueueCallback(new Runnable() { + @Override + public void run() { + final BluetoothGattCallback callback = mCallback; + if (callback != null) { + callback.onConnectionStateChange(BluetoothGatt.this, + GATT_FAILURE, + BluetoothProfile.STATE_DISCONNECTED); + } + } + }); + + synchronized (mStateLock) { + mConnState = CONN_STATE_IDLE; + } + return; + } + try { + mService.clientConnect(mClientIf, mDevice.getAddress(), + !mAutoConnect, mTransport, mOpportunistic, + mPhy, mAttributionSource); // autoConnect is inverse of "isDirect" + } catch (RemoteException e) { + Log.e(TAG, "", e); + } + } + + /** + * Phy update callback + * @hide + */ + @Override + public void onPhyUpdate(String address, int txPhy, int rxPhy, int status) { + if (DBG) { + Log.d(TAG, "onPhyUpdate() - status=" + status + + " address=" + address + " txPhy=" + txPhy + " rxPhy=" + rxPhy); + } + if (!address.equals(mDevice.getAddress())) { + return; + } + + runOrQueueCallback(new Runnable() { + @Override + public void run() { + final BluetoothGattCallback callback = mCallback; + if (callback != null) { + callback.onPhyUpdate(BluetoothGatt.this, txPhy, rxPhy, status); + } + } + }); + } + + /** + * Phy read callback + * @hide + */ + @Override + public void onPhyRead(String address, int txPhy, int rxPhy, int status) { + if (DBG) { + Log.d(TAG, "onPhyRead() - status=" + status + + " address=" + address + " txPhy=" + txPhy + " rxPhy=" + rxPhy); + } + if (!address.equals(mDevice.getAddress())) { + return; + } + + runOrQueueCallback(new Runnable() { + @Override + public void run() { + final BluetoothGattCallback callback = mCallback; + if (callback != null) { + callback.onPhyRead(BluetoothGatt.this, txPhy, rxPhy, status); + } + } + }); + } + + /** + * Client connection state changed + * @hide + */ + @Override + public void onClientConnectionState(int status, int clientIf, + boolean connected, String address) { + if (DBG) { + Log.d(TAG, "onClientConnectionState() - status=" + status + + " clientIf=" + clientIf + " device=" + address); + } + if (!address.equals(mDevice.getAddress())) { + return; + } + int profileState = connected ? BluetoothProfile.STATE_CONNECTED : + BluetoothProfile.STATE_DISCONNECTED; + + runOrQueueCallback(new Runnable() { + @Override + public void run() { + final BluetoothGattCallback callback = mCallback; + if (callback != null) { + callback.onConnectionStateChange(BluetoothGatt.this, status, + profileState); + } + } + }); + + synchronized (mStateLock) { + if (connected) { + mConnState = CONN_STATE_CONNECTED; + } else { + mConnState = CONN_STATE_IDLE; + } + } + + synchronized (mDeviceBusyLock) { + mDeviceBusy = false; + } + } + + /** + * Remote search has been completed. + * The internal object structure should now reflect the state + * of the remote device database. Let the application know that + * we are done at this point. + * @hide + */ + @Override + public void onSearchComplete(String address, List<BluetoothGattService> services, + int status) { + if (DBG) { + Log.d(TAG, + "onSearchComplete() = Device=" + address + " Status=" + status); + } + if (!address.equals(mDevice.getAddress())) { + return; + } + + for (BluetoothGattService s : services) { + //services we receive don't have device set properly. + s.setDevice(mDevice); + } + + mServices.addAll(services); + + // Fix references to included services, as they doesn't point to right objects. + for (BluetoothGattService fixedService : mServices) { + ArrayList<BluetoothGattService> includedServices = + new ArrayList(fixedService.getIncludedServices()); + fixedService.getIncludedServices().clear(); + + for (BluetoothGattService brokenRef : includedServices) { + BluetoothGattService includedService = getService(mDevice, + brokenRef.getUuid(), brokenRef.getInstanceId()); + if (includedService != null) { + fixedService.addIncludedService(includedService); + } else { + Log.e(TAG, "Broken GATT database: can't find included service."); + } + } + } + + runOrQueueCallback(new Runnable() { + @Override + public void run() { + final BluetoothGattCallback callback = mCallback; + if (callback != null) { + callback.onServicesDiscovered(BluetoothGatt.this, status); + } + } + }); + } + + /** + * Remote characteristic has been read. + * Updates the internal value. + * @hide + */ + @Override + @SuppressLint("AndroidFrameworkRequiresPermission") + public void onCharacteristicRead(String address, int status, int handle, + byte[] value) { + if (VDBG) { + Log.d(TAG, "onCharacteristicRead() - Device=" + address + + " handle=" + handle + " Status=" + status); + } + + if (!address.equals(mDevice.getAddress())) { + return; + } + + synchronized (mDeviceBusyLock) { + mDeviceBusy = false; + } + + if ((status == GATT_INSUFFICIENT_AUTHENTICATION + || status == GATT_INSUFFICIENT_ENCRYPTION) + && (mAuthRetryState != AUTH_RETRY_STATE_MITM)) { + try { + final int authReq = (mAuthRetryState == AUTH_RETRY_STATE_IDLE) + ? AUTHENTICATION_NO_MITM : AUTHENTICATION_MITM; + mService.readCharacteristic( + mClientIf, address, handle, authReq, mAttributionSource); + mAuthRetryState++; + return; + } catch (RemoteException e) { + Log.e(TAG, "", e); + } + } + + mAuthRetryState = AUTH_RETRY_STATE_IDLE; + + BluetoothGattCharacteristic characteristic = getCharacteristicById(mDevice, + handle); + if (characteristic == null) { + Log.w(TAG, "onCharacteristicRead() failed to find characteristic!"); + return; + } + + runOrQueueCallback(new Runnable() { + @Override + public void run() { + final BluetoothGattCallback callback = mCallback; + if (callback != null) { + if (status == 0) characteristic.setValue(value); + callback.onCharacteristicRead(BluetoothGatt.this, characteristic, + value, status); + // Keep calling deprecated callback to maintain app compatibility + callback.onCharacteristicRead(BluetoothGatt.this, characteristic, + status); + } + } + }); + } + + /** + * Characteristic has been written to the remote device. + * Let the app know how we did... + * @hide + */ + @Override + @SuppressLint("AndroidFrameworkRequiresPermission") + public void onCharacteristicWrite(String address, int status, int handle, + byte[] value) { + if (VDBG) { + Log.d(TAG, "onCharacteristicWrite() - Device=" + address + + " handle=" + handle + " Status=" + status); + } + + if (!address.equals(mDevice.getAddress())) { + return; + } + + synchronized (mDeviceBusyLock) { + mDeviceBusy = false; + } + + BluetoothGattCharacteristic characteristic = getCharacteristicById(mDevice, + handle); + if (characteristic == null) return; + + if ((status == GATT_INSUFFICIENT_AUTHENTICATION + || status == GATT_INSUFFICIENT_ENCRYPTION) + && (mAuthRetryState != AUTH_RETRY_STATE_MITM)) { + try { + final int authReq = (mAuthRetryState == AUTH_RETRY_STATE_IDLE) + ? AUTHENTICATION_NO_MITM : AUTHENTICATION_MITM; + int requestStatus = BluetoothStatusCodes.ERROR_UNKNOWN; + for (int i = 0; i < WRITE_CHARACTERISTIC_MAX_RETRIES; i++) { + requestStatus = mService.writeCharacteristic(mClientIf, address, + handle, characteristic.getWriteType(), authReq, + value, mAttributionSource); + if (requestStatus + != BluetoothStatusCodes.ERROR_GATT_WRITE_REQUEST_BUSY) { + break; + } + try { + Thread.sleep(WRITE_CHARACTERISTIC_TIME_TO_WAIT); + } catch (InterruptedException e) { + } + } + mAuthRetryState++; + return; + } catch (RemoteException e) { + Log.e(TAG, "", e); + } + } + + mAuthRetryState = AUTH_RETRY_STATE_IDLE; + runOrQueueCallback(new Runnable() { + @Override + public void run() { + final BluetoothGattCallback callback = mCallback; + if (callback != null) { + callback.onCharacteristicWrite(BluetoothGatt.this, characteristic, + status); + } + } + }); + } + + /** + * Remote characteristic has been updated. + * Updates the internal value. + * @hide + */ + @Override + public void onNotify(String address, int handle, byte[] value) { + if (VDBG) Log.d(TAG, "onNotify() - Device=" + address + " handle=" + handle); + + if (!address.equals(mDevice.getAddress())) { + return; + } + + BluetoothGattCharacteristic characteristic = getCharacteristicById(mDevice, + handle); + if (characteristic == null) return; + + runOrQueueCallback(new Runnable() { + @Override + public void run() { + final BluetoothGattCallback callback = mCallback; + if (callback != null) { + characteristic.setValue(value); + callback.onCharacteristicChanged(BluetoothGatt.this, + characteristic, value); + // Keep calling deprecated callback to maintain app compatibility + callback.onCharacteristicChanged(BluetoothGatt.this, + characteristic); + } + } + }); + } + + /** + * Descriptor has been read. + * @hide + */ + @Override + @SuppressLint("AndroidFrameworkRequiresPermission") + public void onDescriptorRead(String address, int status, int handle, byte[] value) { + if (VDBG) { + Log.d(TAG, + "onDescriptorRead() - Device=" + address + " handle=" + handle); + } + + if (!address.equals(mDevice.getAddress())) { + return; + } + + synchronized (mDeviceBusyLock) { + mDeviceBusy = false; + } + + BluetoothGattDescriptor descriptor = getDescriptorById(mDevice, handle); + if (descriptor == null) return; + + + if ((status == GATT_INSUFFICIENT_AUTHENTICATION + || status == GATT_INSUFFICIENT_ENCRYPTION) + && (mAuthRetryState != AUTH_RETRY_STATE_MITM)) { + try { + final int authReq = (mAuthRetryState == AUTH_RETRY_STATE_IDLE) + ? AUTHENTICATION_NO_MITM : AUTHENTICATION_MITM; + mService.readDescriptor( + mClientIf, address, handle, authReq, mAttributionSource); + mAuthRetryState++; + return; + } catch (RemoteException e) { + Log.e(TAG, "", e); + } + } + + mAuthRetryState = AUTH_RETRY_STATE_IDLE; + + runOrQueueCallback(new Runnable() { + @Override + public void run() { + final BluetoothGattCallback callback = mCallback; + if (callback != null) { + if (status == 0) descriptor.setValue(value); + callback.onDescriptorRead(BluetoothGatt.this, descriptor, status, + value); + // Keep calling deprecated callback to maintain app compatibility + callback.onDescriptorRead(BluetoothGatt.this, descriptor, status); + } + } + }); + } + + /** + * Descriptor write operation complete. + * @hide + */ + @Override + @SuppressLint("AndroidFrameworkRequiresPermission") + public void onDescriptorWrite(String address, int status, int handle, + byte[] value) { + if (VDBG) { + Log.d(TAG, + "onDescriptorWrite() - Device=" + address + " handle=" + handle); + } + + if (!address.equals(mDevice.getAddress())) { + return; + } + + synchronized (mDeviceBusyLock) { + mDeviceBusy = false; + } + + BluetoothGattDescriptor descriptor = getDescriptorById(mDevice, handle); + if (descriptor == null) return; + + if ((status == GATT_INSUFFICIENT_AUTHENTICATION + || status == GATT_INSUFFICIENT_ENCRYPTION) + && (mAuthRetryState != AUTH_RETRY_STATE_MITM)) { + try { + final int authReq = (mAuthRetryState == AUTH_RETRY_STATE_IDLE) + ? AUTHENTICATION_NO_MITM : AUTHENTICATION_MITM; + mService.writeDescriptor(mClientIf, address, handle, + authReq, value, mAttributionSource); + mAuthRetryState++; + return; + } catch (RemoteException e) { + Log.e(TAG, "", e); + } + } + + mAuthRetryState = AUTH_RETRY_STATE_IDLE; + + runOrQueueCallback(new Runnable() { + @Override + public void run() { + final BluetoothGattCallback callback = mCallback; + if (callback != null) { + callback.onDescriptorWrite(BluetoothGatt.this, descriptor, status); + } + } + }); + } + + /** + * Prepared write transaction completed (or aborted) + * @hide + */ + @Override + public void onExecuteWrite(String address, int status) { + if (VDBG) { + Log.d(TAG, "onExecuteWrite() - Device=" + address + + " status=" + status); + } + if (!address.equals(mDevice.getAddress())) { + return; + } + + synchronized (mDeviceBusyLock) { + mDeviceBusy = false; + } + + runOrQueueCallback(new Runnable() { + @Override + public void run() { + final BluetoothGattCallback callback = mCallback; + if (callback != null) { + callback.onReliableWriteCompleted(BluetoothGatt.this, status); + } + } + }); + } + + /** + * Remote device RSSI has been read + * @hide + */ + @Override + public void onReadRemoteRssi(String address, int rssi, int status) { + if (VDBG) { + Log.d(TAG, "onReadRemoteRssi() - Device=" + address + + " rssi=" + rssi + " status=" + status); + } + if (!address.equals(mDevice.getAddress())) { + return; + } + runOrQueueCallback(new Runnable() { + @Override + public void run() { + final BluetoothGattCallback callback = mCallback; + if (callback != null) { + callback.onReadRemoteRssi(BluetoothGatt.this, rssi, status); + } + } + }); + } + + /** + * Callback invoked when the MTU for a given connection changes + * @hide + */ + @Override + public void onConfigureMTU(String address, int mtu, int status) { + if (DBG) { + Log.d(TAG, "onConfigureMTU() - Device=" + address + + " mtu=" + mtu + " status=" + status); + } + if (!address.equals(mDevice.getAddress())) { + return; + } + + runOrQueueCallback(new Runnable() { + @Override + public void run() { + final BluetoothGattCallback callback = mCallback; + if (callback != null) { + callback.onMtuChanged(BluetoothGatt.this, mtu, status); + } + } + }); + } + + /** + * Callback invoked when the given connection is updated + * @hide + */ + @Override + public void onConnectionUpdated(String address, int interval, int latency, + int timeout, int status) { + if (DBG) { + Log.d(TAG, "onConnectionUpdated() - Device=" + address + + " interval=" + interval + " latency=" + latency + + " timeout=" + timeout + " status=" + status); + } + if (!address.equals(mDevice.getAddress())) { + return; + } + + runOrQueueCallback(new Runnable() { + @Override + public void run() { + final BluetoothGattCallback callback = mCallback; + if (callback != null) { + callback.onConnectionUpdated(BluetoothGatt.this, interval, latency, + timeout, status); + } + } + }); + } + + /** + * 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, int transport, + boolean opportunistic, int phy, AttributionSource attributionSource) { + mService = iGatt; + mDevice = device; + mTransport = transport; + mPhy = phy; + mOpportunistic = opportunistic; + mAttributionSource = attributionSource; + mServices = new ArrayList<BluetoothGattService>(); + + mConnState = CONN_STATE_IDLE; + mAuthRetryState = AUTH_RETRY_STATE_IDLE; + } + + /** + * Close this Bluetooth GATT client. + * + * 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. + * + * <p>This is an asynchronous call. The callback {@link BluetoothGattCallback#onAppRegistered} + * is used to notify success or failure if the function returns true. + * + * @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. + * + * <p>This is an asynchronous call. The callback {@link BluetoothGattCallback#onAppRegistered} + * is used to notify success or failure if the function returns true. + * + * @param callback GATT callback handler that will receive asynchronous callbacks. + * @param eatt_support indicate to allow for eatt support + * @return If true, the callback will be called to notify success or failure, false on immediate + * error + * @hide + */ + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) + private boolean registerApp(BluetoothGattCallback callback, Handler handler, + boolean eatt_support) { + if (DBG) Log.d(TAG, "registerApp()"); + 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, eatt_support, 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 (DBG) Log.d(TAG, "unregisterApp() - mClientIf=" + mClientIf); + if (mService == null || mClientIf == 0) return; + + 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. + * + * <p>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. + * + * <p>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 device Remote device to connect to + * @param autoConnect Whether to directly connect to the remote device (false) or to + * automatically connect as soon as the remote device becomes available (true). + * @return true, if the connection attempt was initiated successfully + */ + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + @RequiresLegacyBluetoothPermission + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) + /*package*/ boolean connect(Boolean autoConnect, BluetoothGattCallback callback, + Handler handler) { + if (DBG) { + Log.d(TAG, + "connect() - device: " + mDevice.getAddress() + ", 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.getAddress()); + 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. + * + * <p>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 { + // autoConnect is inverse of "isDirect" + mService.clientConnect(mClientIf, mDevice.getAddress(), false, 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. + * <p> + * {@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. + * + * <p>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.getAddress()); + 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.getAddress()); + 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. + * + * <p>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<BluetoothGattService> getServices() { + List<BluetoothGattService> result = + new ArrayList<BluetoothGattService>(); + + for (BluetoothGattService service : mServices) { + if (service.getDevice().equals(mDevice)) { + result.add(service); + } + } + + return result; + } + + /** + * Returns a {@link BluetoothGattService}, if the requested UUID is + * supported by the remote device. + * + * <p>This function requires that service discovery has been completed + * for the given device. + * + * <p>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. + * + * <p>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. + * + * <p>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. + * + * <p>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. + */ + @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_MISSING_BLUETOOTH_PRIVILEGED_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. + * + * <p>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, 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 (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) { + } + } + } catch (RemoteException e) { + Log.e(TAG, "", e); + synchronized (mDeviceBusyLock) { + mDeviceBusy = false; + } + throw e.rethrowFromSystemServer(); + } + + return requestStatus; + } + + /** + * Reads the value for a given descriptor from the associated remote device. + * + * <p>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. + * + * <p>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. + */ + @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. + * + * <p>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; + } + e.rethrowFromSystemServer(); + } + return BluetoothStatusCodes.ERROR_UNKNOWN; + } + + /** + * Initiates a reliable write transaction for a given remote device. + * + * <p>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. + * + * <p>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.getAddress()); + 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. + * + * <p>This function will commit all queued up characteristic write + * operations for a given remote device. + * + * <p>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.getAddress()); + 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. + * + * <p>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.getAddress()); + 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. + * + * <p>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.getAddress()); + 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. + * + * <p>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.getAddress()); + 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. + * + * <p>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. + * + * <p>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.getAddress() + + " 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. + * + * <p>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} + * or {@link BluetoothGatt#CONNECTION_PRIORITY_LOW_POWER}. + * @throws IllegalArgumentException If the parameters are outside of their specified range. + */ + @RequiresBluetoothConnectPermission + @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) + public boolean requestConnectionPriority(int connectionPriority) { + if (connectionPriority < CONNECTION_PRIORITY_BALANCED + || connectionPriority > CONNECTION_PRIORITY_LOW_POWER) { + 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. + * + * <p>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; + } + + /** + * @deprecated Not supported - please use {@link BluetoothManager#getConnectedDevices(int)} + * with {@link BluetoothProfile#GATT} as argument + * @throws UnsupportedOperationException + */ + @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 + */ + @Override + @RequiresNoPermission + @Deprecated + public List<BluetoothDevice> getConnectedDevices() { + throw new UnsupportedOperationException( + "Use BluetoothManager#getConnectedDevices instead."); + } + + /** + * @deprecated Not supported - please use + * {@link BluetoothManager#getDevicesMatchingConnectionStates(int, int[])} + * with {@link BluetoothProfile#GATT} as first argument + * + * @throws UnsupportedOperationException + */ + @Override + @RequiresNoPermission + @Deprecated + public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { + throw new UnsupportedOperationException( + "Use BluetoothManager#getDevicesMatchingConnectionStates instead."); + } +} |