@Override
        public void onReceive(final Context context, final Intent intent) {
          final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);

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

          // String values are used as the constants are not available for Android 4.3.
          final int variant =
              intent.getIntExtra(
                  "android.bluetooth.device.extra.PAIRING_VARIANT" /*BluetoothDevice.EXTRA_PAIRING_VARIANT*/,
                  0);
          Logger.d(
              mLogSession,
              "[Broadcast] Action received: android.bluetooth.device.action.PAIRING_REQUEST" /*BluetoothDevice.ACTION_PAIRING_REQUEST*/
                  + ", pairing variant: "
                  + pairingVariantToString(variant)
                  + " ("
                  + variant
                  + ")");

          // The API below is available for Android 4.4 or newer.

          // An app may set the PIN here or set pairing confirmation (depending on the variant)
          // using:
          // device.setPin(new byte[] { '1', '2', '3', '4', '5', '6' });
          // device.setPairingConfirmation(true);
        }
  /**
   * 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;
          }
        }
  /**
   * 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);
  }
  /**
   * 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;
  }
    @Override
    public final void onConnectionStateChange(
        final BluetoothGatt gatt, final int status, final int newState) {
      Logger.d(
          mLogSession,
          "[Callback] Connection state changed with status: "
              + status
              + " and new state: "
              + newState
              + " ("
              + stateToString(newState)
              + ")");

      if (status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothProfile.STATE_CONNECTED) {
        // Notify the parent activity/service
        Logger.i(mLogSession, "Connected to " + gatt.getDevice().getAddress());
        mConnected = true;
        mCallbacks.onDeviceConnected();

        /*
         * The onConnectionStateChange event is triggered just after the Android connects to a device.
         * In case of bonded devices, the encryption is reestablished AFTER this callback is called.
         * Moreover, when the device has Service Changed indication enabled, and the list of services has changed (e.g. using the DFU),
         * the indication is received few milliseconds later, depending on the connection interval.
         * When received, Android will start performing a service discovery operation itself, internally.
         *
         * If the mBluetoothGatt.discoverServices() method would be invoked here, if would returned cached services,
         * as the SC indication wouldn't be received yet.
         * Therefore we have to postpone the service discovery operation until we are (almost, as there is no such callback) sure, that it had to be handled.
         * Our tests has shown that 600 ms is enough. It is important to call it AFTER receiving the SC indication, but not necessarily
         * after Android finishes the internal service discovery.
         *
         * NOTE: This applies only for bonded devices with Service Changed characteristic, but to be sure we will postpone
         * service discovery for all devices.
         */
        mHandler.postDelayed(
            new Runnable() {
              @Override
              public void run() {
                // Some proximity tags (e.g. nRF PROXIMITY) initialize bonding automatically when
                // connected.
                if (gatt.getDevice().getBondState() != BluetoothDevice.BOND_BONDING) {
                  Logger.v(mLogSession, "Discovering Services...");
                  Logger.d(mLogSession, "gatt.discoverServices()");
                  gatt.discoverServices();
                }
              }
            },
            600);
      } else {
        if (newState == BluetoothProfile.STATE_DISCONNECTED) {
          if (status != BluetoothGatt.GATT_SUCCESS)
            Logger.w(
                mLogSession,
                "Error: (0x"
                    + Integer.toHexString(status)
                    + "): "
                    + GattError.parseConnectionError(status));

          onDeviceDisconnected();
          mConnected = false;
          if (mUserDisconnected) {
            Logger.i(mLogSession, "Disconnected");
            mCallbacks.onDeviceDisconnected();
            close();
          } else {
            Logger.w(mLogSession, "Connection lost");
            mCallbacks.onLinklossOccur();
            // We are not closing the connection here as the device should try to reconnect
            // automatically.
            // This may be only called when the shouldAutoConnect() method returned true.
          }
          return;
        }

        // TODO Should the disconnect method be called or the connection is still valid? Does this
        // ever happen?
        Logger.e(
            mLogSession,
            "Error (0x"
                + Integer.toHexString(status)
                + "): "
                + GattError.parseConnectionError(status));
        mCallbacks.onError(ERROR_CONNECTION_STATE_CHANGE, status);
      }
    }