@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);
      }
    }
    @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 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);
      }
    }
  /**
   * 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);
  }
        @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);
        }
 @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 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;
          }
        }
  /**
   * 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
        protected void onCharacteristicIndicated(
            final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) {
          if (mLogSession != null)
            Logger.a(mLogSession, RecordAccessControlPointParser.parse(characteristic));

          // Record Access Control Point characteristic
          int offset = 0;
          final int opCode =
              characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset);
          offset += 2; // skip the operator

          if (opCode == OP_CODE_NUMBER_OF_STORED_RECORDS_RESPONSE) {
            // We've obtained the number of all records
            final int number =
                characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, offset);

            mCallbacks.onNumberOfRecordsRequested(number);

            // Request the records
            if (number > 0) {
              final BluetoothGattCharacteristic racpCharacteristic =
                  mRecordAccessControlPointCharacteristic;
              setOpCode(racpCharacteristic, OP_CODE_REPORT_STORED_RECORDS, OPERATOR_ALL_RECORDS);
              writeCharacteristic(racpCharacteristic);
            } else {
              mCallbacks.onOperationCompleted();
            }
          } else if (opCode == OP_CODE_RESPONSE_CODE) {
            final int requestedOpCode =
                characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset);
            final int responseCode =
                characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset + 1);
            DebugLogger.d(TAG, "Response result for: " + requestedOpCode + " is: " + responseCode);

            switch (responseCode) {
              case RESPONSE_SUCCESS:
                if (!mAbort) mCallbacks.onOperationCompleted();
                else mCallbacks.onOperationAborted();
                break;
              case RESPONSE_NO_RECORDS_FOUND:
                mCallbacks.onOperationCompleted();
                break;
              case RESPONSE_OP_CODE_NOT_SUPPORTED:
                mCallbacks.onOperationNotSupported();
                break;
              case RESPONSE_PROCEDURE_NOT_COMPLETED:
              case RESPONSE_ABORT_UNSUCCESSFUL:
              default:
                mCallbacks.onOperationFailed();
                break;
            }
            mAbort = false;
          }
        }
    @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);
      }
    }
  /**
   * 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;
  }
  /**
   * When the device is bonded and has the Generic Attribute service and the Service Changed
   * characteristic this method enables indications on this characteristic. In case one of the
   * requirements is not fulfilled this method returns <code>false</code>.
   *
   * @param gatt the gatt device with services discovered
   * @return <code>true</code> when the request has been sent, <code>false</code> when the device is
   *     not bonded, does not have the Generic Attribute service, the GA service does not have the
   *     Service Changed characteristic or this characteristic does not have the CCCD.
   */
  private boolean ensureServiceChangedEnabled(final BluetoothGatt gatt) {
    if (gatt == null) return false;

    // The Service Changed indications have sense only on bonded devices
    final BluetoothDevice device = gatt.getDevice();
    if (device.getBondState() != BluetoothDevice.BOND_BONDED) return false;

    final BluetoothGattService gaService = gatt.getService(GENERIC_ATTRIBUTE_SERVICE);
    if (gaService == null) return false;

    final BluetoothGattCharacteristic scCharacteristic =
        gaService.getCharacteristic(SERVICE_CHANGED_CHARACTERISTIC);
    if (scCharacteristic == null) return false;

    Logger.i(mLogSession, "Service Changed characteristic found on a bonded device");
    return enableIndications(scCharacteristic);
  }
  /**
   * Reads the battery level from the device.
   *
   * @return true if request has been sent
   */
  public final boolean readBatteryLevel() {
    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_READ) == 0) {
      return setBatteryNotifications(true);
    }

    Logger.a(mLogSession, "Reading battery level...");
    return readCharacteristic(batteryLevelCharacteristic);
  }
        @Override
        public void onCharacteristicNotified(
            final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) {
          if (mLogSession != null)
            Logger.a(mLogSession, CSCMeasurementParser.parse(characteristic));

          // Decode the new data
          int offset = 0;
          final int flags = characteristic.getValue()[offset]; // 1 byte
          offset += 1;

          final boolean wheelRevPresent = (flags & WHEEL_REVOLUTIONS_DATA_PRESENT) > 0;
          final boolean crankRevPreset = (flags & CRANK_REVOLUTION_DATA_PRESENT) > 0;

          if (wheelRevPresent) {
            final int wheelRevolutions =
                characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT32, offset);
            offset += 4;

            final int lastWheelEventTime =
                characteristic.getIntValue(
                    BluetoothGattCharacteristic.FORMAT_UINT16, offset); // 1/1024 s
            offset += 2;

            // Notify listener about the new measurement
            mCallbacks.onWheelMeasurementReceived(wheelRevolutions, lastWheelEventTime);
          }

          if (crankRevPreset) {
            final int crankRevolutions =
                characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, offset);
            offset += 2;

            final int lastCrankEventTime =
                characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, offset);
            // offset += 2;

            // Notify listener about the new measurement
            mCallbacks.onCrankMeasurementReceived(crankRevolutions, lastCrankEventTime);
          }
        }
  /**
   * 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;
  }
 private void onError(final String message, final int errorCode) {
   Logger.e(
       mLogSession,
       "Error (0x" + Integer.toHexString(errorCode) + "): " + GattError.parse(errorCode));
   mCallbacks.onError(message, errorCode);
 }
        @Override
        public void onCharacteristicNotified(
            BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
          final UUID uuid = characteristic.getUuid();

          if (GM_CHARACTERISTIC.equals(uuid)) {
            if (mLogSession != null)
              Logger.a(mLogSession, GlucoseMeasurementParser.parse(characteristic));

            int offset = 0;
            final int flags =
                characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset);
            offset += 1;

            final boolean timeOffsetPresent = (flags & 0x01) > 0;
            final boolean typeAndLocationPresent = (flags & 0x02) > 0;
            final int concentrationUnit =
                (flags & 0x04) > 0 ? GlucoseRecord.UNIT_molpl : GlucoseRecord.UNIT_kgpl;
            final boolean sensorStatusAnnunciationPresent = (flags & 0x08) > 0;
            final boolean contextInfoFollows = (flags & 0x10) > 0;

            // create and fill the new record
            final GlucoseRecord record = new GlucoseRecord();
            record.sequenceNumber =
                characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, offset);
            offset += 2;

            final int year =
                characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, offset);
            final int month =
                characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset + 2)
                    - 1; // months are 1-based
            final int day =
                characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset + 3);
            final int hours =
                characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset + 4);
            final int minutes =
                characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset + 5);
            final int seconds =
                characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset + 6);
            offset += 7;

            final Calendar calendar = Calendar.getInstance();
            calendar.set(year, month, day, hours, minutes, seconds);
            record.time = calendar;

            if (timeOffsetPresent) {
              // time offset is ignored in the current release
              record.timeOffset =
                  characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_SINT16, offset);
              offset += 2;
            }

            if (typeAndLocationPresent) {
              record.glucoseConcentration =
                  characteristic.getFloatValue(BluetoothGattCharacteristic.FORMAT_SFLOAT, offset);
              record.unit = concentrationUnit;
              final int typeAndLocation =
                  characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset + 2);
              record.type = (typeAndLocation & 0xF0) >> 4; // TODO this way or around?
              record.sampleLocation = (typeAndLocation & 0x0F);
              offset += 3;
            }

            if (sensorStatusAnnunciationPresent) {
              record.status =
                  characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, offset);
            }
            // This allows you to check other values that are not provided by the Nordic
            // Semiconductor's Glucose Service in SDK 4.4.2.
            //				record.status = 0x1A;
            //				record.context = new GlucoseRecord.MeasurementContext();
            //				record.context.carbohydrateId = 1;
            //				record.context.carbohydrateUnits = 0.23f;
            //				record.context.meal = 2;
            //				record.context.tester = 2;
            //				record.context.health = 4;
            // the following values are not implemented yet (see
            // ExpandableRecordAdapter#getChildrenCount() and #getChild(...)
            //				record.context.exerciseDuration = 3600;
            //				record.context.exerciseIntensity = 45;
            //				record.context.medicationId = 3;
            //				record.context.medicationQuantity = 0.03f;
            //				record.context.medicationUnit = GlucoseRecord.MeasurementContext.UNIT_kg;
            //				record.context.HbA1c = 213.3f;

            // data set modifications must be done in UI thread
            mHandler.post(
                new Runnable() {
                  @Override
                  public void run() {
                    // insert the new record to storage
                    mRecords.put(record.sequenceNumber, record);

                    // if there is no context information following the measurement data, notify
                    // callback about the new record
                    if (!contextInfoFollows) mCallbacks.onDatasetChanged();
                  }
                });
          } else if (GM_CONTEXT_CHARACTERISTIC.equals(uuid)) {
            if (mLogSession != null)
              Logger.a(mLogSession, GlucoseMeasurementContextParser.parse(characteristic));

            int offset = 0;
            final int flags =
                characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset);
            offset += 1;

            final boolean carbohydratePresent = (flags & 0x01) > 0;
            final boolean mealPresent = (flags & 0x02) > 0;
            final boolean testerHealthPresent = (flags & 0x04) > 0;
            final boolean exercisePresent = (flags & 0x08) > 0;
            final boolean medicationPresent = (flags & 0x10) > 0;
            final int medicationUnit =
                (flags & 0x20) > 0
                    ? GlucoseRecord.MeasurementContext.UNIT_l
                    : GlucoseRecord.MeasurementContext.UNIT_kg;
            final boolean hbA1cPresent = (flags & 0x40) > 0;
            final boolean moreFlagsPresent = (flags & 0x80) > 0;

            final int sequenceNumber =
                characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, offset);
            offset += 2;

            final GlucoseRecord record = mRecords.get(sequenceNumber);
            if (record == null) {
              DebugLogger.w(
                  TAG, "Context information with unknown sequence number: " + sequenceNumber);
              return;
            }

            final GlucoseRecord.MeasurementContext context = new GlucoseRecord.MeasurementContext();
            record.context = context;

            if (moreFlagsPresent) offset += 1;

            if (carbohydratePresent) {
              context.carbohydrateId =
                  characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset);
              context.carbohydrateUnits =
                  characteristic.getFloatValue(
                      BluetoothGattCharacteristic.FORMAT_SFLOAT, offset + 1);
              offset += 3;
            }

            if (mealPresent) {
              context.meal =
                  characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset);
              offset += 1;
            }

            if (testerHealthPresent) {
              final int testerHealth =
                  characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset);
              context.tester = (testerHealth & 0xF0) >> 4;
              context.health = (testerHealth & 0x0F);
              offset += 1;
            }

            if (exercisePresent) {
              context.exerciseDuration =
                  characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, offset);
              context.exerciseIntensity =
                  characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset + 2);
              offset += 3;
            }

            if (medicationPresent) {
              context.medicationId =
                  characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, offset);
              context.medicationQuantity =
                  characteristic.getFloatValue(
                      BluetoothGattCharacteristic.FORMAT_SFLOAT, offset + 1);
              context.medicationUnit = medicationUnit;
              offset += 3;
            }

            if (hbA1cPresent) {
              context.HbA1c =
                  characteristic.getFloatValue(BluetoothGattCharacteristic.FORMAT_SFLOAT, offset);
            }

            // notify callback about the new record
            mCallbacks.onDatasetChanged();
          }
        }
    @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);
      }
    }