/* * 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.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothProfile.ServiceListener; import android.bluetooth.IBluetoothManager; import android.bluetooth.IBluetoothStateChangeCallback; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.IBinder; import android.os.ParcelUuid; import android.os.RemoteException; import android.os.ServiceManager; import android.util.Log; 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. * *

BluetoothGatt is a proxy object for controlling the Bluetooth Service * via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get the * BluetoothGatt proxy object. * *

To connect to a remote peripheral device, create a {@link BluetoothGattCallback} * and call {@link #registerApp} to register your application. Gatt capable * devices can be discovered using the {@link #startScan} function or the * regular Bluetooth device discovery process. * @hide */ public final class BluetoothGatt implements BluetoothProfile { private static final String TAG = "BluetoothGatt"; private static final boolean DBG = true; private Context mContext; private ServiceListener mServiceListener; private BluetoothAdapter mAdapter; private IBluetoothGatt mService; private BluetoothGattCallback mCallback; private int mClientIf; private boolean mAuthRetry = false; private List 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; /** A write operation exceeds the maximum length of the attribute */ public static final int GATT_INVALID_ATTRIBUTE_LENGTH = 0xd; /** * No authentication required. * @hide */ /*package*/ static final int AUTHENTICATION_NONE = 0; /** * Authentication requested; no man-in-the-middle protection required. * @hide */ /*package*/ static final int AUTHENTICATION_NO_MITM = 1; /** * Authentication with man-in-the-middle protection requested. * @hide */ /*package*/ static final int AUTHENTICATION_MITM = 2; /** * Bluetooth state change handlers */ private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback = new IBluetoothStateChangeCallback.Stub() { public void onBluetoothStateChange(boolean up) { if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up); if (!up) { if (DBG) Log.d(TAG,"Unbinding service..."); synchronized (mConnection) { mService = null; mContext.unbindService(mConnection); } } else { synchronized (mConnection) { if (mService == null) { if (DBG) Log.d(TAG,"Binding service..."); if (!mContext.bindService(new Intent(IBluetoothGatt.class.getName()), mConnection, 0)) { Log.e(TAG, "Could not bind to Bluetooth GATT Service"); } } } } } }; /** * Service binder handling */ private ServiceConnection mConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { if (DBG) Log.d(TAG, "Proxy object connected"); mService = IBluetoothGatt.Stub.asInterface(service); ServiceListener serviceListener = mServiceListener; if (serviceListener != null) { serviceListener.onServiceConnected(BluetoothProfile.GATT, BluetoothGatt.this); } } public void onServiceDisconnected(ComponentName className) { if (DBG) Log.d(TAG, "Proxy object disconnected"); mService = null; ServiceListener serviceListener = mServiceListener; if (serviceListener != null) { serviceListener.onServiceDisconnected(BluetoothProfile.GATT); } } }; /** * Bluetooth GATT interface callbacks */ private final IBluetoothGattCallback mBluetoothGattCallback = new IBluetoothGattCallback.Stub() { /** * Application interface registered - app is ready to go * @hide */ public void onClientRegistered(int status, int clientIf) { if (DBG) Log.d(TAG, "onClientRegistered() - status=" + status + " clientIf=" + clientIf); mClientIf = clientIf; try { mCallback.onAppRegistered(status); } catch (Exception ex) { Log.w(TAG, "Unhandled exception: " + ex); } } /** * Client connection state changed * @hide */ public void onClientConnectionState(int status, int clientIf, boolean connected, String address) { if (DBG) Log.d(TAG, "onClientConnectionState() - status=" + status + " clientIf=" + clientIf + " device=" + address); try { mCallback.onConnectionStateChange(mAdapter.getRemoteDevice(address), status, connected ? BluetoothProfile.STATE_CONNECTED : BluetoothProfile.STATE_DISCONNECTED); } catch (Exception ex) { Log.w(TAG, "Unhandled exception: " + ex); } } /** * Callback reporting an LE scan result. * @hide */ public void onScanResult(String address, int rssi, byte[] advData) { if (DBG) Log.d(TAG, "onScanResult() - Device=" + address + " RSSI=" +rssi); try { mCallback.onScanResult(mAdapter.getRemoteDevice(address), rssi, advData); } catch (Exception ex) { Log.w(TAG, "Unhandled exception: " + ex); } } /** * A new GATT service has been discovered. * The service is added to the internal list and the search * continues. * @hide */ public void onGetService(String address, int srvcType, int srvcInstId, ParcelUuid srvcUuid) { if (DBG) Log.d(TAG, "onGetService() - Device=" + address + " UUID=" + srvcUuid); BluetoothDevice device = mAdapter.getRemoteDevice(address); mServices.add(new BluetoothGattService(device, srvcUuid.getUuid(), srvcInstId, srvcType)); } /** * An included service has been found durig GATT discovery. * The included service is added to the respective parent. * @hide */ public void onGetIncludedService(String address, int srvcType, int srvcInstId, ParcelUuid srvcUuid, int inclSrvcType, int inclSrvcInstId, ParcelUuid inclSrvcUuid) { if (DBG) Log.d(TAG, "onGetIncludedService() - Device=" + address + " UUID=" + srvcUuid + " Included=" + inclSrvcUuid); BluetoothDevice device = mAdapter.getRemoteDevice(address); BluetoothGattService service = getService(device, srvcUuid.getUuid(), srvcInstId, srvcType); BluetoothGattService includedService = getService(device, inclSrvcUuid.getUuid(), inclSrvcInstId, inclSrvcType); if (service != null && includedService != null) { service.addIncludedService(includedService); } } /** * A new GATT characteristic has been discovered. * Add the new characteristic to the relevant service and continue * the remote device inspection. * @hide */ public void onGetCharacteristic(String address, int srvcType, int srvcInstId, ParcelUuid srvcUuid, int charInstId, ParcelUuid charUuid, int charProps) { if (DBG) Log.d(TAG, "onGetCharacteristic() - Device=" + address + " UUID=" + charUuid); BluetoothDevice device = mAdapter.getRemoteDevice(address); BluetoothGattService service = getService(device, srvcUuid.getUuid(), srvcInstId, srvcType); if (service != null) { service.addCharacteristic(new BluetoothGattCharacteristic( service, charUuid.getUuid(), charInstId, charProps, 0)); } } /** * A new GATT descriptor has been discovered. * Finally, add the descriptor to the related characteristic. * This should conclude the remote device update. * @hide */ public void onGetDescriptor(String address, int srvcType, int srvcInstId, ParcelUuid srvcUuid, int charInstId, ParcelUuid charUuid, ParcelUuid descUuid) { if (DBG) Log.d(TAG, "onGetDescriptor() - Device=" + address + " UUID=" + descUuid); BluetoothDevice device = mAdapter.getRemoteDevice(address); BluetoothGattService service = getService(device, srvcUuid.getUuid(), srvcInstId, srvcType); if (service == null) return; BluetoothGattCharacteristic characteristic = service.getCharacteristic( charUuid.getUuid()); if (characteristic == null) return; characteristic.addDescriptor(new BluetoothGattDescriptor( characteristic, descUuid.getUuid(), 0)); } /** * 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 */ public void onSearchComplete(String address, int status) { if (DBG) Log.d(TAG, "onSearchComplete() = Device=" + address + " Status=" + status); BluetoothDevice device = mAdapter.getRemoteDevice(address); try { mCallback.onServicesDiscovered(device, status); } catch (Exception ex) { Log.w(TAG, "Unhandled exception: " + ex); } } /** * Remote characteristic has been read. * Updates the internal value. * @hide */ public void onCharacteristicRead(String address, int status, int srvcType, int srvcInstId, ParcelUuid srvcUuid, int charInstId, ParcelUuid charUuid, byte[] value) { if (DBG) Log.d(TAG, "onCharacteristicRead() - Device=" + address + " UUID=" + charUuid + " Status=" + status); if ((status == GATT_INSUFFICIENT_AUTHENTICATION || status == GATT_INSUFFICIENT_ENCRYPTION) && mAuthRetry == false) { try { mAuthRetry = true; mService.readCharacteristic(mClientIf, address, srvcType, srvcInstId, srvcUuid, charInstId, charUuid, AUTHENTICATION_MITM); return; } catch (RemoteException e) { Log.e(TAG,"",e); } } mAuthRetry = false; BluetoothDevice device = mAdapter.getRemoteDevice(address); BluetoothGattService service = getService(device, srvcUuid.getUuid(), srvcInstId, srvcType); if (service == null) return; BluetoothGattCharacteristic characteristic = service.getCharacteristic( charUuid.getUuid(), charInstId); if (characteristic == null) return; if (status == 0) characteristic.setValue(value); try { mCallback.onCharacteristicRead(characteristic, status); } catch (Exception ex) { Log.w(TAG, "Unhandled exception: " + ex); } } /** * Characteristic has been written to the remote device. * Let the app know how we did... * @hide */ public void onCharacteristicWrite(String address, int status, int srvcType, int srvcInstId, ParcelUuid srvcUuid, int charInstId, ParcelUuid charUuid) { if (DBG) Log.d(TAG, "onCharacteristicWrite() - Device=" + address + " UUID=" + charUuid + " Status=" + status); BluetoothDevice device = mAdapter.getRemoteDevice(address); BluetoothGattService service = getService(device, srvcUuid.getUuid(), srvcInstId, srvcType); if (service == null) return; BluetoothGattCharacteristic characteristic = service.getCharacteristic( charUuid.getUuid(), charInstId); if (characteristic == null) return; if ((status == GATT_INSUFFICIENT_AUTHENTICATION || status == GATT_INSUFFICIENT_ENCRYPTION) && mAuthRetry == false) { try { mAuthRetry = true; mService.writeCharacteristic(mClientIf, address, srvcType, srvcInstId, srvcUuid, charInstId, charUuid, characteristic.getWriteType(), AUTHENTICATION_MITM, characteristic.getValue()); return; } catch (RemoteException e) { Log.e(TAG,"",e); } } mAuthRetry = false; try { mCallback.onCharacteristicWrite(characteristic, status); } catch (Exception ex) { Log.w(TAG, "Unhandled exception: " + ex); } } /** * Remote characteristic has been updated. * Updates the internal value. * @hide */ public void onNotify(String address, int srvcType, int srvcInstId, ParcelUuid srvcUuid, int charInstId, ParcelUuid charUuid, byte[] value) { if (DBG) Log.d(TAG, "onNotify() - Device=" + address + " UUID=" + charUuid); BluetoothDevice device = mAdapter.getRemoteDevice(address); BluetoothGattService service = getService(device, srvcUuid.getUuid(), srvcInstId, srvcType); if (service == null) return; BluetoothGattCharacteristic characteristic = service.getCharacteristic( charUuid.getUuid(), charInstId); if (characteristic == null) return; characteristic.setValue(value); try { mCallback.onCharacteristicChanged(characteristic); } catch (Exception ex) { Log.w(TAG, "Unhandled exception: " + ex); } } /** * Descriptor has been read. * @hide */ public void onDescriptorRead(String address, int status, int srvcType, int srvcInstId, ParcelUuid srvcUuid, int charInstId, ParcelUuid charUuid, ParcelUuid descrUuid, byte[] value) { if (DBG) Log.d(TAG, "onDescriptorRead() - Device=" + address + " UUID=" + charUuid); BluetoothDevice device = mAdapter.getRemoteDevice(address); BluetoothGattService service = getService(device, srvcUuid.getUuid(), srvcInstId, srvcType); if (service == null) return; BluetoothGattCharacteristic characteristic = service.getCharacteristic( charUuid.getUuid(), charInstId); if (characteristic == null) return; BluetoothGattDescriptor descriptor = characteristic.getDescriptor( descrUuid.getUuid()); if (descriptor == null) return; if (status == 0) descriptor.setValue(value); if ((status == GATT_INSUFFICIENT_AUTHENTICATION || status == GATT_INSUFFICIENT_ENCRYPTION) && mAuthRetry == false) { try { mAuthRetry = true; mService.readDescriptor(mClientIf, address, srvcType, srvcInstId, srvcUuid, charInstId, charUuid, descrUuid, AUTHENTICATION_MITM); } catch (RemoteException e) { Log.e(TAG,"",e); } } mAuthRetry = true; try { mCallback.onDescriptorRead(descriptor, status); } catch (Exception ex) { Log.w(TAG, "Unhandled exception: " + ex); } } /** * Descriptor write operation complete. * @hide */ public void onDescriptorWrite(String address, int status, int srvcType, int srvcInstId, ParcelUuid srvcUuid, int charInstId, ParcelUuid charUuid, ParcelUuid descrUuid) { if (DBG) Log.d(TAG, "onDescriptorWrite() - Device=" + address + " UUID=" + charUuid); BluetoothDevice device = mAdapter.getRemoteDevice(address); BluetoothGattService service = getService(device, srvcUuid.getUuid(), srvcInstId, srvcType); if (service == null) return; BluetoothGattCharacteristic characteristic = service.getCharacteristic( charUuid.getUuid(), charInstId); if (characteristic == null) return; BluetoothGattDescriptor descriptor = characteristic.getDescriptor( descrUuid.getUuid()); if (descriptor == null) return; if ((status == GATT_INSUFFICIENT_AUTHENTICATION || status == GATT_INSUFFICIENT_ENCRYPTION) && mAuthRetry == false) { try { mAuthRetry = true; mService.writeDescriptor(mClientIf, address, srvcType, srvcInstId, srvcUuid, charInstId, charUuid, descrUuid, characteristic.getWriteType(), AUTHENTICATION_MITM, descriptor.getValue()); } catch (RemoteException e) { Log.e(TAG,"",e); } } mAuthRetry = false; try { mCallback.onDescriptorWrite(descriptor, status); } catch (Exception ex) { Log.w(TAG, "Unhandled exception: " + ex); } } /** * Prepared write transaction completed (or aborted) * @hide */ public void onExecuteWrite(String address, int status) { if (DBG) Log.d(TAG, "onExecuteWrite() - Device=" + address + " status=" + status); BluetoothDevice device = mAdapter.getRemoteDevice(address); try { mCallback.onReliableWriteCompleted(device, status); } catch (Exception ex) { Log.w(TAG, "Unhandled exception: " + ex); } } /** * Remote device RSSI has been read * @hide */ public void onReadRemoteRssi(String address, int rssi, int status) { if (DBG) Log.d(TAG, "onReadRemoteRssi() - Device=" + address + " rssi=" + rssi + " status=" + status); BluetoothDevice device = mAdapter.getRemoteDevice(address); try { mCallback.onReadRemoteRssi(device, rssi, status); } catch (Exception ex) { Log.w(TAG, "Unhandled exception: " + ex); } } }; /** * Create a BluetoothGatt proxy object. */ /*package*/ BluetoothGatt(Context context, ServiceListener l) { mContext = context; mServiceListener = l; mAdapter = BluetoothAdapter.getDefaultAdapter(); mServices = new ArrayList(); IBinder b = ServiceManager.getService(BluetoothAdapter.BLUETOOTH_MANAGER_SERVICE); if (b != null) { IBluetoothManager mgr = IBluetoothManager.Stub.asInterface(b); try { mgr.registerStateChangeCallback(mBluetoothStateChangeCallback); } catch (RemoteException re) { Log.e(TAG, "Unable to register BluetoothStateChangeCallback", re); } } else { Log.e(TAG, "Unable to get BluetoothManager interface."); throw new RuntimeException("BluetoothManager inactive"); } //Bind to the service only if the Bluetooth is ON if(mAdapter.isEnabled()){ if (!context.bindService(new Intent(IBluetoothGatt.class.getName()), mConnection, 0)) { Log.e(TAG, "Could not bind to Bluetooth Gatt Service"); } } } /** * Close the connection to the gatt service. */ /*package*/ void close() { if (DBG) Log.d(TAG, "close()"); unregisterApp(); mServiceListener = null; IBinder b = ServiceManager.getService(BluetoothAdapter.BLUETOOTH_MANAGER_SERVICE); if (b != null) { IBluetoothManager mgr = IBluetoothManager.Stub.asInterface(b); try { mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback); } catch (RemoteException re) { Log.e(TAG, "Unable to unregister BluetoothStateChangeCallback", re); } } synchronized (mConnection) { if (mService != null) { mService = null; mContext.unbindService(mConnection); } } } /** * Returns a service by UUID, instance and type. * @hide */ /*package*/ BluetoothGattService getService(BluetoothDevice device, UUID uuid, int instanceId, int type) { for(BluetoothGattService svc : mServices) { if (svc.getDevice().equals(device) && svc.getType() == type && svc.getInstanceId() == instanceId && svc.getUuid().equals(uuid)) { return svc; } } return null; } /** * Register an application callback to start using Gatt. * *

This is an asynchronous call. The callback is used to notify * success or failure if the function returns true. * *

Requires {@link android.Manifest.permission#BLUETOOTH} permission. * * @param callback Gatt callback handler that will receive asynchronous * callbacks. * @return true, if application was successfully registered. */ public boolean registerApp(BluetoothGattCallback callback) { if (DBG) Log.d(TAG, "registerApp()"); if (mService == null) return false; mCallback = callback; UUID uuid = UUID.randomUUID(); if (DBG) Log.d(TAG, "registerApp() - UUID=" + uuid); try { mService.registerClient(new ParcelUuid(uuid), mBluetoothGattCallback); } catch (RemoteException e) { Log.e(TAG,"",e); return false; } return true; } /** * Unregister the current application and callbacks. */ public void unregisterApp() { if (DBG) Log.d(TAG, "unregisterApp() - mClientIf=" + mClientIf); if (mService == null || mClientIf == 0) return; try { mCallback = null; mService.unregisterClient(mClientIf); mClientIf = 0; } catch (RemoteException e) { Log.e(TAG,"",e); } } /** * Starts a scan for Bluetooth LE devices. * *

Results of the scan are reported using the * {@link BluetoothGattCallback#onScanResult} callback. * *

Requires {@link android.Manifest.permission#BLUETOOTH} permission. * * @return true, if the scan was started successfully */ public boolean startScan() { if (DBG) Log.d(TAG, "startScan()"); if (mService == null || mClientIf == 0) return false; try { mService.startScan(mClientIf, false); } catch (RemoteException e) { Log.e(TAG,"",e); return false; } return true; } /** * Starts a scan for Bluetooth LE devices, looking for devices that * advertise given services. * *

Devices which advertise all specified services are reported using the * {@link BluetoothGattCallback#onScanResult} callback. * *

Requires {@link android.Manifest.permission#BLUETOOTH} permission. * * @param serviceUuids Array of services to look for * @return true, if the scan was started successfully */ public boolean startScan(UUID[] serviceUuids) { if (DBG) Log.d(TAG, "startScan() - with UUIDs"); if (mService == null || mClientIf == 0) return false; try { ParcelUuid[] uuids = new ParcelUuid[serviceUuids.length]; for(int i = 0; i != uuids.length; ++i) { uuids[i] = new ParcelUuid(serviceUuids[i]); } mService.startScanWithUuids(mClientIf, false, uuids); } catch (RemoteException e) { Log.e(TAG,"",e); return false; } return true; } /** * Stops an ongoing Bluetooth LE device scan. * *

Requires {@link android.Manifest.permission#BLUETOOTH} permission. */ public void stopScan() { if (DBG) Log.d(TAG, "stopScan()"); if (mService == null || mClientIf == 0) return; try { mService.stopScan(mClientIf, false); } 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 paramter 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 false. * *

Requires {@link android.Manifest.permission#BLUETOOTH} permission. * * @param device Remote device to connect to * @param autoConnect Whether to directly connect to the remote device (false) * or to automatically connect as soon as the remote * device becomes available (true). * @return true, if the connection attempt was initiated successfully */ public boolean connect(BluetoothDevice device, boolean autoConnect) { if (DBG) Log.d(TAG, "connect() - device: " + device.getAddress() + ", auto: " + autoConnect); if (mService == null || mClientIf == 0) return false; try { mService.clientConnect(mClientIf, device.getAddress(), autoConnect ? false : true); // autoConnect is inverse of "isDirect" } catch (RemoteException e) { Log.e(TAG,"",e); return false; } return true; } /** * Disconnects an established connection, or cancels a connection attempt * currently in progress. * *

Requires {@link android.Manifest.permission#BLUETOOTH} permission. * * @param device Remote device */ public void cancelConnection(BluetoothDevice device) { if (DBG) Log.d(TAG, "cancelOpen() - device: " + device.getAddress()); if (mService == null || mClientIf == 0) return; try { mService.clientDisconnect(mClientIf, device.getAddress()); } catch (RemoteException e) { Log.e(TAG,"",e); } } /** * 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. * *

Requires {@link android.Manifest.permission#BLUETOOTH} permission. * * @param device Remote device to explore * @return true, if the remote service discovery has been started */ public boolean discoverServices(BluetoothDevice device) { if (DBG) Log.d(TAG, "discoverServices() - device: " + device.getAddress()); if (mService == null || mClientIf == 0) return false; mServices.clear(); try { mService.discoverServices(mClientIf, device.getAddress()); } 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. * *

Requires {@link android.Manifest.permission#BLUETOOTH} permission. * * @param device Remote device * @return List of services on the remote device. Returns an empty list * if service discovery has not yet been performed. */ public List getServices(BluetoothDevice device) { List result = new ArrayList(); for (BluetoothGattService service : mServices) { if (service.getDevice().equals(device)) { result.add(service); } } return result; } /** * Returns a {@link BluetoothGattService}, if the requested UUID is * supported by the remote device. * *

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. * *

Requires {@link android.Manifest.permission#BLUETOOTH} permission. * * @param device Remote device * @param uuid UUID of the requested service * @return BluetoothGattService if supported, or null if the requested * service is not offered by the remote device. */ public BluetoothGattService getService(BluetoothDevice device, UUID uuid) { for (BluetoothGattService service : mServices) { if (service.getDevice().equals(device) && 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} * callback. * *

Requires {@link android.Manifest.permission#BLUETOOTH} permission. * * @param characteristic Characteristic to read from the remote device * @return true, if the read operation was initiated successfully */ public boolean readCharacteristic(BluetoothGattCharacteristic characteristic) { if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_READ) == 0) return false; if (DBG) 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; try { mService.readCharacteristic(mClientIf, device.getAddress(), service.getType(), service.getInstanceId(), new ParcelUuid(service.getUuid()), characteristic.getInstanceId(), new ParcelUuid(characteristic.getUuid()), AUTHENTICATION_NONE); } catch (RemoteException e) { Log.e(TAG,"",e); return false; } return true; } /** * Writes a given characteristic and it's 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. * *

Requires {@link android.Manifest.permission#BLUETOOTH} permission. * * @param characteristic Characteristic to write on the remote device * @return true, if the write operation was initiated successfully */ public boolean writeCharacteristic(BluetoothGattCharacteristic characteristic) { if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_WRITE) == 0 && (characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) == 0) return false; if (DBG) Log.d(TAG, "writeCharacteristic() - 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; try { mService.writeCharacteristic(mClientIf, device.getAddress(), service.getType(), service.getInstanceId(), new ParcelUuid(service.getUuid()), characteristic.getInstanceId(), new ParcelUuid(characteristic.getUuid()), characteristic.getWriteType(), AUTHENTICATION_NONE, characteristic.getValue()); } catch (RemoteException e) { Log.e(TAG,"",e); return false; } return true; } /** * 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. * *

Requires {@link android.Manifest.permission#BLUETOOTH} permission. * * @param descriptor Descriptor value to read from the remote device * @return true, if the read operation was initiated successfully */ public boolean readDescriptor(BluetoothGattDescriptor descriptor) { if (DBG) 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; try { mService.readDescriptor(mClientIf, device.getAddress(), service.getType(), service.getInstanceId(), new ParcelUuid(service.getUuid()), characteristic.getInstanceId(), new ParcelUuid(characteristic.getUuid()), new ParcelUuid(descriptor.getUuid()), AUTHENTICATION_NONE); } catch (RemoteException e) { Log.e(TAG,"",e); 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. * *

Requires {@link android.Manifest.permission#BLUETOOTH} permission. * * @param descriptor Descriptor to write to the associated remote device * @return true, if the write operation was initiated successfully */ public boolean writeDescriptor(BluetoothGattDescriptor descriptor) { if (DBG) Log.d(TAG, "writeDescriptor() - 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; try { mService.writeDescriptor(mClientIf, device.getAddress(), service.getType(), service.getInstanceId(), new ParcelUuid(service.getUuid()), characteristic.getInstanceId(), new ParcelUuid(characteristic.getUuid()), new ParcelUuid(descriptor.getUuid()), characteristic.getWriteType(), AUTHENTICATION_NONE, descriptor.getValue()); } catch (RemoteException e) { Log.e(TAG,"",e); return false; } return true; } /** * 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 an {@link BluetoothGattCallback#onCharacteristicWrite} callback * in response to every {@link #writeCharacteristic} 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 commiting any values on the * remote device. * *

Requires {@link android.Manifest.permission#BLUETOOTH} permission. * * @param device Remote device * @return true, if the reliable write transaction has been initiated */ public boolean beginReliableWrite(BluetoothDevice device) { if (DBG) Log.d(TAG, "beginReliableWrite() - device: " + device.getAddress()); if (mService == null || mClientIf == 0) return false; try { mService.beginReliableWrite(mClientIf, device.getAddress()); } 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. * *

Requires {@link android.Manifest.permission#BLUETOOTH} permission. * * @param device Remote device * @return true, if the request to execute the transaction has been sent */ public boolean executeReliableWrite(BluetoothDevice device) { if (DBG) Log.d(TAG, "executeReliableWrite() - device: " + device.getAddress()); if (mService == null || mClientIf == 0) return false; try { mService.endReliableWrite(mClientIf, device.getAddress(), true); } catch (RemoteException e) { Log.e(TAG,"",e); 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. * *

Requires {@link android.Manifest.permission#BLUETOOTH} permission. * * @param device Remote device */ public void abortReliableWrite(BluetoothDevice device) { if (DBG) Log.d(TAG, "abortReliableWrite() - device: " + device.getAddress()); if (mService == null || mClientIf == 0) return; try { mService.endReliableWrite(mClientIf, device.getAddress(), false); } catch (RemoteException e) { Log.e(TAG,"",e); } } /** * Enable or disable notifications/indications for a given characteristic. * *

Once notifications are enabled for a characteristic, a * {@link BluetoothGattCallback#onCharacteristicChanged} callback will be * triggered if the remote device indicates that the given characteristic * has changed. * *

Requires {@link android.Manifest.permission#BLUETOOTH} permission. * * @param characteristic The characteristic for which to enable notifications * @param enable Set to true to enable notifications/indications * @return true, if the requested notification status was set successfully */ 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(), service.getType(), service.getInstanceId(), new ParcelUuid(service.getUuid()), characteristic.getInstanceId(), new ParcelUuid(characteristic.getUuid()), enable); } 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 */ public boolean refresh(BluetoothDevice device) { if (DBG) Log.d(TAG, "refresh() - device: " + device.getAddress()); if (mService == null || mClientIf == 0) return false; try { mService.refreshDevice(mClientIf, device.getAddress()); } 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. * *

Requires {@link android.Manifest.permission#BLUETOOTH} permission. * * @param device Remote device * @return true, if the RSSI value has been requested successfully */ public boolean readRemoteRssi(BluetoothDevice device) { if (DBG) Log.d(TAG, "readRssi() - device: " + device.getAddress()); if (mService == null || mClientIf == 0) return false; try { mService.readRemoteRssi(mClientIf, device.getAddress()); } catch (RemoteException e) { Log.e(TAG,"",e); return false; } return true; } /** * Get the current connection state of the profile. * *

This is not specific to any application configuration but represents * the connection state of the local Bluetooth adapter for this profile. * This can be used by applications like status bar which would just like * to know the state of the local adapter. * *

Requires {@link android.Manifest.permission#BLUETOOTH} permission. * * @param device Remote bluetooth device. * @return State of the profile connection. One of * {@link #STATE_CONNECTED}, {@link #STATE_CONNECTING}, * {@link #STATE_DISCONNECTED}, {@link #STATE_DISCONNECTING} */ @Override public int getConnectionState(BluetoothDevice device) { if (DBG) Log.d(TAG,"getConnectionState()"); if (mService == null) return STATE_DISCONNECTED; List connectedDevices = getConnectedDevices(); for(BluetoothDevice connectedDevice : connectedDevices) { if (device.equals(connectedDevice)) { return STATE_CONNECTED; } } return STATE_DISCONNECTED; } /** * Get connected devices for the Gatt profile. * *

Return the set of devices which are in state {@link #STATE_CONNECTED} * *

This is not specific to any application configuration but represents * the connection state of the local Bluetooth adapter for this profile. * This can be used by applications like status bar which would just like * to know the state of the local adapter. * *

Requires {@link android.Manifest.permission#BLUETOOTH} permission. * * @return List of devices. The list will be empty on error. */ @Override public List getConnectedDevices() { if (DBG) Log.d(TAG,"getConnectedDevices"); List connectedDevices = new ArrayList(); if (mService == null) return connectedDevices; try { connectedDevices = mService.getDevicesMatchingConnectionStates( new int[] { BluetoothProfile.STATE_CONNECTED }); } catch (RemoteException e) { Log.e(TAG,"",e); } return connectedDevices; } /** * Get a list of devices that match any of the given connection * states. * *

If none of the devices match any of the given states, * an empty list will be returned. * *

This is not specific to any application configuration but represents * the connection state of the local Bluetooth adapter for this profile. * This can be used by applications like status bar which would just like * to know the state of the local adapter. * *

Requires {@link android.Manifest.permission#BLUETOOTH} permission. * * @param states Array of states. States can be one of * {@link #STATE_CONNECTED}, {@link #STATE_CONNECTING}, * {@link #STATE_DISCONNECTED}, {@link #STATE_DISCONNECTING}, * @return List of devices. The list will be empty on error. */ @Override public List getDevicesMatchingConnectionStates(int[] states) { if (DBG) Log.d(TAG,"getDevicesMatchingConnectionStates"); List devices = new ArrayList(); if (mService == null) return devices; try { devices = mService.getDevicesMatchingConnectionStates(states); } catch (RemoteException e) { Log.e(TAG,"",e); } return devices; } }