Home | History | Annotate | Download | only in com.example.android.bluetoothlegatt
      1 /*
      2  * Copyright (C) 2013 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.example.android.bluetoothlegatt;
     18 
     19 import android.app.Service;
     20 import android.bluetooth.BluetoothAdapter;
     21 import android.bluetooth.BluetoothDevice;
     22 import android.bluetooth.BluetoothGatt;
     23 import android.bluetooth.BluetoothGattCallback;
     24 import android.bluetooth.BluetoothGattCharacteristic;
     25 import android.bluetooth.BluetoothGattDescriptor;
     26 import android.bluetooth.BluetoothGattService;
     27 import android.bluetooth.BluetoothManager;
     28 import android.bluetooth.BluetoothProfile;
     29 import android.content.Context;
     30 import android.content.Intent;
     31 import android.os.Binder;
     32 import android.os.IBinder;
     33 import android.util.Log;
     34 
     35 import java.util.List;
     36 import java.util.UUID;
     37 
     38 /**
     39  * Service for managing connection and data communication with a GATT server hosted on a
     40  * given Bluetooth LE device.
     41  */
     42 public class BluetoothLeService extends Service {
     43     private final static String TAG = BluetoothLeService.class.getSimpleName();
     44 
     45     private BluetoothManager mBluetoothManager;
     46     private BluetoothAdapter mBluetoothAdapter;
     47     private String mBluetoothDeviceAddress;
     48     private BluetoothGatt mBluetoothGatt;
     49     private int mConnectionState = STATE_DISCONNECTED;
     50 
     51     private static final int STATE_DISCONNECTED = 0;
     52     private static final int STATE_CONNECTING = 1;
     53     private static final int STATE_CONNECTED = 2;
     54 
     55     public final static String ACTION_GATT_CONNECTED =
     56             "com.example.bluetooth.le.ACTION_GATT_CONNECTED";
     57     public final static String ACTION_GATT_DISCONNECTED =
     58             "com.example.bluetooth.le.ACTION_GATT_DISCONNECTED";
     59     public final static String ACTION_GATT_SERVICES_DISCOVERED =
     60             "com.example.bluetooth.le.ACTION_GATT_SERVICES_DISCOVERED";
     61     public final static String ACTION_DATA_AVAILABLE =
     62             "com.example.bluetooth.le.ACTION_DATA_AVAILABLE";
     63     public final static String EXTRA_DATA =
     64             "com.example.bluetooth.le.EXTRA_DATA";
     65 
     66     public final static UUID UUID_HEART_RATE_MEASUREMENT =
     67             UUID.fromString(SampleGattAttributes.HEART_RATE_MEASUREMENT);
     68 
     69     // Implements callback methods for GATT events that the app cares about.  For example,
     70     // connection change and services discovered.
     71     private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
     72         @Override
     73         public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
     74             String intentAction;
     75             if (newState == BluetoothProfile.STATE_CONNECTED) {
     76                 intentAction = ACTION_GATT_CONNECTED;
     77                 mConnectionState = STATE_CONNECTED;
     78                 broadcastUpdate(intentAction);
     79                 Log.i(TAG, "Connected to GATT server.");
     80                 // Attempts to discover services after successful connection.
     81                 Log.i(TAG, "Attempting to start service discovery:" +
     82                         mBluetoothGatt.discoverServices());
     83 
     84             } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
     85                 intentAction = ACTION_GATT_DISCONNECTED;
     86                 mConnectionState = STATE_DISCONNECTED;
     87                 Log.i(TAG, "Disconnected from GATT server.");
     88                 broadcastUpdate(intentAction);
     89             }
     90         }
     91 
     92         @Override
     93         public void onServicesDiscovered(BluetoothGatt gatt, int status) {
     94             if (status == BluetoothGatt.GATT_SUCCESS) {
     95                 broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED);
     96             } else {
     97                 Log.w(TAG, "onServicesDiscovered received: " + status);
     98             }
     99         }
    100 
    101         @Override
    102         public void onCharacteristicRead(BluetoothGatt gatt,
    103                                          BluetoothGattCharacteristic characteristic,
    104                                          int status) {
    105             if (status == BluetoothGatt.GATT_SUCCESS) {
    106                 broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
    107             }
    108         }
    109 
    110         @Override
    111         public void onCharacteristicChanged(BluetoothGatt gatt,
    112                                             BluetoothGattCharacteristic characteristic) {
    113             broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
    114         }
    115     };
    116 
    117     private void broadcastUpdate(final String action) {
    118         final Intent intent = new Intent(action);
    119         sendBroadcast(intent);
    120     }
    121 
    122     private void broadcastUpdate(final String action,
    123                                  final BluetoothGattCharacteristic characteristic) {
    124         final Intent intent = new Intent(action);
    125 
    126         // This is special handling for the Heart Rate Measurement profile.  Data parsing is
    127         // carried out as per profile specifications:
    128         // http://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.heart_rate_measurement.xml
    129         if (UUID_HEART_RATE_MEASUREMENT.equals(characteristic.getUuid())) {
    130             int flag = characteristic.getProperties();
    131             int format = -1;
    132             if ((flag & 0x01) != 0) {
    133                 format = BluetoothGattCharacteristic.FORMAT_UINT16;
    134                 Log.d(TAG, "Heart rate format UINT16.");
    135             } else {
    136                 format = BluetoothGattCharacteristic.FORMAT_UINT8;
    137                 Log.d(TAG, "Heart rate format UINT8.");
    138             }
    139             final int heartRate = characteristic.getIntValue(format, 1);
    140             Log.d(TAG, String.format("Received heart rate: %d", heartRate));
    141             intent.putExtra(EXTRA_DATA, String.valueOf(heartRate));
    142         } else {
    143             // For all other profiles, writes the data formatted in HEX.
    144             final byte[] data = characteristic.getValue();
    145             if (data != null && data.length > 0) {
    146                 final StringBuilder stringBuilder = new StringBuilder(data.length);
    147                 for(byte byteChar : data)
    148                     stringBuilder.append(String.format("%02X ", byteChar));
    149                 intent.putExtra(EXTRA_DATA, new String(data) + "\n" + stringBuilder.toString());
    150             }
    151         }
    152         sendBroadcast(intent);
    153     }
    154 
    155     public class LocalBinder extends Binder {
    156         BluetoothLeService getService() {
    157             return BluetoothLeService.this;
    158         }
    159     }
    160 
    161     @Override
    162     public IBinder onBind(Intent intent) {
    163         return mBinder;
    164     }
    165 
    166     @Override
    167     public boolean onUnbind(Intent intent) {
    168         // After using a given device, you should make sure that BluetoothGatt.close() is called
    169         // such that resources are cleaned up properly.  In this particular example, close() is
    170         // invoked when the UI is disconnected from the Service.
    171         close();
    172         return super.onUnbind(intent);
    173     }
    174 
    175     private final IBinder mBinder = new LocalBinder();
    176 
    177     /**
    178      * Initializes a reference to the local Bluetooth adapter.
    179      *
    180      * @return Return true if the initialization is successful.
    181      */
    182     public boolean initialize() {
    183         // For API level 18 and above, get a reference to BluetoothAdapter through
    184         // BluetoothManager.
    185         if (mBluetoothManager == null) {
    186             mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
    187             if (mBluetoothManager == null) {
    188                 Log.e(TAG, "Unable to initialize BluetoothManager.");
    189                 return false;
    190             }
    191         }
    192 
    193         mBluetoothAdapter = mBluetoothManager.getAdapter();
    194         if (mBluetoothAdapter == null) {
    195             Log.e(TAG, "Unable to obtain a BluetoothAdapter.");
    196             return false;
    197         }
    198 
    199         return true;
    200     }
    201 
    202     /**
    203      * Connects to the GATT server hosted on the Bluetooth LE device.
    204      *
    205      * @param address The device address of the destination device.
    206      *
    207      * @return Return true if the connection is initiated successfully. The connection result
    208      *         is reported asynchronously through the
    209      *         {@code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)}
    210      *         callback.
    211      */
    212     public boolean connect(final String address) {
    213         if (mBluetoothAdapter == null || address == null) {
    214             Log.w(TAG, "BluetoothAdapter not initialized or unspecified address.");
    215             return false;
    216         }
    217 
    218         // Previously connected device.  Try to reconnect.
    219         if (mBluetoothDeviceAddress != null && address.equals(mBluetoothDeviceAddress)
    220                 && mBluetoothGatt != null) {
    221             Log.d(TAG, "Trying to use an existing mBluetoothGatt for connection.");
    222             if (mBluetoothGatt.connect()) {
    223                 mConnectionState = STATE_CONNECTING;
    224                 return true;
    225             } else {
    226                 return false;
    227             }
    228         }
    229 
    230         final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
    231         if (device == null) {
    232             Log.w(TAG, "Device not found.  Unable to connect.");
    233             return false;
    234         }
    235         // We want to directly connect to the device, so we are setting the autoConnect
    236         // parameter to false.
    237         mBluetoothGatt = device.connectGatt(this, false, mGattCallback);
    238         Log.d(TAG, "Trying to create a new connection.");
    239         mBluetoothDeviceAddress = address;
    240         mConnectionState = STATE_CONNECTING;
    241         return true;
    242     }
    243 
    244     /**
    245      * Disconnects an existing connection or cancel a pending connection. The disconnection result
    246      * is reported asynchronously through the
    247      * {@code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)}
    248      * callback.
    249      */
    250     public void disconnect() {
    251         if (mBluetoothAdapter == null || mBluetoothGatt == null) {
    252             Log.w(TAG, "BluetoothAdapter not initialized");
    253             return;
    254         }
    255         mBluetoothGatt.disconnect();
    256     }
    257 
    258     /**
    259      * After using a given BLE device, the app must call this method to ensure resources are
    260      * released properly.
    261      */
    262     public void close() {
    263         if (mBluetoothGatt == null) {
    264             return;
    265         }
    266         mBluetoothGatt.close();
    267         mBluetoothGatt = null;
    268     }
    269 
    270     /**
    271      * Request a read on a given {@code BluetoothGattCharacteristic}. The read result is reported
    272      * asynchronously through the {@code BluetoothGattCallback#onCharacteristicRead(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic, int)}
    273      * callback.
    274      *
    275      * @param characteristic The characteristic to read from.
    276      */
    277     public void readCharacteristic(BluetoothGattCharacteristic characteristic) {
    278         if (mBluetoothAdapter == null || mBluetoothGatt == null) {
    279             Log.w(TAG, "BluetoothAdapter not initialized");
    280             return;
    281         }
    282         mBluetoothGatt.readCharacteristic(characteristic);
    283     }
    284 
    285     /**
    286      * Enables or disables notification on a give characteristic.
    287      *
    288      * @param characteristic Characteristic to act on.
    289      * @param enabled If true, enable notification.  False otherwise.
    290      */
    291     public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic,
    292                                               boolean enabled) {
    293         if (mBluetoothAdapter == null || mBluetoothGatt == null) {
    294             Log.w(TAG, "BluetoothAdapter not initialized");
    295             return;
    296         }
    297         mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);
    298 
    299         // This is specific to Heart Rate Measurement.
    300         if (UUID_HEART_RATE_MEASUREMENT.equals(characteristic.getUuid())) {
    301             BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
    302                     UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));
    303             descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
    304             mBluetoothGatt.writeDescriptor(descriptor);
    305         }
    306     }
    307 
    308     /**
    309      * Retrieves a list of supported GATT services on the connected device. This should be
    310      * invoked only after {@code BluetoothGatt#discoverServices()} completes successfully.
    311      *
    312      * @return A {@code List} of supported services.
    313      */
    314     public List<BluetoothGattService> getSupportedGattServices() {
    315         if (mBluetoothGatt == null) return null;
    316 
    317         return mBluetoothGatt.getServices();
    318     }
    319 }
    320