Home | History | Annotate | Download | only in connectivity
      1 page.title=Bluetooth Low Energy
      2 page.tags=wireless,bluetoothadapter,bluetoothdevice,BLE,BTLE
      3 @jd:body
      4 
      5 <div id="qv-wrapper">
      6 <div id="qv">
      7 
      8   <h2>In this document</h2>
      9   <ol>
     10     <li><a href="#terms">Key Terms and Concepts</a>
     11     <ol>
     12       <li><a href="#roles">Roles and Responsibilities</a></li>
     13     </ol>
     14     </li>
     15     <li><a href="#permissions">BLE Permissions</a></li>
     16     <li><a href="#setup">Setting Up BLE</a></li>
     17     <li><a href="#find">Finding BLE Devices</a></li>
     18     <li><a href="#connect">Connecting to a GATT Server</a></li>
     19     <li><a href="#read">Reading BLE Attributes</a></li>
     20     <li><a href="#notification">Receiving GATT Notifications</a></li>
     21     <li><a href="#close">Closing the Client App</a></li>
     22   </ol>
     23 
     24   <h2>Key classes</h2>
     25   <ol>
     26     <li>{@link android.bluetooth.BluetoothGatt}</li>
     27     <li>{@link android.bluetooth.BluetoothGattCallback}</li>
     28     <li>{@link android.bluetooth.BluetoothGattCharacteristic}</li>
     29     <li>{@link android.bluetooth.BluetoothGattService}</li>
     30   </ol>
     31 
     32   <h2>Related samples</h2>
     33   <ol>
     34     <li><a href="{@docRoot}tools/samples/index.html">Bluetooth LE sample</a></li>
     35   </ol>
     36 
     37    <h2>See Also</h2>
     38   <ol>
     39     <li><a href="http://developers.google.com/events/io/sessions/326240948">
     40     Best Practices for Bluetooth Development</a> (video)</li>
     41 
     42   </ol>
     43 
     44 </div>
     45 </div>
     46 
     47 <a class="notice-developers-video" href="http://www.youtube.com/watch?v=vUbFB1Qypg8">
     48 <div>
     49     <h3>Video</h3>
     50     <p>DevBytes: Bluetooth Low Energy API</p>
     51 </div>
     52 </a>
     53 
     54 
     55 
     56 
     57 <p>
     58 Android 4.3 (API Level 18) introduces built-in platform support for Bluetooth Low
     59 Energy in the <em>central role</em> and provides APIs that apps can use to discover
     60 devices, query for services, and read/write characteristics.
     61 In contrast to
     62 <a href="{@docRoot}guide/topics/connectivity/bluetooth.html">Classic Bluetooth</a>,
     63 Bluetooth Low Energy (BLE) is designed to provide significantly lower power consumption.
     64 This allows Android apps to communicate with BLE devices that have low power requirements,
     65 such as proximity sensors, heart rate monitors, fitness devices, and so on.</p>
     66 
     67 <h2 id="terms">Key Terms and Concepts</h2>
     68 <p>Here is a summary of key BLE terms and concepts:</p>
     69 <ul>
     70   <li><strong>Generic Attribute Profile (GATT)</strong>&mdash;The GATT profile
     71 is a general specification for sending and receiving short pieces of data known
     72 as "attributes" over a BLE link. All current Low Energy application profiles are
     73 based on GATT.
     74     <ul>
     75       <li>The Bluetooth SIG defines many
     76 <a href="https://www.bluetooth.org/en-us/specification/adopted-specifications">profiles</a>
     77 for Low Energy devices. A profile is a specification for how a device works in a
     78 particular application. Note that a device can implement more than one profile.
     79 For example, a device could contain a heart rate monitor and a battery level
     80 detector.</li>
     81     </ul>
     82   </li>
     83   <li><strong>Attribute Protocol (ATT)</strong>&mdash;GATT is built on top of
     84 the Attribute Protocol (ATT). This is also referred to as GATT/ATT. ATT is
     85 optimized to run on BLE devices. To this end, it uses as few bytes as possible.
     86 Each attribute is uniquely identified by a Universally Unique Identifier (UUID),
     87 which is a standardized 128-bit format for a string ID used to uniquely
     88 identify information. The <em>attributes</em> transported by ATT are formatted
     89 as <em>characteristics</em> and <em>services</em>. </li>
     90 
     91   <li><strong>Characteristic</strong>&mdash;A characteristic contains a single
     92 value and 0-n descriptors that describe the characteristic's value. A
     93 characteristic can be thought of as a type, analogous to a class.</li>
     94   <li><strong>Descriptor</strong>&mdash;Descriptors are defined attributes that
     95 describe a characteristic value. For example, a descriptor might specify a
     96 human-readable description, an acceptable range for a characteristic's value, or
     97 a unit of measure that is specific to a characteristic's value.</li>
     98 
     99   <li><strong>Service</strong>&mdash;A service is a collection of
    100 characteristics. For example, you could have a service called
    101 &quot;Heart Rate Monitor&quot; that includes characteristics such as
    102 &quot;heart rate measurement.&quot; You can find a list of existing GATT-based
    103 profiles and services on
    104 <a href="https://www.bluetooth.org/en-us/specification/adopted-specifications">
    105 bluetooth.org</a>.</li>
    106 
    107 </ul>
    108 
    109 <h3 id="roles">Roles and Responsibilities</h3>
    110 
    111 <p>Here are the roles and responsibilities that apply when
    112 an Android device interacts with a BLE device:</p>
    113 
    114 <ul>
    115   <li>Central vs. peripheral. This applies to the BLE connection itself. The
    116   device in the central role scans, looking for advertisement, and the device in
    117   the peripheral role makes the advertisement.</li>
    118   <li>GATT server vs. GATT client. This determines how two devices talk to each
    119 other once they've established the connection.</li>
    120 </ul>
    121 
    122 <p>To understand the distinction, imagine that you have an Android phone and
    123 an activity tracker that is a BLE device. The phone supports the
    124 central role; the activity tracker supports the peripheral role (to
    125 establish a BLE connection you need one of each&mdash;two things that only support
    126 peripheral couldn't talk to each other, nor could two things that only support
    127 central).</p>
    128 
    129 <p>Once the phone and the activity tracker have established a connection, they
    130 start transferring GATT metadata to one another. Depending on the kind of data they transfer,
    131 one or the other might act as the server. For example, if the activity tracker
    132 wants to report sensor data to the phone, it might make sense for the activity
    133 tracker to act as the server. If the activity tracker wants to receive updates
    134 from the phone, then it might make sense for the phone to act
    135 as the server.</p>
    136 
    137 <p>
    138 In the example used in this document, the Android app (running on an Android
    139 device) is the GATT client. The app gets data from the GATT server, which is a
    140 BLE heart rate monitor that supports the
    141 <a href="http://developer.bluetooth.org/TechnologyOverview/Pages/HRP.aspx">Heart
    142 Rate Profile</a>.  But you could alternatively design
    143 your Android app to play the GATT server
    144 role. See {@link android.bluetooth.BluetoothGattServer} for more
    145 information.</p>
    146 
    147 <h2 id="permissions">BLE Permissions</h2>
    148 
    149 <p>In order to use Bluetooth features in your application, you must declare
    150 the Bluetooth permission {@link android.Manifest.permission#BLUETOOTH}.
    151 You need this permission to perform any Bluetooth communication,
    152 such as requesting a connection, accepting a connection, and transferring data.</p>
    153 
    154 <p>If you want your app to initiate device discovery or manipulate Bluetooth
    155 settings, you must also declare the {@link android.Manifest.permission#BLUETOOTH_ADMIN}
    156 permission. <strong>Note:</strong> If you use the
    157 {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission, then you must
    158 also have the {@link android.Manifest.permission#BLUETOOTH} permission.</p>
    159 
    160 <p>Declare the Bluetooth permission(s) in your application manifest file. For
    161 example:</p>
    162 
    163 <pre>
    164 &lt;uses-permission android:name=&quot;android.permission.BLUETOOTH&quot;/&gt;
    165 &lt;uses-permission android:name=&quot;android.permission.BLUETOOTH_ADMIN&quot;/&gt;</pre>
    166 
    167 <p>If you want to declare that your app is available to BLE-capable devices only,
    168 include the following in your app's manifest:</p>
    169 
    170 <pre>&lt;uses-feature android:name=&quot;android.hardware.bluetooth_le&quot; android:required=&quot;true&quot;/&gt;
    171 </pre>
    172 
    173 <p>However, if you want to make your app available to devices that don't support BLE,
    174 you should still include this element in your app's manifest, but set {@code required="false"}. 
    175 Then at run-time you can determine BLE availability by using
    176 {@link android.content.pm.PackageManager#hasSystemFeature PackageManager.hasSystemFeature()}:
    177 
    178 <pre>// Use this check to determine whether BLE is supported on the device. Then
    179 // you can selectively disable BLE-related features.
    180 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
    181     Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();
    182     finish();
    183 }</pre>
    184 
    185 <h2 id="setup">Setting Up BLE</h2>
    186 
    187 <p>Before your application can communicate over BLE, you need
    188 to verify that BLE is supported on the device, and if so, ensure that it is enabled.
    189 Note that this check is only necessary if {@code &lt;uses-feature.../&gt;}
    190 is set to false.</p>
    191 
    192 <p>If BLE is not supported, then you should gracefully disable any
    193 BLE features. If BLE is supported, but disabled, then you can request that the
    194 user enable Bluetooth without leaving your application. This setup is
    195 accomplished in two steps, using the {@link android.bluetooth.BluetoothAdapter}.
    196 </p>
    197 
    198 
    199 <ol>
    200 <li>Get the {@link android.bluetooth.BluetoothAdapter}
    201 <p>The {@link android.bluetooth.BluetoothAdapter} is required for any and all
    202 Bluetooth activity. The {@link android.bluetooth.BluetoothAdapter} represents
    203 the device's own Bluetooth adapter (the Bluetooth radio). There's one Bluetooth
    204 adapter for the entire system, and your application can interact with it using
    205 this object. The snippet below shows how to get the adapter. Note that this approach
    206 uses {@link android.content.Context#getSystemService getSystemService()} to return
    207 an instance of {@link android.bluetooth.BluetoothManager}, which is then
    208 used to get the adapter. Android 4.3 (API Level 18) introduces
    209 {@link android.bluetooth.BluetoothManager}:</p>
    210 
    211 <pre>// Initializes Bluetooth adapter.
    212 final BluetoothManager bluetoothManager =
    213         (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
    214 mBluetoothAdapter = bluetoothManager.getAdapter();
    215 </pre>
    216 </li>
    217 
    218 <li>Enable Bluetooth
    219 <p>Next, you need to ensure that Bluetooth is enabled. Call {@link
    220 android.bluetooth.BluetoothAdapter#isEnabled()} to check whether Bluetooth is
    221 currently enabled. If this method returns false, then Bluetooth is disabled.
    222 The following snippet checks whether Bluetooth is enabled. If it isn't, the
    223 snippet displays an error prompting the user to go to Settings to enable
    224 Bluetooth:</p>
    225 <pre>private BluetoothAdapter mBluetoothAdapter;
    226 ...
    227 // Ensures Bluetooth is available on the device and it is enabled. If not,
    228 // displays a dialog requesting user permission to enable Bluetooth.
    229 if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
    230     Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
    231     startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
    232 }
    233 </li>
    234 </ol>
    235 
    236 
    237 
    238 <h2 id="find">Finding BLE Devices</h2>
    239 
    240 <p>To find BLE devices, you use the
    241 {@link android.bluetooth.BluetoothAdapter#startLeScan startLeScan()} method.
    242 This method takes a  {@link android.bluetooth.BluetoothAdapter.LeScanCallback}
    243 as a parameter. You must implement this callback, because that is how scan
    244 results are returned. Because scanning is battery-intensive, you should observe
    245 the following guidelines:</p>
    246 <ul>
    247   <li>As soon as you find the desired device, stop scanning.</li>
    248   <li>Never scan on a loop, and set a time limit on your scan. A device that was
    249 previously available may have moved out of range, and continuing to scan drains
    250 the battery.</li>
    251 </ul>
    252 
    253 <p>The following snippet shows how to start and stop a scan:</p>
    254 
    255 <pre>/**
    256  * Activity for scanning and displaying available BLE devices.
    257  */
    258 public class DeviceScanActivity extends ListActivity {
    259 
    260     private BluetoothAdapter mBluetoothAdapter;
    261     private boolean mScanning;
    262     private Handler mHandler;
    263 
    264     // Stops scanning after 10 seconds.
    265     private static final long SCAN_PERIOD = 10000;
    266     ...
    267     private void scanLeDevice(final boolean enable) {
    268         if (enable) {
    269             // Stops scanning after a pre-defined scan period.
    270             mHandler.postDelayed(new Runnable() {
    271                 &#64;Override
    272                 public void run() {
    273                     mScanning = false;
    274                     mBluetoothAdapter.stopLeScan(mLeScanCallback);
    275                 }
    276             }, SCAN_PERIOD);
    277 
    278             mScanning = true;
    279             mBluetoothAdapter.startLeScan(mLeScanCallback);
    280         } else {
    281             mScanning = false;
    282             mBluetoothAdapter.stopLeScan(mLeScanCallback);
    283         }
    284         ...
    285     }
    286 ...
    287 }
    288 </pre>
    289 
    290 <p>If you want to scan for only specific types of peripherals, you can instead
    291 call {@link android.bluetooth.BluetoothAdapter#startLeScan startLeScan(UUID[], BluetoothAdapter.LeScanCallback)},
    292 providing an array of {@link java.util.UUID} objects that specify the GATT
    293 services your app supports.</p>
    294 
    295 <p>Here is an implementation of the
    296 {@link android.bluetooth.BluetoothAdapter.LeScanCallback},
    297 which is the interface used to deliver BLE scan results:</p>
    298 
    299 <pre>
    300 private LeDeviceListAdapter mLeDeviceListAdapter;
    301 ...
    302 // Device scan callback.
    303 private BluetoothAdapter.LeScanCallback mLeScanCallback =
    304         new BluetoothAdapter.LeScanCallback() {
    305     &#64;Override
    306     public void onLeScan(final BluetoothDevice device, int rssi,
    307             byte[] scanRecord) {
    308         runOnUiThread(new Runnable() {
    309            &#64;Override
    310            public void run() {
    311                mLeDeviceListAdapter.addDevice(device);
    312                mLeDeviceListAdapter.notifyDataSetChanged();
    313            }
    314        });
    315    }
    316 };</pre>
    317 
    318 
    319 <p class="note"><strong>Note:</strong> You can only scan for Bluetooth LE devices
    320 <em>or</em> scan for Classic Bluetooth devices, as described in
    321 <a href="{@docRoot}guide/topics/connectivity/bluetooth.html">Bluetooth</a>. You cannot
    322 scan for both Bluetooth LE and classic devices at the same time.</p>
    323 
    324 <h2 id="connect">Connecting to a GATT Server</h2>
    325 
    326 <p>The first step in interacting with a BLE device is connecting to it&mdash;
    327 more specifically, connecting to the GATT server on the device. To
    328 connect to a GATT server on a BLE device, you use the
    329 {@link android.bluetooth.BluetoothDevice#connectGatt connectGatt()} method.
    330 This method takes three parameters: a {@link android.content.Context} object,
    331 <code>autoConnect</code> (boolean indicating whether to automatically connect to
    332 the BLE device as soon as it becomes available), and a reference to a
    333 {@link android.bluetooth.BluetoothGattCallback}: </p>
    334 
    335 <pre>mBluetoothGatt = device.connectGatt(this, false, mGattCallback);</pre>
    336 
    337 <p>This connects to the GATT server hosted by the BLE device, and returns a
    338 {@link android.bluetooth.BluetoothGatt} instance, which you can then use to
    339 conduct GATT client operations. The caller (the Android app) is the GATT client.
    340 The {@link android.bluetooth.BluetoothGattCallback} is used to deliver results
    341 to the client, such as connection status, as well as any further GATT client
    342 operations.</p>
    343 
    344 <p>In this example, the BLE app provides an activity
    345 (<code>DeviceControlActivity</code>) to connect,
    346 display data, and display GATT services and characteristics
    347 supported by the device.  Based on user input, this activity communicates with a
    348 {@link android.app.Service} called {@code BluetoothLeService},
    349 which interacts with the BLE device via the Android BLE API:</p>
    350 
    351 <pre>
    352 // A service that interacts with the BLE device via the Android BLE API.
    353 public class BluetoothLeService extends Service {
    354     private final static String TAG = BluetoothLeService.class.getSimpleName();
    355 
    356     private BluetoothManager mBluetoothManager;
    357     private BluetoothAdapter mBluetoothAdapter;
    358     private String mBluetoothDeviceAddress;
    359     private BluetoothGatt mBluetoothGatt;
    360     private int mConnectionState = STATE_DISCONNECTED;
    361 
    362     private static final int STATE_DISCONNECTED = 0;
    363     private static final int STATE_CONNECTING = 1;
    364     private static final int STATE_CONNECTED = 2;
    365 
    366     public final static String ACTION_GATT_CONNECTED =
    367             "com.example.bluetooth.le.ACTION_GATT_CONNECTED";
    368     public final static String ACTION_GATT_DISCONNECTED =
    369             "com.example.bluetooth.le.ACTION_GATT_DISCONNECTED";
    370     public final static String ACTION_GATT_SERVICES_DISCOVERED =
    371             "com.example.bluetooth.le.ACTION_GATT_SERVICES_DISCOVERED";
    372     public final static String ACTION_DATA_AVAILABLE =
    373             "com.example.bluetooth.le.ACTION_DATA_AVAILABLE";
    374     public final static String EXTRA_DATA =
    375             "com.example.bluetooth.le.EXTRA_DATA";
    376 
    377     public final static UUID UUID_HEART_RATE_MEASUREMENT =
    378             UUID.fromString(SampleGattAttributes.HEART_RATE_MEASUREMENT);
    379 
    380     // Various callback methods defined by the BLE API.
    381     private final BluetoothGattCallback mGattCallback =
    382             new BluetoothGattCallback() {
    383         &#64;Override
    384         public void onConnectionStateChange(BluetoothGatt gatt, int status,
    385                 int newState) {
    386             String intentAction;
    387             if (newState == BluetoothProfile.STATE_CONNECTED) {
    388                 intentAction = ACTION_GATT_CONNECTED;
    389                 mConnectionState = STATE_CONNECTED;
    390                 broadcastUpdate(intentAction);
    391                 Log.i(TAG, "Connected to GATT server.");
    392                 Log.i(TAG, "Attempting to start service discovery:" +
    393                         mBluetoothGatt.discoverServices());
    394 
    395             } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
    396                 intentAction = ACTION_GATT_DISCONNECTED;
    397                 mConnectionState = STATE_DISCONNECTED;
    398                 Log.i(TAG, "Disconnected from GATT server.");
    399                 broadcastUpdate(intentAction);
    400             }
    401         }
    402 
    403         &#64;Override
    404         // New services discovered
    405         public void onServicesDiscovered(BluetoothGatt gatt, int status) {
    406             if (status == BluetoothGatt.GATT_SUCCESS) {
    407                 broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED);
    408             } else {
    409                 Log.w(TAG, "onServicesDiscovered received: " + status);
    410             }
    411         }
    412 
    413         &#64;Override
    414         // Result of a characteristic read operation
    415         public void onCharacteristicRead(BluetoothGatt gatt,
    416                 BluetoothGattCharacteristic characteristic,
    417                 int status) {
    418             if (status == BluetoothGatt.GATT_SUCCESS) {
    419                 broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
    420             }
    421         }
    422      ...
    423     };
    424 ...
    425 }</pre>
    426 
    427 <p>When a particular callback is triggered, it calls the appropriate
    428 {@code broadcastUpdate()} helper method and passes it an action. Note that the data
    429 parsing in this section is performed in accordance with the Bluetooth Heart Rate
    430 Measurement
    431 <a href="http://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.heart_rate_measurement.xml">
    432 profile specifications</a>:</p>
    433 
    434 <pre>private void broadcastUpdate(final String action) {
    435     final Intent intent = new Intent(action);
    436     sendBroadcast(intent);
    437 }
    438 
    439 private void broadcastUpdate(final String action,
    440                              final BluetoothGattCharacteristic characteristic) {
    441     final Intent intent = new Intent(action);
    442 
    443     // This is special handling for the Heart Rate Measurement profile. Data
    444     // parsing is carried out as per profile specifications.
    445     if (UUID_HEART_RATE_MEASUREMENT.equals(characteristic.getUuid())) {
    446         int flag = characteristic.getProperties();
    447         int format = -1;
    448         if ((flag &amp; 0x01) != 0) {
    449             format = BluetoothGattCharacteristic.FORMAT_UINT16;
    450             Log.d(TAG, &quot;Heart rate format UINT16.&quot;);
    451         } else {
    452             format = BluetoothGattCharacteristic.FORMAT_UINT8;
    453             Log.d(TAG, &quot;Heart rate format UINT8.&quot;);
    454         }
    455         final int heartRate = characteristic.getIntValue(format, 1);
    456         Log.d(TAG, String.format(&quot;Received heart rate: %d&quot;, heartRate));
    457         intent.putExtra(EXTRA_DATA, String.valueOf(heartRate));
    458     } else {
    459         // For all other profiles, writes the data formatted in HEX.
    460         final byte[] data = characteristic.getValue();
    461         if (data != null &amp;&amp; data.length &gt; 0) {
    462             final StringBuilder stringBuilder = new StringBuilder(data.length);
    463             for(byte byteChar : data)
    464                 stringBuilder.append(String.format(&quot;%02X &quot;, byteChar));
    465             intent.putExtra(EXTRA_DATA, new String(data) + &quot;\n&quot; +
    466                     stringBuilder.toString());
    467         }
    468     }
    469     sendBroadcast(intent);
    470 }</pre>
    471 
    472 
    473 
    474 <p>Back in <code>DeviceControlActivity</code>, these events are handled by a
    475 {@link android.content.BroadcastReceiver}:</p>
    476 
    477 <pre>// Handles various events fired by the Service.
    478 // ACTION_GATT_CONNECTED: connected to a GATT server.
    479 // ACTION_GATT_DISCONNECTED: disconnected from a GATT server.
    480 // ACTION_GATT_SERVICES_DISCOVERED: discovered GATT services.
    481 // ACTION_DATA_AVAILABLE: received data from the device. This can be a
    482 // result of read or notification operations.
    483 private final BroadcastReceiver mGattUpdateReceiver = new BroadcastReceiver() {
    484     &#64;Override
    485     public void onReceive(Context context, Intent intent) {
    486         final String action = intent.getAction();
    487         if (BluetoothLeService.ACTION_GATT_CONNECTED.equals(action)) {
    488             mConnected = true;
    489             updateConnectionState(R.string.connected);
    490             invalidateOptionsMenu();
    491         } else if (BluetoothLeService.ACTION_GATT_DISCONNECTED.equals(action)) {
    492             mConnected = false;
    493             updateConnectionState(R.string.disconnected);
    494             invalidateOptionsMenu();
    495             clearUI();
    496         } else if (BluetoothLeService.
    497                 ACTION_GATT_SERVICES_DISCOVERED.equals(action)) {
    498             // Show all the supported services and characteristics on the
    499             // user interface.
    500             displayGattServices(mBluetoothLeService.getSupportedGattServices());
    501         } else if (BluetoothLeService.ACTION_DATA_AVAILABLE.equals(action)) {
    502             displayData(intent.getStringExtra(BluetoothLeService.EXTRA_DATA));
    503         }
    504     }
    505 };
    506 </pre>
    507 
    508 <h2 id="read">Reading BLE Attributes</h2>
    509 
    510 <p>Once your Android app has connected to a GATT server and discovered services,
    511 it can read and write attributes, where supported. For example, this snippet iterates
    512 through the server's services and characteristics and displays them in the UI:</p>
    513 
    514 <pre>
    515 public class DeviceControlActivity extends Activity {
    516     ...
    517     // Demonstrates how to iterate through the supported GATT
    518     // Services/Characteristics.
    519     // In this sample, we populate the data structure that is bound to the
    520     // ExpandableListView on the UI.
    521     private void displayGattServices(List&lt;BluetoothGattService&gt; gattServices) {
    522         if (gattServices == null) return;
    523         String uuid = null;
    524         String unknownServiceString = getResources().
    525                 getString(R.string.unknown_service);
    526         String unknownCharaString = getResources().
    527                 getString(R.string.unknown_characteristic);
    528         ArrayList&lt;HashMap&lt;String, String&gt;&gt; gattServiceData =
    529                 new ArrayList&lt;HashMap&lt;String, String&gt;&gt;();
    530         ArrayList&lt;ArrayList&lt;HashMap&lt;String, String&gt;&gt;&gt; gattCharacteristicData
    531                 = new ArrayList&lt;ArrayList&lt;HashMap&lt;String, String&gt;&gt;&gt;();
    532         mGattCharacteristics =
    533                 new ArrayList&lt;ArrayList&lt;BluetoothGattCharacteristic&gt;&gt;();
    534 
    535         // Loops through available GATT Services.
    536         for (BluetoothGattService gattService : gattServices) {
    537             HashMap&lt;String, String&gt; currentServiceData =
    538                     new HashMap&lt;String, String&gt;();
    539             uuid = gattService.getUuid().toString();
    540             currentServiceData.put(
    541                     LIST_NAME, SampleGattAttributes.
    542                             lookup(uuid, unknownServiceString));
    543             currentServiceData.put(LIST_UUID, uuid);
    544             gattServiceData.add(currentServiceData);
    545 
    546             ArrayList&lt;HashMap&lt;String, String&gt;&gt; gattCharacteristicGroupData =
    547                     new ArrayList&lt;HashMap&lt;String, String&gt;&gt;();
    548             List&lt;BluetoothGattCharacteristic&gt; gattCharacteristics =
    549                     gattService.getCharacteristics();
    550             ArrayList&lt;BluetoothGattCharacteristic&gt; charas =
    551                     new ArrayList&lt;BluetoothGattCharacteristic&gt;();
    552            // Loops through available Characteristics.
    553             for (BluetoothGattCharacteristic gattCharacteristic :
    554                     gattCharacteristics) {
    555                 charas.add(gattCharacteristic);
    556                 HashMap&lt;String, String&gt; currentCharaData =
    557                         new HashMap&lt;String, String&gt;();
    558                 uuid = gattCharacteristic.getUuid().toString();
    559                 currentCharaData.put(
    560                         LIST_NAME, SampleGattAttributes.lookup(uuid,
    561                                 unknownCharaString));
    562                 currentCharaData.put(LIST_UUID, uuid);
    563                 gattCharacteristicGroupData.add(currentCharaData);
    564             }
    565             mGattCharacteristics.add(charas);
    566             gattCharacteristicData.add(gattCharacteristicGroupData);
    567          }
    568     ...
    569     }
    570 ...
    571 }</pre>
    572 
    573 <h2 id="notification">Receiving GATT Notifications</h2>
    574 
    575 <p>It's common for BLE apps to ask to be notified when a particular
    576 characteristic changes on the device. This snippet shows how to set a notification
    577 for a characteristic, using the
    578 {@link android.bluetooth.BluetoothGatt#setCharacteristicNotification setCharacteristicNotification()}
    579 method:</p>
    580 
    581 <pre>
    582 private BluetoothGatt mBluetoothGatt;
    583 BluetoothGattCharacteristic characteristic;
    584 boolean enabled;
    585 ...
    586 mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);
    587 ...
    588 BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
    589         UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));
    590 descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
    591 mBluetoothGatt.writeDescriptor(descriptor);</pre>
    592 
    593 <p>Once notifications are enabled for a characteristic,
    594 an {@link android.bluetooth.BluetoothGattCallback#onCharacteristicChanged onCharacteristicChanged()}
    595 callback is triggered if the characteristic changes on the remote device:</p>
    596 
    597 <pre>&#64;Override
    598 // Characteristic notification
    599 public void onCharacteristicChanged(BluetoothGatt gatt,
    600         BluetoothGattCharacteristic characteristic) {
    601     broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
    602 }
    603 </pre>
    604 
    605 <h2 id="close">Closing the Client App</h2>
    606 
    607 <p>Once your app has finished using a BLE device, it should call
    608 {@link android.bluetooth.BluetoothGatt#close close()}
    609 so the system can release resources appropriately:</p>
    610 
    611 <pre>public void close() {
    612     if (mBluetoothGatt == null) {
    613         return;
    614     }
    615     mBluetoothGatt.close();
    616     mBluetoothGatt = null;
    617 }</pre>
    618