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
}