| /* |
| * 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. |
| * |
| * <p>This class provides Bluetooth Gatt functionality to enable communication |
| * with Bluetooth Smart or Smart Ready devices. |
| * |
| * <p>BluetoothGatt is a proxy object for controlling the Bluetooth Service |
| * via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get the |
| * BluetoothGatt proxy object. |
| * |
| * <p>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<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; |
| |
| /** 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<BluetoothGattService>(); |
| |
| 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. |
| * |
| * <p>This is an asynchronous call. The callback is used to notify |
| * success or failure if the function returns true. |
| * |
| * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. |
| * |
| * @param callback Gatt callback handler that will receive asynchronous |
| * callbacks. |
| * @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. |
| * |
| * <p>Results of the scan are reported using the |
| * {@link BluetoothGattCallback#onScanResult} callback. |
| * |
| * <p>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. |
| * |
| * <p>Devices which advertise all specified services are reported using the |
| * {@link BluetoothGattCallback#onScanResult} callback. |
| * |
| * <p>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. |
| * |
| * <p>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. |
| * |
| * <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 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. |
| * |
| * <p>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. |
| * |
| * <p>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. |
| * |
| * <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. |
| * |
| * <p>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. |
| * |
| * <p>This function requires that service discovery has been completed |
| * for the given device. |
| * |
| * <p>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<BluetoothGattService> getServices(BluetoothDevice device) { |
| List<BluetoothGattService> result = |
| new ArrayList<BluetoothGattService>(); |
| |
| 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. |
| * |
| * <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. |
| * |
| * <p>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. |
| * |
| * <p>This is an asynchronous operation. The result of the read operation |
| * is reported by the {@link BluetoothGattCallback#onCharacteristicRead} |
| * callback. |
| * |
| * <p>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. |
| * |
| * <p>Once the write operation has been completed, the |
| * {@link BluetoothGattCallback#onCharacteristicWrite} callback is invoked, |
| * reporting the result of the operation. |
| * |
| * <p>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. |
| * |
| * <p>Once the read operation has been completed, the |
| * {@link BluetoothGattCallback#onDescriptorRead} callback is |
| * triggered, signaling the result of the operation. |
| * |
| * <p>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. |
| * |
| * <p>A {@link BluetoothGattCallback#onDescriptorWrite} callback is |
| * triggered to report the result of the write operation. |
| * |
| * <p>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. |
| * |
| * <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 an {@link BluetoothGattCallback#onCharacteristicWrite} callback |
| * in response to every {@link #writeCharacteristic} 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 commiting any values on the |
| * remote device. |
| * |
| * <p>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. |
| * |
| * <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. |
| * |
| * <p>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. |
| * |
| * <p>Calling this function will discard all queued characteristic write |
| * operations for a given remote device. |
| * |
| * <p>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. |
| * |
| * <p>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. |
| * |
| * <p>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. |
| * |
| * <p>The {@link BluetoothGattCallback#onReadRemoteRssi} callback will be |
| * invoked when the RSSI value has been read. |
| * |
| * <p>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. |
| * |
| * <p>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. |
| * |
| * <p>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<BluetoothDevice> connectedDevices = getConnectedDevices(); |
| for(BluetoothDevice connectedDevice : connectedDevices) { |
| if (device.equals(connectedDevice)) { |
| return STATE_CONNECTED; |
| } |
| } |
| |
| return STATE_DISCONNECTED; |
| } |
| |
| /** |
| * Get connected devices for the Gatt profile. |
| * |
| * <p> Return the set of devices which are in state {@link #STATE_CONNECTED} |
| * |
| * <p>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. |
| * |
| * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission. |
| * |
| * @return List of devices. The list will be empty on error. |
| */ |
| @Override |
| public List<BluetoothDevice> getConnectedDevices() { |
| if (DBG) Log.d(TAG,"getConnectedDevices"); |
| |
| List<BluetoothDevice> connectedDevices = new ArrayList<BluetoothDevice>(); |
| 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. |
| * |
| * <p> If none of the devices match any of the given states, |
| * an empty list will be returned. |
| * |
| * <p>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. |
| * |
| * <p>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<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { |
| if (DBG) Log.d(TAG,"getDevicesMatchingConnectionStates"); |
| |
| List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>(); |
| if (mService == null) return devices; |
| |
| try { |
| devices = mService.getDevicesMatchingConnectionStates(states); |
| } catch (RemoteException e) { |
| Log.e(TAG,"",e); |
| } |
| |
| return devices; |
| } |
| } |