/** Reads the device info characteristics and updates the UI components */
  private void readDeviceInfoCharacteristics() {
    // Get all readable characteristics and descriptors of interest and add
    // request to a request queue

    BluetoothGattCharacteristic characteristic = null;

    // Get model number
    characteristic =
        GattUtils.getCharacteristic(
            mPickedDeviceGatt, Constants.DEVICE_INFO_SERVICE_UUID, Constants.MODEL_NUMBER_UUID);
    mRequestQueue.addReadCharacteristic(mPickedDeviceGatt, characteristic);

    // Get manufacturer name
    characteristic =
        GattUtils.getCharacteristic(
            mPickedDeviceGatt,
            Constants.DEVICE_INFO_SERVICE_UUID,
            Constants.MANUFACTURER_NAME_UUID);
    mRequestQueue.addReadCharacteristic(mPickedDeviceGatt, characteristic);

    // Get system Id
    characteristic =
        GattUtils.getCharacteristic(
            mPickedDeviceGatt, Constants.DEVICE_INFO_SERVICE_UUID, Constants.SYSTEM_ID_UUID);
    mRequestQueue.addReadCharacteristic(mPickedDeviceGatt, characteristic);
    mRequestQueue.execute();
  }
 /** Write the hello input descriptor to the device */
 private void writeHelloInputCharacteristic() {
   // Set the enable/disable notification settings
   BluetoothGattCharacteristic notifyCharacteristic =
       GattUtils.getCharacteristic(
           mPickedDeviceGatt,
           Constants.HELLO_SERVICE_UUID,
           Constants.HELLO_CHARACTERISTIC_INPUT_UUID);
   int notificationState = mSpinnerHelloInput.getSelectedItemPosition();
   if (notificationState >= 0 && notificationState <= 2) {
     BluetoothGattDescriptor descriptor =
         GattUtils.getDescriptor(
             mPickedDeviceGatt,
             Constants.HELLO_SERVICE_UUID,
             Constants.HELLO_CHARACTERISTIC_INPUT_UUID,
             Constants.CLIENT_CONFIG_DESCRIPTOR_UUID);
     if (notificationState > 0) {
       mPickedDeviceGatt.setCharacteristicNotification(notifyCharacteristic, true);
       if (notificationState == 1) {
         descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
       } else if (notificationState == 2) {
         descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE);
       }
     } else {
       mPickedDeviceGatt.setCharacteristicNotification(notifyCharacteristic, false);
       descriptor.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
     }
     mRequestQueue.addWriteDescriptor(mPickedDeviceGatt, descriptor);
     mRequestQueue.execute();
   }
 }
 /**
  * Reads the manufacturer name, model number, system id, battery level from the device and updates
  * the UI
  */
 private void readHelloConfigurationCharacteristic() {
   BluetoothGattCharacteristic characteristic = null;
   characteristic =
       GattUtils.getCharacteristic(
           mPickedDeviceGatt,
           Constants.HELLO_SERVICE_UUID,
           Constants.HELLO_CHARACTERISTIC_CONFIGURATION_UUID);
   mRequestQueue.addReadCharacteristic(mPickedDeviceGatt, characteristic);
   mRequestQueue.execute();
 }
  /** Reads the battery characteristics and updates the UI components */
  private void readBatteryCharacteristic() {
    // Get all readable characteristics and descriptors of interest and add
    // request to a request queue

    BluetoothGattCharacteristic characteristic = null;
    // Get battery level
    characteristic =
        GattUtils.getCharacteristic(
            mPickedDeviceGatt, Constants.BATTERY_SERVICE_UUID, Constants.BATTERY_LEVEL_UUID);
    mRequestQueue.addReadCharacteristic(mPickedDeviceGatt, characteristic);
    mRequestQueue.execute();
  }
  /** Reads the hello input characteristic and updates the UI components */
  private void readHelloInputCharacteristic() {
    // Get all readable characteristics and descriptors of interest and add
    // request to a request queue
    BluetoothGattDescriptor descriptor = null;

    // Get client config descriptor: enable/disable notification
    descriptor =
        GattUtils.getDescriptor(
            mPickedDeviceGatt,
            Constants.HELLO_SERVICE_UUID,
            Constants.HELLO_CHARACTERISTIC_INPUT_UUID,
            Constants.CLIENT_CONFIG_DESCRIPTOR_UUID);
    mRequestQueue.addReadDescriptor(mPickedDeviceGatt, descriptor);
    mRequestQueue.execute();
  }
  /** Write the hello configuration characteristic to the device */
  private void writeHelloConfigurationCharacteristic() {
    BluetoothGattCharacteristic characteristic = null;

    int blinks = 0;
    try {
      blinks = Integer.parseInt(mEditTextHelloConfiguration.getText().toString());
      characteristic =
          GattUtils.getCharacteristic(
              mPickedDeviceGatt,
              Constants.HELLO_SERVICE_UUID,
              Constants.HELLO_CHARACTERISTIC_CONFIGURATION_UUID);
      characteristic.setValue(blinks, BluetoothGattCharacteristic.FORMAT_UINT8, 0);
      mRequestQueue.addWriteCharacteristic(mPickedDeviceGatt, characteristic);
    } catch (Throwable t) {
      Log.w(TAG, "invalid number of notifications");
      mEditTextHelloConfiguration.setText("0");
    }
    mRequestQueue.addWriteCharacteristic(mPickedDeviceGatt, characteristic);
    mRequestQueue.execute();
  }
