BluetoothLeGatt / src / com.example.android.bluetoothlegatt /

BluetoothLeService.java

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
}