content: /*
   * Copyright (C) 2013 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package com.example.android.bluetoothlegatt;
  
  import android.app.Activity;
  import android.bluetooth.BluetoothGattCharacteristic;
  import android.bluetooth.BluetoothGattService;
  import android.content.BroadcastReceiver;
  import android.content.ComponentName;
  import android.content.Context;
  import android.content.Intent;
  import android.content.IntentFilter;
  import android.content.ServiceConnection;
  import android.os.Bundle;
  import android.os.IBinder;
  import android.util.Log;
  import android.view.Menu;
  import android.view.MenuItem;
  import android.view.View;
  import android.widget.ExpandableListView;
  import android.widget.SimpleExpandableListAdapter;
  import android.widget.TextView;
  
  import java.util.ArrayList;
  import java.util.HashMap;
  import java.util.List;
  
  /**
   * For a given BLE device, this Activity provides the user interface to connect, display data,
   * and display GATT services and characteristics supported by the device.  The Activity
   * communicates with {@code BluetoothLeService}, which in turn interacts with the
   * Bluetooth LE API.
   */
  public class DeviceControlActivity extends Activity {
      private final static String TAG = DeviceControlActivity.class.getSimpleName();
  
      public static final String EXTRAS_DEVICE_NAME = "DEVICE_NAME";
      public static final String EXTRAS_DEVICE_ADDRESS = "DEVICE_ADDRESS";
  
      private TextView mConnectionState;
      private TextView mDataField;
      private String mDeviceName;
      private String mDeviceAddress;
      private ExpandableListView mGattServicesList;
      private BluetoothLeService mBluetoothLeService;
      private ArrayList<ArrayList<BluetoothGattCharacteristic>> mGattCharacteristics =
              new ArrayList<ArrayList<BluetoothGattCharacteristic>>();
      private boolean mConnected = false;
      private BluetoothGattCharacteristic mNotifyCharacteristic;
  
      private final String LIST_NAME = "NAME";
      private final String LIST_UUID = "UUID";
  
      // Code to manage Service lifecycle.
      private final ServiceConnection mServiceConnection = new ServiceConnection() {
  
          @Override
          public void onServiceConnected(ComponentName componentName, IBinder service) {
              mBluetoothLeService = ((BluetoothLeService.LocalBinder) service).getService();
              if (!mBluetoothLeService.initialize()) {
                  Log.e(TAG, "Unable to initialize Bluetooth");
                  finish();
              }
              // Automatically connects to the device upon successful start-up initialization.
              mBluetoothLeService.connect(mDeviceAddress);
          }
  
          @Override
          public void onServiceDisconnected(ComponentName componentName) {
              mBluetoothLeService = null;
          }
      };
  
      // Handles various events fired by the Service.
      // ACTION_GATT_CONNECTED: connected to a GATT server.
      // ACTION_GATT_DISCONNECTED: disconnected from a GATT server.
      // ACTION_GATT_SERVICES_DISCOVERED: discovered GATT services.
      // ACTION_DATA_AVAILABLE: received data from the device.  This can be a result of read
      //                        or notification operations.
      private final BroadcastReceiver mGattUpdateReceiver = new BroadcastReceiver() {
          @Override
          public void onReceive(Context context, Intent intent) {
              final String action = intent.getAction();
              if (BluetoothLeService.ACTION_GATT_CONNECTED.equals(action)) {
                  mConnected = true;
                  updateConnectionState(R.string.connected);
                  invalidateOptionsMenu();
              } else if (BluetoothLeService.ACTION_GATT_DISCONNECTED.equals(action)) {
                  mConnected = false;
                  updateConnectionState(R.string.disconnected);
                  invalidateOptionsMenu();
                  clearUI();
              } else if (BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED.equals(action)) {
                  // Show all the supported services and characteristics on the user interface.
                  displayGattServices(mBluetoothLeService.getSupportedGattServices());
              } else if (BluetoothLeService.ACTION_DATA_AVAILABLE.equals(action)) {
                  displayData(intent.getStringExtra(BluetoothLeService.EXTRA_DATA));
              }
          }
      };
  
      // If a given GATT characteristic is selected, check for supported features.  This sample
      // demonstrates 'Read' and 'Notify' features.  See
      // http://d.android.com/reference/android/bluetooth/BluetoothGatt.html for the complete
      // list of supported characteristic features.
      private final ExpandableListView.OnChildClickListener servicesListClickListner =
              new ExpandableListView.OnChildClickListener() {
                  @Override
                  public boolean onChildClick(ExpandableListView parent, View v, int groupPosition,
                                              int childPosition, long id) {
                      if (mGattCharacteristics != null) {
                          final BluetoothGattCharacteristic characteristic =
                                  mGattCharacteristics.get(groupPosition).get(childPosition);
                          final int charaProp = characteristic.getProperties();
                          if ((charaProp | BluetoothGattCharacteristic.PROPERTY_READ) > 0) {
                              // If there is an active notification on a characteristic, clear
                              // it first so it doesn't update the data field on the user interface.
                              if (mNotifyCharacteristic != null) {
                                  mBluetoothLeService.setCharacteristicNotification(
                                          mNotifyCharacteristic, false);
                                  mNotifyCharacteristic = null;
                              }
                              mBluetoothLeService.readCharacteristic(characteristic);
                          }
                          if ((charaProp | BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0) {
                              mNotifyCharacteristic = characteristic;
                              mBluetoothLeService.setCharacteristicNotification(
                                      characteristic, true);
                          }
                          return true;
                      }
                      return false;
                  }
      };
  
      private void clearUI() {
          mGattServicesList.setAdapter((SimpleExpandableListAdapter) null);
          mDataField.setText(R.string.no_data);
      }
  
      @Override
      public void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.gatt_services_characteristics);
  
          final Intent intent = getIntent();
          mDeviceName = intent.getStringExtra(EXTRAS_DEVICE_NAME);
          mDeviceAddress = intent.getStringExtra(EXTRAS_DEVICE_ADDRESS);
  
          // Sets up UI references.
          ((TextView) findViewById(R.id.device_address)).setText(mDeviceAddress);
          mGattServicesList = (ExpandableListView) findViewById(R.id.gatt_services_list);
          mGattServicesList.setOnChildClickListener(servicesListClickListner);
          mConnectionState = (TextView) findViewById(R.id.connection_state);
          mDataField = (TextView) findViewById(R.id.data_value);
  
          getActionBar().setTitle(mDeviceName);
          getActionBar().setDisplayHomeAsUpEnabled(true);
          Intent gattServiceIntent = new Intent(this, BluetoothLeService.class);
          bindService(gattServiceIntent, mServiceConnection, BIND_AUTO_CREATE);
      }
  
      @Override
      protected void onResume() {
          super.onResume();
          registerReceiver(mGattUpdateReceiver, makeGattUpdateIntentFilter());
          if (mBluetoothLeService != null) {
              final boolean result = mBluetoothLeService.connect(mDeviceAddress);
              Log.d(TAG, "Connect request result=" + result);
          }
      }
  
      @Override
      protected void onPause() {
          super.onPause();
          unregisterReceiver(mGattUpdateReceiver);
      }
  
      @Override
      protected void onDestroy() {
          super.onDestroy();
          unbindService(mServiceConnection);
          mBluetoothLeService = null;
      }
  
      @Override
      public boolean onCreateOptionsMenu(Menu menu) {
          getMenuInflater().inflate(R.menu.gatt_services, menu);
          if (mConnected) {
              menu.findItem(R.id.menu_connect).setVisible(false);
              menu.findItem(R.id.menu_disconnect).setVisible(true);
          } else {
              menu.findItem(R.id.menu_connect).setVisible(true);
              menu.findItem(R.id.menu_disconnect).setVisible(false);
          }
          return true;
      }
  
      @Override
      public boolean onOptionsItemSelected(MenuItem item) {
          switch(item.getItemId()) {
              case R.id.menu_connect:
                  mBluetoothLeService.connect(mDeviceAddress);
                  return true;
              case R.id.menu_disconnect:
                  mBluetoothLeService.disconnect();
                  return true;
              case android.R.id.home:
                  onBackPressed();
                  return true;
          }
          return super.onOptionsItemSelected(item);
      }
  
      private void updateConnectionState(final int resourceId) {
          runOnUiThread(new Runnable() {
              @Override
              public void run() {
                  mConnectionState.setText(resourceId);
              }
          });
      }
  
      private void displayData(String data) {
          if (data != null) {
              mDataField.setText(data);
          }
      }
  
      // Demonstrates how to iterate through the supported GATT Services/Characteristics.
      // In this sample, we populate the data structure that is bound to the ExpandableListView
      // on the UI.
      private void displayGattServices(List<BluetoothGattService> gattServices) {
          if (gattServices == null) return;
          String uuid = null;
          String unknownServiceString = getResources().getString(R.string.unknown_service);
          String unknownCharaString = getResources().getString(R.string.unknown_characteristic);
          ArrayList<HashMap<String, String>> gattServiceData = new ArrayList<HashMap<String, String>>();
          ArrayList<ArrayList<HashMap<String, String>>> gattCharacteristicData
                  = new ArrayList<ArrayList<HashMap<String, String>>>();
          mGattCharacteristics = new ArrayList<ArrayList<BluetoothGattCharacteristic>>();
  
          // Loops through available GATT Services.
          for (BluetoothGattService gattService : gattServices) {
              HashMap<String, String> currentServiceData = new HashMap<String, String>();
              uuid = gattService.getUuid().toString();
              currentServiceData.put(
                      LIST_NAME, SampleGattAttributes.lookup(uuid, unknownServiceString));
              currentServiceData.put(LIST_UUID, uuid);
              gattServiceData.add(currentServiceData);
  
              ArrayList<HashMap<String, String>> gattCharacteristicGroupData =
                      new ArrayList<HashMap<String, String>>();
              List<BluetoothGattCharacteristic> gattCharacteristics =
                      gattService.getCharacteristics();
              ArrayList<BluetoothGattCharacteristic> charas =
                      new ArrayList<BluetoothGattCharacteristic>();
  
              // Loops through available Characteristics.
              for (BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) {
                  charas.add(gattCharacteristic);
                  HashMap<String, String> currentCharaData = new HashMap<String, String>();
                  uuid = gattCharacteristic.getUuid().toString();
                  currentCharaData.put(
                          LIST_NAME, SampleGattAttributes.lookup(uuid, unknownCharaString));
                  currentCharaData.put(LIST_UUID, uuid);
                  gattCharacteristicGroupData.add(currentCharaData);
              }
              mGattCharacteristics.add(charas);
              gattCharacteristicData.add(gattCharacteristicGroupData);
          }
  
          SimpleExpandableListAdapter gattServiceAdapter = new SimpleExpandableListAdapter(
                  this,
                  gattServiceData,
                  android.R.layout.simple_expandable_list_item_2,
                  new String[] {LIST_NAME, LIST_UUID},
                  new int[] { android.R.id.text1, android.R.id.text2 },
                  gattCharacteristicData,
                  android.R.layout.simple_expandable_list_item_2,
                  new String[] {LIST_NAME, LIST_UUID},
                  new int[] { android.R.id.text1, android.R.id.text2 }
          );
          mGattServicesList.setAdapter(gattServiceAdapter);
      }
  
      private static IntentFilter makeGattUpdateIntentFilter() {
          final IntentFilter intentFilter = new IntentFilter();
          intentFilter.addAction(BluetoothLeService.ACTION_GATT_CONNECTED);
          intentFilter.addAction(BluetoothLeService.ACTION_GATT_DISCONNECTED);
          intentFilter.addAction(BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED);
          intentFilter.addAction(BluetoothLeService.ACTION_DATA_AVAILABLE);
          return intentFilter;
      }
  }
  