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.Activity;
20
import android.bluetooth.BluetoothGattCharacteristic;
21
import android.bluetooth.BluetoothGattService;
22
import android.content.BroadcastReceiver;
23
import android.content.ComponentName;
24
import android.content.Context;
25
import android.content.Intent;
26
import android.content.IntentFilter;
27
import android.content.ServiceConnection;
28
import android.os.Bundle;
29
import android.os.IBinder;
30
import android.util.Log;
31
import android.view.Menu;
32
import android.view.MenuItem;
33
import android.view.View;
34
import android.widget.ExpandableListView;
35
import android.widget.SimpleExpandableListAdapter;
36
import android.widget.TextView;
37
38
import java.util.ArrayList;
39
import java.util.HashMap;
40
import java.util.List;
41
42
/**
43
* For a given BLE device, this Activity provides the user interface to connect, display data,
44
* and display GATT services and characteristics supported by the device. The Activity
45
* communicates with {@code BluetoothLeService}, which in turn interacts with the
46
* Bluetooth LE API.
47
*/
48
public class DeviceControlActivity extends Activity {
49
private final static String TAG = DeviceControlActivity.class.getSimpleName();
50
51
public static final String EXTRAS_DEVICE_NAME = "DEVICE_NAME";
52
public static final String EXTRAS_DEVICE_ADDRESS = "DEVICE_ADDRESS";
53
54
private TextView mConnectionState;
55
private TextView mDataField;
56
private String mDeviceName;
57
private String mDeviceAddress;
58
private ExpandableListView mGattServicesList;
59
private BluetoothLeService mBluetoothLeService;
60
private ArrayList<ArrayList<BluetoothGattCharacteristic>> mGattCharacteristics =
61
new ArrayList<ArrayList<BluetoothGattCharacteristic>>();
62
private boolean mConnected = false;
63
private BluetoothGattCharacteristic mNotifyCharacteristic;
64
65
private final String LIST_NAME = "NAME";
66
private final String LIST_UUID = "UUID";
67
68
// Code to manage Service lifecycle.
69
private final ServiceConnection mServiceConnection = new ServiceConnection() {
70
71
@Override
72
public void onServiceConnected(ComponentName componentName, IBinder service) {
73
mBluetoothLeService = ((BluetoothLeService.LocalBinder) service).getService();
74
if (!mBluetoothLeService.initialize()) {
75
Log.e(TAG, "Unable to initialize Bluetooth");
76
finish();
77
}
78
// Automatically connects to the device upon successful start-up initialization.
79
mBluetoothLeService.connect(mDeviceAddress);
80
}
81
82
@Override
83
public void onServiceDisconnected(ComponentName componentName) {
84
mBluetoothLeService = null;
85
}
86
};
87
88
// Handles various events fired by the Service.
89
// ACTION_GATT_CONNECTED: connected to a GATT server.
90
// ACTION_GATT_DISCONNECTED: disconnected from a GATT server.
91
// ACTION_GATT_SERVICES_DISCOVERED: discovered GATT services.
92
// ACTION_DATA_AVAILABLE: received data from the device. This can be a result of read
93
// or notification operations.
94
private final BroadcastReceiver mGattUpdateReceiver = new BroadcastReceiver() {
95
@Override
96
public void onReceive(Context context, Intent intent) {
97
final String action = intent.getAction();
98
if (BluetoothLeService.ACTION_GATT_CONNECTED.equals(action)) {
99
mConnected = true;
100
updateConnectionState(R.string.connected);
101
invalidateOptionsMenu();
102
} else if (BluetoothLeService.ACTION_GATT_DISCONNECTED.equals(action)) {
103
mConnected = false;
104
updateConnectionState(R.string.disconnected);
105
invalidateOptionsMenu();
106
clearUI();
107
} else if (BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED.equals(action)) {
108
// Show all the supported services and characteristics on the user interface.
109
displayGattServices(mBluetoothLeService.getSupportedGattServices());
110
} else if (BluetoothLeService.ACTION_DATA_AVAILABLE.equals(action)) {
111
displayData(intent.getStringExtra(BluetoothLeService.EXTRA_DATA));
112
}
113
}
114
};
115
116
// If a given GATT characteristic is selected, check for supported features. This sample
117
// demonstrates 'Read' and 'Notify' features. See
118
// http://d.android.com/reference/android/bluetooth/BluetoothGatt.html for the complete
119
// list of supported characteristic features.
120
private final ExpandableListView.OnChildClickListener servicesListClickListner =
121
new ExpandableListView.OnChildClickListener() {
122
@Override
123
public boolean onChildClick(ExpandableListView parent, View v, int groupPosition,
124
int childPosition, long id) {
125
if (mGattCharacteristics != null) {
126
final BluetoothGattCharacteristic characteristic =
127
mGattCharacteristics.get(groupPosition).get(childPosition);
128
final int charaProp = characteristic.getProperties();
129
if ((charaProp | BluetoothGattCharacteristic.PROPERTY_READ) > 0) {
130
// If there is an active notification on a characteristic, clear
131
// it first so it doesn't update the data field on the user interface.
132
if (mNotifyCharacteristic != null) {
133
mBluetoothLeService.setCharacteristicNotification(
134
mNotifyCharacteristic, false);
135
mNotifyCharacteristic = null;
136
}
137
mBluetoothLeService.readCharacteristic(characteristic);
138
}
139
if ((charaProp | BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0) {
140
mNotifyCharacteristic = characteristic;
141
mBluetoothLeService.setCharacteristicNotification(
142
characteristic, true);
143
}
144
return true;
145
}
146
return false;
147
}
148
};
149
150
private void clearUI() {
151
mGattServicesList.setAdapter((SimpleExpandableListAdapter) null);
152
mDataField.setText(R.string.no_data);
153
}
154
155
@Override
156
public void onCreate(Bundle savedInstanceState) {
157
super.onCreate(savedInstanceState);
158
setContentView(R.layout.gatt_services_characteristics);
159
160
final Intent intent = getIntent();
161
mDeviceName = intent.getStringExtra(EXTRAS_DEVICE_NAME);
162
mDeviceAddress = intent.getStringExtra(EXTRAS_DEVICE_ADDRESS);
163
164
// Sets up UI references.
165
((TextView) findViewById(R.id.device_address)).setText(mDeviceAddress);
166
mGattServicesList = (ExpandableListView) findViewById(R.id.gatt_services_list);
167
mGattServicesList.setOnChildClickListener(servicesListClickListner);
168
mConnectionState = (TextView) findViewById(R.id.connection_state);
169
mDataField = (TextView) findViewById(R.id.data_value);
170
171
getActionBar().setTitle(mDeviceName);
172
getActionBar().setDisplayHomeAsUpEnabled(true);
173
Intent gattServiceIntent = new Intent(this, BluetoothLeService.class);
174
bindService(gattServiceIntent, mServiceConnection, BIND_AUTO_CREATE);
175
}
176
177
@Override
178
protected void onResume() {
179
super.onResume();
180
registerReceiver(mGattUpdateReceiver, makeGattUpdateIntentFilter());
181
if (mBluetoothLeService != null) {
182
final boolean result = mBluetoothLeService.connect(mDeviceAddress);
183
Log.d(TAG, "Connect request result=" + result);
184
}
185
}
186
187
@Override
188
protected void onPause() {
189
super.onPause();
190
unregisterReceiver(mGattUpdateReceiver);
191
}
192
193
@Override
194
protected void onDestroy() {
195
super.onDestroy();
196
unbindService(mServiceConnection);
197
mBluetoothLeService = null;
198
}
199
200
@Override
201
public boolean onCreateOptionsMenu(Menu menu) {
202
getMenuInflater().inflate(R.menu.gatt_services, menu);
203
if (mConnected) {
204
menu.findItem(R.id.menu_connect).setVisible(false);
205
menu.findItem(R.id.menu_disconnect).setVisible(true);
206
} else {
207
menu.findItem(R.id.menu_connect).setVisible(true);
208
menu.findItem(R.id.menu_disconnect).setVisible(false);
209
}
210
return true;
211
}
212
213
@Override
214
public boolean onOptionsItemSelected(MenuItem item) {
215
switch(item.getItemId()) {
216
case R.id.menu_connect:
217
mBluetoothLeService.connect(mDeviceAddress);
218
return true;
219
case R.id.menu_disconnect:
220
mBluetoothLeService.disconnect();
221
return true;
222
case android.R.id.home:
223
onBackPressed();
224
return true;
225
}
226
return super.onOptionsItemSelected(item);
227
}
228
229
private void updateConnectionState(final int resourceId) {
230
runOnUiThread(new Runnable() {
231
@Override
232
public void run() {
233
mConnectionState.setText(resourceId);
234
}
235
});
236
}
237
238
private void displayData(String data) {
239
if (data != null) {
240
mDataField.setText(data);
241
}
242
}
243
244
// Demonstrates how to iterate through the supported GATT Services/Characteristics.
245
// In this sample, we populate the data structure that is bound to the ExpandableListView
246
// on the UI.
247
private void displayGattServices(List<BluetoothGattService> gattServices) {
248
if (gattServices == null) return;
249
String uuid = null;
250
String unknownServiceString = getResources().getString(R.string.unknown_service);
251
String unknownCharaString = getResources().getString(R.string.unknown_characteristic);
252
ArrayList<HashMap<String, String>> gattServiceData = new ArrayList<HashMap<String, String>>();
253
ArrayList<ArrayList<HashMap<String, String>>> gattCharacteristicData
254
= new ArrayList<ArrayList<HashMap<String, String>>>();
255
mGattCharacteristics = new ArrayList<ArrayList<BluetoothGattCharacteristic>>();
256
257
// Loops through available GATT Services.
258
for (BluetoothGattService gattService : gattServices) {
259
HashMap<String, String> currentServiceData = new HashMap<String, String>();
260
uuid = gattService.getUuid().toString();
261
currentServiceData.put(
262
LIST_NAME, SampleGattAttributes.lookup(uuid, unknownServiceString));
263
currentServiceData.put(LIST_UUID, uuid);
264
gattServiceData.add(currentServiceData);
265
266
ArrayList<HashMap<String, String>> gattCharacteristicGroupData =
267
new ArrayList<HashMap<String, String>>();
268
List<BluetoothGattCharacteristic> gattCharacteristics =
269
gattService.getCharacteristics();
270
ArrayList<BluetoothGattCharacteristic> charas =
271
new ArrayList<BluetoothGattCharacteristic>();
272
273
// Loops through available Characteristics.
274
for (BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) {
275
charas.add(gattCharacteristic);
276
HashMap<String, String> currentCharaData = new HashMap<String, String>();
277
uuid = gattCharacteristic.getUuid().toString();
278
currentCharaData.put(
279
LIST_NAME, SampleGattAttributes.lookup(uuid, unknownCharaString));
280
currentCharaData.put(LIST_UUID, uuid);
281
gattCharacteristicGroupData.add(currentCharaData);
282
}
283
mGattCharacteristics.add(charas);
284
gattCharacteristicData.add(gattCharacteristicGroupData);
285
}
286
287
SimpleExpandableListAdapter gattServiceAdapter = new SimpleExpandableListAdapter(
288
this,
289
gattServiceData,
290
android.R.layout.simple_expandable_list_item_2,
291
new String[] {LIST_NAME, LIST_UUID},
292
new int[] { android.R.id.text1, android.R.id.text2 },
293
gattCharacteristicData,
294
android.R.layout.simple_expandable_list_item_2,
295
new String[] {LIST_NAME, LIST_UUID},
296
new int[] { android.R.id.text1, android.R.id.text2 }
297
);
298
mGattServicesList.setAdapter(gattServiceAdapter);
299
}
300
301
private static IntentFilter makeGattUpdateIntentFilter() {
302
final IntentFilter intentFilter = new IntentFilter();
303
intentFilter.addAction(BluetoothLeService.ACTION_GATT_CONNECTED);
304
intentFilter.addAction(BluetoothLeService.ACTION_GATT_DISCONNECTED);
305
intentFilter.addAction(BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED);
306
intentFilter.addAction(BluetoothLeService.ACTION_DATA_AVAILABLE);
307
return intentFilter;
308
}
309
}