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 
clearUI()150     private void clearUI() {
151         mGattServicesList.setAdapter((SimpleExpandableListAdapter) null);
152         mDataField.setText(R.string.no_data);
153     }
154 
155     @Override
onCreate(Bundle savedInstanceState)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
onResume()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
onPause()188     protected void onPause() {
189         super.onPause();
190         unregisterReceiver(mGattUpdateReceiver);
191     }
192 
193     @Override
onDestroy()194     protected void onDestroy() {
195         super.onDestroy();
196         unbindService(mServiceConnection);
197         mBluetoothLeService = null;
198     }
199 
200     @Override
onCreateOptionsMenu(Menu menu)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
onOptionsItemSelected(MenuItem item)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 
updateConnectionState(final int resourceId)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 
displayData(String data)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.
displayGattServices(List<BluetoothGattService> gattServices)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 
makeGattUpdateIntentFilter()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 }
310