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 }