@Override
    public final void onCharacteristicChanged(
        final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) {
      final String data = ParserUtils.parse(characteristic);

      if (isBatteryLevelCharacteristic(characteristic)) {
        Logger.i(
            mLogSession,
            "Notification received from " + characteristic.getUuid() + ", value: " + data);
        final int batteryValue =
            characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 0);
        Logger.a(mLogSession, "Battery level received: " + batteryValue + "%");
        mCallbacks.onBatteryValueReceived(batteryValue);
      } else {
        final BluetoothGattDescriptor cccd =
            characteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR_UUID);
        final boolean notifications =
            cccd == null
                || cccd.getValue() == null
                || cccd.getValue().length != 2
                || cccd.getValue()[0] == 0x01;

        if (notifications) {
          Logger.i(
              mLogSession,
              "Notification received from " + characteristic.getUuid() + ", value: " + data);
          onCharacteristicNotified(gatt, characteristic);
        } else { // indications
          Logger.i(
              mLogSession,
              "Indication received from " + characteristic.getUuid() + ", value: " + data);
          onCharacteristicIndicated(gatt, characteristic);
        }
      }
    }
 @Override
 public void onCharacteristicWrite(
     final BluetoothGatt gatt,
     final BluetoothGattCharacteristic characteristic,
     final int status) {
   if (status == BluetoothGatt.GATT_SUCCESS) {
     Logger.i(
         mLogSession,
         "Data written to "
             + characteristic.getUuid()
             + ", value: "
             + ParserUtils.parse(characteristic.getValue()));
     // The value has been written. Notify the manager and proceed with the initialization queue.
     onCharacteristicWrite(gatt, characteristic);
     nextRequest();
   } else if (status == BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION) {
     if (gatt.getDevice().getBondState() != BluetoothDevice.BOND_NONE) {
       DebugLogger.w(TAG, ERROR_AUTH_ERROR_WHILE_BONDED);
       mCallbacks.onError(ERROR_AUTH_ERROR_WHILE_BONDED, status);
     }
   } else {
     DebugLogger.e(TAG, "onCharacteristicRead error " + status);
     onError(ERROR_READ_CHARACTERISTIC, status);
   }
 }
    @Override
    public final void onDescriptorWrite(
        final BluetoothGatt gatt, final BluetoothGattDescriptor descriptor, final int status) {
      if (status == BluetoothGatt.GATT_SUCCESS) {
        Logger.i(
            mLogSession,
            "Data written to descr. "
                + descriptor.getUuid()
                + ", value: "
                + ParserUtils.parse(descriptor));

        if (isServiceChangedCCCD(descriptor)) {
          Logger.a(mLogSession, "Service Changed notifications enabled");
          if (!readBatteryLevel()) nextRequest();
        } else if (isBatteryLevelCCCD(descriptor)) {
          final byte[] value = descriptor.getValue();
          if (value != null && value.length > 0 && value[0] == 0x01) {
            Logger.a(mLogSession, "Battery Level notifications enabled");
            nextRequest();
          } else Logger.a(mLogSession, "Battery Level notifications disabled");
        } else {
          nextRequest();
        }
      } else if (status == BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION) {
        if (gatt.getDevice().getBondState() != BluetoothDevice.BOND_NONE) {
          DebugLogger.w(TAG, ERROR_AUTH_ERROR_WHILE_BONDED);
          mCallbacks.onError(ERROR_AUTH_ERROR_WHILE_BONDED, status);
        }
      } else {
        DebugLogger.e(TAG, "onDescriptorWrite error " + status);
        onError(ERROR_WRITE_DESCRIPTOR, status);
      }
    }
    @Override
    public final void onCharacteristicRead(
        final BluetoothGatt gatt,
        final BluetoothGattCharacteristic characteristic,
        final int status) {
      if (status == BluetoothGatt.GATT_SUCCESS) {
        Logger.i(
            mLogSession,
            "Read Response received from "
                + characteristic.getUuid()
                + ", value: "
                + ParserUtils.parse(characteristic));

        if (isBatteryLevelCharacteristic(characteristic)) {
          final int batteryValue =
              characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 0);
          Logger.a(mLogSession, "Battery level received: " + batteryValue + "%");
          mCallbacks.onBatteryValueReceived(batteryValue);

          // The Battery Level value has been read. Let's try to enable Battery Level notifications.
          // If the Battery Level characteristic does not have the NOTIFY property, proceed with the
          // initialization queue.
          if (!setBatteryNotifications(true)) nextRequest();
        } else {
          // The value has been read. Notify the manager and proceed with the initialization queue.
          onCharacteristicRead(gatt, characteristic);
          nextRequest();
        }
      } else if (status == BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION) {
        if (gatt.getDevice().getBondState() != BluetoothDevice.BOND_NONE) {
          DebugLogger.w(TAG, ERROR_AUTH_ERROR_WHILE_BONDED);
          mCallbacks.onError(ERROR_AUTH_ERROR_WHILE_BONDED, status);
        }
      } else {
        DebugLogger.e(TAG, "onCharacteristicRead error " + status);
        onError(ERROR_READ_CHARACTERISTIC, status);
      }
    }