/** Main activity for the the Hello Client application */
public class MainActivity extends Activity
    implements OnClickListener, Callback, OnItemSelectedListener {
  private static final String TAG = Constants.TAG_PREFIX + "MainActivity";
  private static final String FRAGMENT_DEVICE_PICKER = "DevicePickerDialog";

  // Used to format timestamp for notification entries in the notification
  // view
  private static final SimpleDateFormat mFormatter =
      new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");

  /**
   * Callback object that the LE Gatt service calls to report callback events that occur
   *
   * @author fredc
   */
  private class GattCallback extends BluetoothGattCallback {

    /** Callback invoked by Android framework and a LE connection state change occurs */
    @Override
    public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
      Log.d(
          MainActivity.TAG,
          "onConnectionStateChange(): address=="
              + gatt.getDevice().getAddress()
              + ", status = "
              + status
              + ", state="
              + newState);
      boolean isConnected = (newState == BluetoothAdapter.STATE_CONNECTED);

      boolean isOk = (status == 0);
      if (isConnected && isOk) {
        // Discover services, and return connection state = connected
        // after services discovered
        isOk = gatt.discoverServices();
        if (isOk) {
          return;
        }
      }

      // If we got here, this is a disconnect with or without error
      // close gatt connection
      if (!isOk) {
        gatt.close();
      }
      processConnectionStateChanged(false, !isOk);
    }

    /** Callback invoked by Android framework when LE service discovery completes */
    @Override
    public void onServicesDiscovered(BluetoothGatt gatt, int status) {
      if (status != 0) {
        // Error occurred. close the ocnnection and return a
        // disconnected status
        gatt.close();
        try {
          processConnectionStateChanged(false, true);
        } catch (Throwable t) {
          Log.e(TAG, "error", t);
        }
      } else {
        try {
          processConnectionStateChanged(true, false);
        } catch (Throwable t) {
          Log.e(TAG, "error", t);
        }
      }
    }

    /** Callback invoked by Android framework when a characteristic read completes */
    @Override
    public void onCharacteristicRead(
        BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
      if (status == 0) {
        try {
          processCharacteristicRead(characteristic);
        } catch (Throwable t) {
          Log.e(TAG, "error", t);
        }
      }
      mRequestQueue.next(); // Execute the next queued request, if
      // any
    }

    /** Callback invoked by Android framework when a descriptor read completes */
    @Override
    public void onDescriptorRead(
        BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
      if (status == 0) {
        try {
          processDescriptorRead(descriptor);
        } catch (Throwable t) {
          Log.e(TAG, "error", t);
        }
      }
      mRequestQueue.next(); // Execute the next queued request, if
      // any
    }

    /** Callback invoked by Android framework when a characteristic notification occurs */
    @Override
    public void onCharacteristicChanged(
        BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
      try {
        processCharacteristicNotification(characteristic);
      } catch (Throwable t) {
        Log.e(TAG, "error", t);
      }
    }

    /** Callback invoked by Android framework when a descriptor write completes */
    @Override
    public void onDescriptorWrite(
        BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
      if (status == 0) {
        try {
          processDescriptorWrite(descriptor);
        } catch (Throwable t) {
          Log.e(TAG, "error", t);
        }
      }

      mRequestQueue.next(); // Execute the next queued request, if any
    }

    /** Callback invoked by Android framework when a characteristic write completes */
    @Override
    public void onCharacteristicWrite(
        BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
      super.onCharacteristicWrite(gatt, characteristic, status);
      if (status == 0) {
        try {
          processCharacteristicWrite(characteristic);
        } catch (Throwable t) {
          Log.e(TAG, "error", t);
        }
      }

      mRequestQueue.next(); // Execute the next queued request, if any
    }
  }

  // UI Components
  // Device picker components
  private LinearLayout mButtonSelectDevice; // Button to start device picker
  private DevicePickerFragment mDevicePicker;
  private TextView mTextDeviceName; // Displays device's name
  private TextView mTextDeviceAddress; // Displays device's address

  // Connection components
  private Button mButtonConnect; // Button to connect to a device
  private Button mButtonDisconnect; // Button to connect from a device
  private TextView mTextConnectionState; // Displays current connection state

  // Hello service components
  private TextView mLabelHelloInput;
  private Spinner mSpinnerHelloInput;
  private Button mButtonReadHelloInput;
  private TextView mLabelHelloConfiguration;
  private EditText mEditTextHelloConfiguration;
  private Button mButtonReadHelloConfiguration;
  private Button mButtonWriteHelloConfiguration;

  // Device info service components
  private TextView mTextManufacturerName; // Displays the manufacterer name
  private TextView mTextModelNumber; // Displays the model number
  private TextView mTextSystemId; // Displays the system id
  private Button mButtonReadDeviceInfo; // Button to refresh the device
  // information

  // Battery service components
  private TextView mTextBatteryLevel; // Displays the battery level
  private Button mButtonReadBatteryInfo; // Button to refresh the device
  // information

  // Notification components
  private TextView mTextAreaNotification; // Displays notifications
  private Button mButtonClear; // Button to clear the notifications

  private final GattCallback mGattCallback = new GattCallback();
  private final RequestQueue mRequestQueue = GattUtils.createRequestQueue();
  private BluetoothAdapter mBtAdapter;
  private BluetoothDevice mPickedDevice;
  private BluetoothGatt mPickedDeviceGatt;
  private boolean mPickedDeviceIsConnected;
  private boolean mSyncNotificationSetting;

  /**
   * Helper function to show a toast notification message
   *
   * @param msg
   */
  private void showMessage(String msg) {
    Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
  }

  /**
   * Check Bluetooth is available and enabled, and initialize Bluetooth adapter
   *
   * @return
   */
  private boolean checkAndInitBluetooth() {
    mBtAdapter = BluetoothAdapter.getDefaultAdapter();
    if (mBtAdapter == null || !mBtAdapter.isEnabled()) {
      return false;
    }
    return true;
  }

  /**
   * Initialize the device picker
   *
   * @return
   */
  private void initDevicePicker() {
    mDevicePicker = DevicePickerFragment.createDialog(this, null, true);
  }

  /** Cleanup the device picker */
  private void cleanupDevicePicker() {
    if (mDevicePicker != null) {
      mDevicePicker = null;
    }
  }

  private void closeDevice() {
    if (mPickedDeviceGatt != null) {
      mPickedDeviceGatt.close();
      mPickedDeviceGatt = null;
    }
  }

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Check bluetooth is available. If not, exit
    if (!checkAndInitBluetooth()) {
      showMessage(getString(R.string.error_bluetooth_not_available));
      finish();
      return;
    }

    // Initialize the UI components, and register a listeners
    setContentView(R.layout.main);

    // Load device picker components
    mButtonSelectDevice = (LinearLayout) findViewById(R.id.btn_selectdevice);
    mButtonSelectDevice.setOnClickListener(this);
    mTextDeviceName = (TextView) findViewById(R.id.deviceName);
    mTextDeviceAddress = (TextView) findViewById(R.id.deviceAddress);

    // Load connection components
    mButtonConnect = (Button) findViewById(R.id.btn_connect);
    mButtonConnect.setOnClickListener(this);
    mButtonDisconnect = (Button) findViewById(R.id.btn_disconnect);
    mButtonDisconnect.setOnClickListener(this);
    mTextConnectionState = (TextView) findViewById(R.id.connectionState);

    // Load hello service components
    mLabelHelloInput = (TextView) findViewById(R.id.label_hello_input);
    mLabelHelloInput.setText(
        getString(R.string.label_hello_input, Constants.HELLO_CHARACTERISTIC_INPUT_UUID));
    mSpinnerHelloInput = (Spinner) findViewById(R.id.sp_hello_input);
    mSpinnerHelloInput.setEnabled(false);

    mButtonReadHelloInput = (Button) findViewById(R.id.btn_read_hello_input);
    mButtonReadHelloInput.setOnClickListener(this);

    mLabelHelloConfiguration = (TextView) findViewById(R.id.label_hello_configuration);
    mLabelHelloConfiguration.setText(
        getString(
            R.string.label_hello_configuration, Constants.HELLO_CHARACTERISTIC_CONFIGURATION_UUID));
    mEditTextHelloConfiguration = (EditText) findViewById(R.id.value_hello_configuration);
    mButtonReadHelloConfiguration = (Button) findViewById(R.id.btn_read_hello_configuration);
    mButtonReadHelloConfiguration.setOnClickListener(this);
    mButtonWriteHelloConfiguration = (Button) findViewById(R.id.btn_write_hello_configuration);
    mButtonWriteHelloConfiguration.setOnClickListener(this);

    // Load device info components
    mTextManufacturerName = (TextView) findViewById(R.id.value_manufacturer_name);
    mTextModelNumber = (TextView) findViewById(R.id.value_model_number);
    mTextSystemId = (TextView) findViewById(R.id.value_system_id);
    mButtonReadDeviceInfo = (Button) findViewById(R.id.btn_read_device_info);
    mButtonReadDeviceInfo.setOnClickListener(this);

    // Load battery info components
    mTextBatteryLevel = (TextView) findViewById(R.id.value_battery_lvel);
    mButtonReadBatteryInfo = (Button) findViewById(R.id.btn_read_battery);
    mButtonReadBatteryInfo.setOnClickListener(this);

    // Load notification components
    mTextAreaNotification = (TextView) findViewById(R.id.value_notifications);
    mTextAreaNotification.setMovementMethod(ScrollingMovementMethod.getInstance());
    mButtonClear = (Button) findViewById(R.id.btn_clear);
    mButtonClear.setOnClickListener(this);

    // Initialize the device picker UI fragment
    initDevicePicker();

    // refresh the UI component states
    updateWidgets();
  }

  /** Updates the UI widgets based on the latest connection state */
  private void updateWidgets() {
    if (mPickedDevice == null) {
      // No devices selected: set initial state
      mButtonConnect.setEnabled(false);
      mButtonDisconnect.setEnabled(false);
      mButtonSelectDevice.setEnabled(true);
      mTextDeviceName.setText(R.string.no_device);
      mTextDeviceAddress.setText("");
    } else {
      // Device picked, always set the connect/disconnect buttons enabled
      mButtonConnect.setEnabled(true);
      mButtonDisconnect.setEnabled(true);

      if (mPickedDeviceIsConnected) {
        // Set resources when connected

        // Disable selecting new device when connected
        mButtonSelectDevice.setEnabled(false);

        // Set the connection state status
        mTextConnectionState.setText(getString(R.string.connected));

        // Update hello service components
        mSpinnerHelloInput.setEnabled(true);
        mSpinnerHelloInput.setOnItemSelectedListener(this);
        mButtonReadHelloInput.setEnabled(true);
        mEditTextHelloConfiguration.setEnabled(true);
        mButtonReadHelloConfiguration.setEnabled(true);
        mButtonWriteHelloConfiguration.setEnabled(true);

        // Update device info components
        mButtonReadDeviceInfo.setEnabled(true);

        // Update battery components
        mButtonReadBatteryInfo.setEnabled(true);

        // Update notification components
        mTextAreaNotification.setEnabled(true);
        mButtonClear.setEnabled(true);

      } else {
        // Update resources when disconnected

        // Enable selecting new device when connected
        mButtonSelectDevice.setEnabled(true);

        // Set the connection state status
        mTextConnectionState.setText(getString(R.string.disconnected));

        // Update hello service components
        mSpinnerHelloInput.setEnabled(false);
        mSpinnerHelloInput.setOnItemSelectedListener(null);
        mSpinnerHelloInput.setSelection(0);
        mButtonReadHelloInput.setEnabled(false);

        mEditTextHelloConfiguration.setEnabled(false);
        mEditTextHelloConfiguration.setText("");
        mButtonReadHelloConfiguration.setEnabled(false);
        mButtonWriteHelloConfiguration.setEnabled(false);

        // Update device info components
        mButtonReadDeviceInfo.setEnabled(false);
        mTextManufacturerName.setText(R.string.no_value);
        mTextModelNumber.setText(R.string.no_value);
        mTextSystemId.setText(R.string.no_value);

        // Update battery components
        mButtonReadBatteryInfo.setEnabled(false);
        mTextBatteryLevel.setText(R.string.no_value);

        // Update notification components
        mTextAreaNotification.setEnabled(false);
        mTextAreaNotification.setText("");
        mButtonClear.setEnabled(false);
      }
    }
  }

  @Override
  public void onDestroy() {
    closeDevice();
    cleanupDevicePicker();
    super.onDestroy();
  }

  /** Callback invoked when buttons/switches clicked */
  @Override
  public void onClick(View v) {
    if (v == mButtonSelectDevice) {
      // Start the device selector
      mDevicePicker.show(getFragmentManager(), FRAGMENT_DEVICE_PICKER);
    } else if (v == mButtonConnect) {
      // Start device connection
      connect();
    } else if (v == mButtonDisconnect) {
      // Start device disconnect
      disconnect();
    } else if (v == mButtonReadDeviceInfo) {
      // Start reading the device information characteristics
      readDeviceInfoCharacteristics();
    } else if (v == mButtonReadBatteryInfo) {
      // Start reading the battery characteristics
      readBatteryCharacteristic();
    } else if (v == mButtonReadHelloConfiguration) {
      // Start reading the hello configuration characteristics
    } else if (v == mButtonWriteHelloConfiguration) {
      // Start writing the hello configuration characteristics
      writeHelloConfigurationCharacteristic();
    } else if (v == mButtonReadHelloInput) {
      // Start reading hello input descriptors
      readHelloInputCharacteristic();
    } else if (v == mButtonClear) {
      // Clear the notification area
      mTextAreaNotification.setText("");
    }
  }

  /**
   * Called when the hello input is selected by user
   *
   * @param view
   * @param view2
   * @param pos
   * @param id
   */
  @Override
  public void onItemSelected(AdapterView<?> view, View view2, int pos, long id) {
    if (view == mSpinnerHelloInput) {
      // Update the input characteristic on the device
      writeHelloInputCharacteristic();
    }
  }

  /** Called when no hello input is selected */
  @Override
  public void onNothingSelected(AdapterView<?> view) {}

  /**
   * Callback invoked when a device was picked from the device picker
   *
   * @param device
   */
  @Override
  public void onDevicePicked(BluetoothDevice device) {
    Log.d(TAG, "onDevicePicked: " + device == null ? "" : device.getAddress());
    // Close any outstanding connections to remote devices
    closeDevice();

    // Get the remote device object
    String address = device.getAddress();
    mPickedDevice = mBtAdapter.getRemoteDevice(address);

    // Get the name
    String name = mPickedDevice.getName();
    if (name == null || name.isEmpty()) {
      name = address;
    }

    // Set UI resources
    mTextDeviceName.setText(name);
    mTextDeviceAddress.setText(address);
    // Update the connect widget
    mButtonConnect.setEnabled(true);
    mButtonDisconnect.setEnabled(true);
  }

  /** Callback invoked when a devicepicker was dismissed without a device picked */
  @Override
  public void onDevicePickError() {
    Log.d(TAG, "onDevicePickError");
  }

  /** Callback invoked when a devicepicker encountered an unexpected error */
  @Override
  public void onDevicePickCancelled() {
    Log.d(TAG, "onDevicePickCancelled");
  }

  /** Connect to the picked device */
  private void connect() {
    if (mPickedDevice == null) {
      showMessage(
          getString(R.string.error_connect, mPickedDevice.getName(), mPickedDevice.getAddress()));
      return;
    }

    mPickedDeviceGatt = mPickedDevice.connectGatt(this, false, mGattCallback);

    if (mPickedDeviceGatt == null) {
      showMessage(
          getString(R.string.error_connect, mPickedDevice.getName(), mPickedDevice.getAddress()));
    }
  }

  /** Disconnects the picked device */
  private void disconnect() {
    if (mPickedDeviceGatt != null) {
      mPickedDeviceGatt.disconnect();
      closeDevice();
    }
  }

  /**
   * Called when a gatt connection state changes. This function updates the UI
   *
   * @param gatt
   */
  private void processConnectionStateChanged(final boolean isConnected, final boolean hasError) {
    runOnUiThread(
        new Runnable() {

          @Override
          public void run() {
            if (hasError) {
              showMessage(
                  getString(
                      R.string.error_connect, mPickedDevice.getName(), mPickedDevice.getAddress()));
            }
            mPickedDeviceIsConnected = isConnected;
            updateWidgets();

            // Refresh the device information
            if (mPickedDeviceIsConnected) {
              mSyncNotificationSetting = true;
              readEverything();
            }
          }
        });
  }

  /** Reads the device info characteristics and updates the UI components */
  private void readDeviceInfoCharacteristics() {
    // Get all readable characteristics and descriptors of interest and add
    // request to a request queue

    BluetoothGattCharacteristic characteristic = null;

    // Get model number
    characteristic =
        GattUtils.getCharacteristic(
            mPickedDeviceGatt, Constants.DEVICE_INFO_SERVICE_UUID, Constants.MODEL_NUMBER_UUID);
    mRequestQueue.addReadCharacteristic(mPickedDeviceGatt, characteristic);

    // Get manufacturer name
    characteristic =
        GattUtils.getCharacteristic(
            mPickedDeviceGatt,
            Constants.DEVICE_INFO_SERVICE_UUID,
            Constants.MANUFACTURER_NAME_UUID);
    mRequestQueue.addReadCharacteristic(mPickedDeviceGatt, characteristic);

    // Get system Id
    characteristic =
        GattUtils.getCharacteristic(
            mPickedDeviceGatt, Constants.DEVICE_INFO_SERVICE_UUID, Constants.SYSTEM_ID_UUID);
    mRequestQueue.addReadCharacteristic(mPickedDeviceGatt, characteristic);
    mRequestQueue.execute();
  }

  /** Reads the battery characteristics and updates the UI components */
  private void readBatteryCharacteristic() {
    // Get all readable characteristics and descriptors of interest and add
    // request to a request queue

    BluetoothGattCharacteristic characteristic = null;
    // Get battery level
    characteristic =
        GattUtils.getCharacteristic(
            mPickedDeviceGatt, Constants.BATTERY_SERVICE_UUID, Constants.BATTERY_LEVEL_UUID);
    mRequestQueue.addReadCharacteristic(mPickedDeviceGatt, characteristic);
    mRequestQueue.execute();
  }

  /** Reads the hello input characteristic and updates the UI components */
  private void readHelloInputCharacteristic() {
    // Get all readable characteristics and descriptors of interest and add
    // request to a request queue
    BluetoothGattDescriptor descriptor = null;

    // Get client config descriptor: enable/disable notification
    descriptor =
        GattUtils.getDescriptor(
            mPickedDeviceGatt,
            Constants.HELLO_SERVICE_UUID,
            Constants.HELLO_CHARACTERISTIC_INPUT_UUID,
            Constants.CLIENT_CONFIG_DESCRIPTOR_UUID);
    mRequestQueue.addReadDescriptor(mPickedDeviceGatt, descriptor);
    mRequestQueue.execute();
  }

  /**
   * Reads the manufacturer name, model number, system id, battery level from the device and updates
   * the UI
   */
  private void readHelloConfigurationCharacteristic() {
    BluetoothGattCharacteristic characteristic = null;
    characteristic =
        GattUtils.getCharacteristic(
            mPickedDeviceGatt,
            Constants.HELLO_SERVICE_UUID,
            Constants.HELLO_CHARACTERISTIC_CONFIGURATION_UUID);
    mRequestQueue.addReadCharacteristic(mPickedDeviceGatt, characteristic);
    mRequestQueue.execute();
  }

  /** Read every characteristic on the device */
  private void readEverything() {
    readDeviceInfoCharacteristics();
    readBatteryCharacteristic();
    readHelloConfigurationCharacteristic();
    readHelloInputCharacteristic();
  }

  /** Write the hello configuration characteristic to the device */
  private void writeHelloConfigurationCharacteristic() {
    BluetoothGattCharacteristic characteristic = null;

    int blinks = 0;
    try {
      blinks = Integer.parseInt(mEditTextHelloConfiguration.getText().toString());
      characteristic =
          GattUtils.getCharacteristic(
              mPickedDeviceGatt,
              Constants.HELLO_SERVICE_UUID,
              Constants.HELLO_CHARACTERISTIC_CONFIGURATION_UUID);
      characteristic.setValue(blinks, BluetoothGattCharacteristic.FORMAT_UINT8, 0);
      mRequestQueue.addWriteCharacteristic(mPickedDeviceGatt, characteristic);
    } catch (Throwable t) {
      Log.w(TAG, "invalid number of notifications");
      mEditTextHelloConfiguration.setText("0");
    }
    mRequestQueue.addWriteCharacteristic(mPickedDeviceGatt, characteristic);
    mRequestQueue.execute();
  }

  /** Write the hello input descriptor to the device */
  private void writeHelloInputCharacteristic() {
    // Set the enable/disable notification settings
    BluetoothGattCharacteristic notifyCharacteristic =
        GattUtils.getCharacteristic(
            mPickedDeviceGatt,
            Constants.HELLO_SERVICE_UUID,
            Constants.HELLO_CHARACTERISTIC_INPUT_UUID);
    int notificationState = mSpinnerHelloInput.getSelectedItemPosition();
    if (notificationState >= 0 && notificationState <= 2) {
      BluetoothGattDescriptor descriptor =
          GattUtils.getDescriptor(
              mPickedDeviceGatt,
              Constants.HELLO_SERVICE_UUID,
              Constants.HELLO_CHARACTERISTIC_INPUT_UUID,
              Constants.CLIENT_CONFIG_DESCRIPTOR_UUID);
      if (notificationState > 0) {
        mPickedDeviceGatt.setCharacteristicNotification(notifyCharacteristic, true);
        if (notificationState == 1) {
          descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
        } else if (notificationState == 2) {
          descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE);
        }
      } else {
        mPickedDeviceGatt.setCharacteristicNotification(notifyCharacteristic, false);
        descriptor.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
      }
      mRequestQueue.addWriteDescriptor(mPickedDeviceGatt, descriptor);
      mRequestQueue.execute();
    }
  }

  /**
   * Callback invoked by the Android framework when a read characteristic successfully completes
   *
   * @param characteristic
   */
  private void processCharacteristicRead(final BluetoothGattCharacteristic characteristic) {
    runOnUiThread(
        new Runnable() {

          @Override
          public void run() {
            UUID uuid = characteristic.getUuid();
            if (Constants.MANUFACTURER_NAME_UUID.equals(uuid)) {
              mTextManufacturerName.setText(characteristic.getStringValue(0));
            } else if (Constants.MODEL_NUMBER_UUID.equals(uuid)) {
              mTextModelNumber.setText(characteristic.getStringValue(0));
            } else if (Constants.SYSTEM_ID_UUID.equals(uuid)) {
              byte[] systemIdBytes = characteristic.getValue();
              long systemIdLong = GattUtils.unsignedBytesToLong(systemIdBytes, 8, 0);
              long manuId = 0xFFFFFFFFFFL & systemIdLong; // 40bits
              long orgId = 0xFFFFFFL & (systemIdLong >> 40);
              String manuIdString = String.format("%010X", manuId);
              String orgIdString = String.format("%06X", orgId);
              mTextSystemId.setText(orgIdString + " " + manuIdString);
            } else if (Constants.BATTERY_LEVEL_UUID.equals(uuid)) {
              int batteryLevel =
                  characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 0);
              mTextBatteryLevel.setText(String.valueOf(batteryLevel));
            } else if (Constants.HELLO_CHARACTERISTIC_CONFIGURATION_UUID.equals(uuid)) {
              int blinks = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 0);
              mEditTextHelloConfiguration.setText(String.valueOf(blinks));
            }
          }
        });
  }

  /**
   * Callback invoked by the Android framework when a read descriptor successfully completes
   *
   * @param descriptor
   */
  private void processDescriptorRead(final BluetoothGattDescriptor descriptor) {
    runOnUiThread(
        new Runnable() {

          @Override
          public void run() {
            UUID uuid = descriptor.getUuid();
            if (Constants.CLIENT_CONFIG_DESCRIPTOR_UUID.equals(uuid)) {
              byte[] descriptorBytes = descriptor.getValue();
              int notificationState = (int) GattUtils.unsignedBytesToLong(descriptorBytes, 2, 0);
              Log.d(TAG, "notificationState = " + notificationState);
              if (notificationState >= 0 && notificationState <= 2) {
                // Temporarily disable the spinner listener when we set
                // the selection
                mSpinnerHelloInput.setOnItemSelectedListener(null);
                mSpinnerHelloInput.setSelection(notificationState);
                // Re-enable the spinner listener
                mSpinnerHelloInput.setOnItemSelectedListener(MainActivity.this);
              }
              if (mSyncNotificationSetting && notificationState > 0) {
                // On initial connection, mSyncNotificationSetting is
                // set to indicate that we should set characteristic
                // notification if the descriptor is set
                mSyncNotificationSetting = false;
                BluetoothGattCharacteristic notifyCharacteristic =
                    GattUtils.getCharacteristic(
                        mPickedDeviceGatt,
                        Constants.HELLO_SERVICE_UUID,
                        Constants.HELLO_CHARACTERISTIC_INPUT_UUID);
                mPickedDeviceGatt.setCharacteristicNotification(notifyCharacteristic, true);
              }
            }
          }
        });
  }

  /**
   * Callback invoked by the Android framework when a write descriptor successfully completes
   *
   * @param descriptor
   */
  private void processDescriptorWrite(final BluetoothGattDescriptor descriptor) {
    runOnUiThread(
        new Runnable() {

          @Override
          public void run() {
            readEverything();
          }
        });
  }

  /**
   * Callback invoked by the Android framework when a write characteristic successfully completes
   *
   * @param characteristic
   */
  private void processCharacteristicWrite(final BluetoothGattCharacteristic characteristic) {
    runOnUiThread(
        new Runnable() {

          @Override
          public void run() {
            readEverything();
          }
        });
  }

  /**
   * Callback invoked by the Android framework when a characteristic notification is received
   *
   * @param characteristic
   */
  private void processCharacteristicNotification(final BluetoothGattCharacteristic characteristic) {
    runOnUiThread(
        new Runnable() {

          @Override
          public void run() {
            String s = characteristic.getStringValue(0);

            // Write a entry to the log viewer: "timestamp value"
            mTextAreaNotification.append(mFormatter.format(Calendar.getInstance().getTime()));
            mTextAreaNotification.append(" ");
            mTextAreaNotification.append(s);
            mTextAreaNotification.append("\n");

            // Auto scroll to the bottom
            Layout l = mTextAreaNotification.getLayout();
            if (l != null) {
              int scrollAmount =
                  l.getLineTop(mTextAreaNotification.getLineCount())
                      - mTextAreaNotification.getHeight();
              // if there is no need to scroll, scrollAmount will be <=0
              if (scrollAmount > 0) mTextAreaNotification.scrollTo(0, scrollAmount);
              else mTextAreaNotification.scrollTo(0, 0);
            }
          }
        });
  }
}