Home | History | Annotate | Download | only in com.example.android.bluetoothlegatt
      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 }
    310