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 }