@Override
    public final void onServicesDiscovered(final BluetoothGatt gatt, final int status) {
      if (status == BluetoothGatt.GATT_SUCCESS) {
        Logger.i(mLogSession, "Services Discovered");
        if (isRequiredServiceSupported(gatt)) {
          Logger.v(mLogSession, "Primary service found");
          final boolean optionalServicesFound = isOptionalServiceSupported(gatt);
          if (optionalServicesFound) Logger.v(mLogSession, "Secondary service found");

          // Notify the parent activity
          mCallbacks.onServicesDiscovered(optionalServicesFound);

          // Obtain the queue of initialization requests
          mInitInProgress = true;
          mInitQueue = initGatt(gatt);

          // When the device is bonded and has Service Changed characteristic, the indications must
          // be enabled first.
          // In case this method returns true we have to continue in the onDescriptorWrite callback
          if (ensureServiceChangedEnabled(gatt)) return;

          // We have discovered services, let's start by reading the battery level value. If the
          // characteristic is not readable, try to enable notifications.
          // If there is no Battery service, proceed with the initialization queue.
          if (!readBatteryLevel()) nextRequest();
        } else {
          Logger.w(mLogSession, "Device is not supported");
          mCallbacks.onDeviceNotSupported();
          disconnect();
        }
      } else {
        DebugLogger.e(TAG, "onServicesDiscovered error " + status);
        onError(ERROR_DISCOVERY_SERVICE, status);
      }
    }
  /**
   * This method tries to enable notifications on the Battery Level characteristic.
   *
   * @param enable <code>true</code> to enable battery notifications, false to disable
   * @return true if request has been sent
   */
  public boolean setBatteryNotifications(final boolean enable) {
    final BluetoothGatt gatt = mBluetoothGatt;
    if (gatt == null) {
      return false;
    }

    final BluetoothGattService batteryService = gatt.getService(BATTERY_SERVICE);
    if (batteryService == null) return false;

    final BluetoothGattCharacteristic batteryLevelCharacteristic =
        batteryService.getCharacteristic(BATTERY_LEVEL_CHARACTERISTIC);
    if (batteryLevelCharacteristic == null) return false;

    // Check characteristic property
    final int properties = batteryLevelCharacteristic.getProperties();
    if ((properties & BluetoothGattCharacteristic.PROPERTY_NOTIFY) == 0) return false;

    gatt.setCharacteristicNotification(batteryLevelCharacteristic, enable);
    final BluetoothGattDescriptor descriptor =
        batteryLevelCharacteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID);
    if (descriptor != null) {
      if (enable) {
        descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
        Logger.a(mLogSession, "Enabling battery level notifications...");
        Logger.v(mLogSession, "Enabling notifications for " + BATTERY_LEVEL_CHARACTERISTIC);
        Logger.d(
            mLogSession,
            "gatt.writeDescriptor("
                + CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID
                + ", value=0x01-00)");
      } else {
        descriptor.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
        Logger.a(mLogSession, "Disabling battery level notifications...");
        Logger.v(mLogSession, "Disabling notifications for " + BATTERY_LEVEL_CHARACTERISTIC);
        Logger.d(
            mLogSession,
            "gatt.writeDescriptor("
                + CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID
                + ", value=0x00-00)");
      }
      return gatt.writeDescriptor(descriptor);
    }
    return false;
  }
  /**
   * Sends the read request to the given characteristic.
   *
   * @param characteristic the characteristic to read
   * @return true if request has been sent
   */
  protected final boolean readCharacteristic(final BluetoothGattCharacteristic characteristic) {
    final BluetoothGatt gatt = mBluetoothGatt;
    if (gatt == null || characteristic == null) return false;

    // Check characteristic property
    final int properties = characteristic.getProperties();
    if ((properties & BluetoothGattCharacteristic.PROPERTY_READ) == 0) return false;

    Logger.v(mLogSession, "Reading characteristic " + characteristic.getUuid());
    Logger.d(mLogSession, "gatt.readCharacteristic(" + characteristic.getUuid() + ")");
    return gatt.readCharacteristic(characteristic);
  }
  /**
   * Disconnects from the device. Does nothing if not connected.
   *
   * @return true if device is to be disconnected. False if it was already disconnected.
   */
  public boolean disconnect() {
    mUserDisconnected = true;

    if (mConnected && mBluetoothGatt != null) {
      Logger.v(mLogSession, "Disconnecting...");
      mCallbacks.onDeviceDisconnecting();
      Logger.d(mLogSession, "gatt.disconnect()");
      mBluetoothGatt.disconnect();
      return true;
    }
    return false;
  }
  /**
   * Writes the characteristic value to the given characteristic.
   *
   * @param characteristic the characteristic to write to
   * @return true if request has been sent
   */
  protected final boolean writeCharacteristic(final BluetoothGattCharacteristic characteristic) {
    final BluetoothGatt gatt = mBluetoothGatt;
    if (gatt == null || characteristic == null) return false;

    // Check characteristic property
    final int properties = characteristic.getProperties();
    if ((properties
            & (BluetoothGattCharacteristic.PROPERTY_WRITE
                | BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE))
        == 0) return false;

    Logger.v(mLogSession, "Writing characteristic " + characteristic.getUuid());
    Logger.d(mLogSession, "gatt.writeCharacteristic(" + characteristic.getUuid() + ")");
    return gatt.writeCharacteristic(characteristic);
  }
  /**
   * Connects to the Bluetooth Smart device
   *
   * @param device a device to connect to
   */
  public void connect(final BluetoothDevice device) {
    if (mConnected) return;

    if (mBluetoothGatt != null) {
      Logger.d(mLogSession, "gatt.close()");
      mBluetoothGatt.close();
      mBluetoothGatt = null;
    }

    final boolean autoConnect = shouldAutoConnect();
    mUserDisconnected =
        !autoConnect; // We will receive Linkloss events only when the device is connected with
                      // autoConnect=true
    Logger.v(mLogSession, "Connecting...");
    Logger.d(mLogSession, "gatt = device.connectGatt(autoConnect = " + autoConnect + ")");
    mBluetoothGatt = device.connectGatt(mContext, autoConnect, getGattCallback());
  }
        @Override
        public void onReceive(final Context context, final Intent intent) {
          final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
          final int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1);
          final int previousBondState =
              intent.getIntExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, -1);

          // Skip other devices
          if (mBluetoothGatt == null
              || !device.getAddress().equals(mBluetoothGatt.getDevice().getAddress())) return;

          Logger.d(
              mLogSession,
              "[Broadcast] Action received: "
                  + BluetoothDevice.ACTION_BOND_STATE_CHANGED
                  + ", bond state changed to: "
                  + bondStateToString(bondState)
                  + " ("
                  + bondState
                  + ")");
          DebugLogger.i(
              TAG,
              "Bond state changed for: "
                  + device.getName()
                  + " new state: "
                  + bondState
                  + " previous: "
                  + previousBondState);

          switch (bondState) {
            case BluetoothDevice.BOND_BONDING:
              mCallbacks.onBondingRequired();
              break;
            case BluetoothDevice.BOND_BONDED:
              Logger.i(mLogSession, "Device bonded");
              mCallbacks.onBonded();

              // Start initializing again.
              // In fact, bonding forces additional, internal service discovery (at least on Nexus
              // devices), so this method may safely be used to start this process again.
              Logger.v(mLogSession, "Discovering Services...");
              Logger.d(mLogSession, "gatt.discoverServices()");
              mBluetoothGatt.discoverServices();
              break;
          }
        }
  /**
   * Enables indications on given characteristic
   *
   * @return true is the request has been sent, false if one of the arguments was <code>null</code>
   *     or the characteristic does not have the CCCD.
   */
  protected final boolean enableIndications(final BluetoothGattCharacteristic characteristic) {
    final BluetoothGatt gatt = mBluetoothGatt;
    if (gatt == null || characteristic == null) return false;

    // Check characteristic property
    final int properties = characteristic.getProperties();
    if ((properties & BluetoothGattCharacteristic.PROPERTY_INDICATE) == 0) return false;

    gatt.setCharacteristicNotification(characteristic, true);
    final BluetoothGattDescriptor descriptor =
        characteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID);
    if (descriptor != null) {
      descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE);
      Logger.v(mLogSession, "Enabling indications for " + characteristic.getUuid());
      Logger.d(
          mLogSession,
          "gatt.writeDescriptor("
              + CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID
              + ", value=0x02-00)");
      return gatt.writeDescriptor(descriptor);
    }
    return false;
  }