/**
   * Required for making Beacon parcelable
   *
   * @param in parcel
   */
  protected Beacon(Parcel in) {
    int size = in.readInt();

    this.mIdentifiers = new ArrayList<Identifier>(size);
    for (int i = 0; i < size; i++) {
      mIdentifiers.add(Identifier.parse(in.readString()));
    }
    mDistance = in.readDouble();
    mRssi = in.readInt();
    mTxPower = in.readInt();
    mBluetoothAddress = in.readString();
    mBeaconTypeCode = in.readInt();
    mServiceUuid = in.readInt();
    int dataSize = in.readInt();
    this.mDataFields = new ArrayList<Long>(dataSize);
    for (int i = 0; i < dataSize; i++) {
      mDataFields.add(in.readLong());
    }
    int extraDataSize = in.readInt();
    if (LogManager.isVerboseLoggingEnabled()) {
      LogManager.d(TAG, "reading " + extraDataSize + " extra data fields from parcel");
    }
    this.mExtraDataFields = new ArrayList<Long>(extraDataSize);
    for (int i = 0; i < extraDataSize; i++) {
      mExtraDataFields.add(in.readLong());
    }
    mManufacturer = in.readInt();
    mBluetoothName = in.readString();
  }
 /**
  * Required for making object Parcelable. If you override this class, you must override this
  * method if you add any additional fields.
  */
 public void writeToParcel(Parcel out, int flags) {
   out.writeInt(mIdentifiers.size());
   LogManager.d(TAG, "serializing identifiers of size %s", mIdentifiers.size());
   for (Identifier identifier : mIdentifiers) {
     out.writeString(identifier == null ? null : identifier.toString());
   }
   out.writeDouble(getDistance());
   out.writeInt(mRssi);
   out.writeInt(mTxPower);
   out.writeString(mBluetoothAddress);
   out.writeInt(mBeaconTypeCode);
   out.writeInt(mServiceUuid);
   out.writeInt(mDataFields.size());
   for (Long dataField : mDataFields) {
     out.writeLong(dataField);
   }
   if (LogManager.isVerboseLoggingEnabled()) {
     LogManager.d(TAG, "writing " + mExtraDataFields.size() + " extra data fields to parcel");
   }
   out.writeInt(mExtraDataFields.size());
   for (Long dataField : mExtraDataFields) {
     out.writeLong(dataField);
   }
   out.writeInt(mManufacturer);
   out.writeString(mBluetoothName);
 }
  @TargetApi(5)
  protected Beacon fromScanData(
      byte[] bytesToProcess, int rssi, BluetoothDevice device, Beacon beacon) {
    BleAdvertisement advert = new BleAdvertisement(bytesToProcess);
    boolean parseFailed = false;
    Pdu pduToParse = null;
    int startByte = 0;
    ArrayList<Identifier> identifiers = new ArrayList<Identifier>();
    ArrayList<Long> dataFields = new ArrayList<Long>();

    for (Pdu pdu : advert.getPdus()) {
      if (pdu.getType() == Pdu.GATT_SERVICE_UUID_PDU_TYPE
          || pdu.getType() == Pdu.MANUFACTURER_DATA_PDU_TYPE) {
        pduToParse = pdu;
        if (LogManager.isVerboseLoggingEnabled()) {
          LogManager.d(
              TAG,
              "Processing pdu type %02X: %s with startIndex: %d, endIndex: %d",
              pdu.getType(),
              bytesToHex(bytesToProcess),
              pdu.getStartIndex(),
              pdu.getEndIndex());
        }
        break;
      } else {
        if (LogManager.isVerboseLoggingEnabled()) {
          LogManager.d(TAG, "Ignoring pdu type %02X", pdu.getType());
        }
      }
    }
    if (pduToParse == null) {
      if (LogManager.isVerboseLoggingEnabled()) {
        LogManager.d(TAG, "No PDUs to process in this packet.");
      }
      parseFailed = true;
    } else {
      byte[] serviceUuidBytes = null;
      byte[] typeCodeBytes =
          longToByteArray(
              getMatchingBeaconTypeCode(),
              mMatchingBeaconTypeCodeEndOffset - mMatchingBeaconTypeCodeStartOffset + 1);
      if (getServiceUuid() != null) {
        serviceUuidBytes =
            longToByteArray(
                getServiceUuid(), mServiceUuidEndOffset - mServiceUuidStartOffset + 1, false);
      }
      startByte = pduToParse.getStartIndex();
      boolean patternFound = false;

      if (getServiceUuid() == null) {
        if (byteArraysMatch(
            bytesToProcess, startByte + mMatchingBeaconTypeCodeStartOffset, typeCodeBytes, 0)) {
          patternFound = true;
        }
      } else {
        if (byteArraysMatch(
                bytesToProcess, startByte + mServiceUuidStartOffset, serviceUuidBytes, 0)
            && byteArraysMatch(
                bytesToProcess, startByte + mMatchingBeaconTypeCodeStartOffset, typeCodeBytes, 0)) {
          patternFound = true;
        }
      }

      if (patternFound == false) {
        // This is not a beacon
        if (getServiceUuid() == null) {
          if (LogManager.isVerboseLoggingEnabled()) {
            LogManager.d(
                TAG,
                "This is not a matching Beacon advertisement. (Was expecting %s.  "
                    + "The bytes I see are: %s",
                byteArrayToString(typeCodeBytes),
                bytesToHex(bytesToProcess));
          }
        } else {
          if (LogManager.isVerboseLoggingEnabled()) {
            LogManager.d(
                TAG,
                "This is not a matching Beacon advertisement. Was expecting %s at offset %d and %s at offset %d.  "
                    + "The bytes I see are: %s",
                byteArrayToString(serviceUuidBytes),
                startByte + mServiceUuidStartOffset,
                byteArrayToString(typeCodeBytes),
                startByte + mMatchingBeaconTypeCodeStartOffset,
                bytesToHex(bytesToProcess));
          }
        }
        parseFailed = true;
        beacon = null;
      } else {
        if (LogManager.isVerboseLoggingEnabled()) {
          LogManager.d(
              TAG,
              "This is a recognized beacon advertisement -- %s seen",
              byteArrayToString(typeCodeBytes));
        }
      }

      if (patternFound) {
        if (bytesToProcess.length <= startByte + mLayoutSize && mAllowPduOverflow) {
          // If the layout size is bigger than this PDU, and we allow overflow.  Make sure
          // the byte buffer is big enough by zero padding the end so we don't try to read
          // outside the byte array of the advertisement
          if (LogManager.isVerboseLoggingEnabled()) {
            LogManager.d(
                TAG,
                "Expanding buffer because it is too short to parse: "
                    + bytesToProcess.length
                    + ", needed: "
                    + (startByte + mLayoutSize));
          }
          bytesToProcess = ensureMaxSize(bytesToProcess, startByte + mLayoutSize);
        }
        for (int i = 0; i < mIdentifierEndOffsets.size(); i++) {
          int endIndex = mIdentifierEndOffsets.get(i) + startByte;

          if (endIndex > pduToParse.getEndIndex() && mIdentifierVariableLengthFlags.get(i)) {
            if (LogManager.isVerboseLoggingEnabled()) {
              LogManager.d(
                  TAG, "Need to truncate identifier by " + (endIndex - pduToParse.getEndIndex()));
            }
            // If this is a variable length identifier, we truncate it to the size that
            // is available in the packet
            Identifier identifier =
                Identifier.fromBytes(
                    bytesToProcess,
                    mIdentifierStartOffsets.get(i) + startByte,
                    pduToParse.getEndIndex() + 1,
                    mIdentifierLittleEndianFlags.get(i));
            identifiers.add(identifier);
          } else if (endIndex > pduToParse.getEndIndex() && !mAllowPduOverflow) {
            parseFailed = true;
            if (LogManager.isVerboseLoggingEnabled()) {
              LogManager.d(
                  TAG,
                  "Cannot parse identifier "
                      + i
                      + " because PDU is too short.  endIndex: "
                      + endIndex
                      + " PDU endIndex: "
                      + pduToParse.getEndIndex());
            }
          } else {
            Identifier identifier =
                Identifier.fromBytes(
                    bytesToProcess,
                    mIdentifierStartOffsets.get(i) + startByte,
                    endIndex + 1,
                    mIdentifierLittleEndianFlags.get(i));
            identifiers.add(identifier);
          }
        }
        for (int i = 0; i < mDataEndOffsets.size(); i++) {
          int endIndex = mDataEndOffsets.get(i) + startByte;
          if (endIndex > pduToParse.getEndIndex() && !mAllowPduOverflow) {
            if (LogManager.isVerboseLoggingEnabled()) {
              LogManager.d(
                  TAG,
                  "Cannot parse data field "
                      + i
                      + " because PDU is too short.  endIndex: "
                      + endIndex
                      + " PDU endIndex: "
                      + pduToParse.getEndIndex()
                      + ".  Setting value to 0");
            }
            dataFields.add(new Long(0l));
          } else {
            String dataString =
                byteArrayToFormattedString(
                    bytesToProcess,
                    mDataStartOffsets.get(i) + startByte,
                    endIndex,
                    mDataLittleEndianFlags.get(i));
            dataFields.add(Long.parseLong(dataString));
          }
        }

        if (mPowerStartOffset != null) {
          int endIndex = mPowerEndOffset + startByte;
          int txPower = 0;
          try {
            if (endIndex > pduToParse.getEndIndex() && !mAllowPduOverflow) {
              parseFailed = true;
              if (LogManager.isVerboseLoggingEnabled()) {
                LogManager.d(
                    TAG,
                    "Cannot parse power field because PDU is too short.  endIndex: "
                        + endIndex
                        + " PDU endIndex: "
                        + pduToParse.getEndIndex());
              }
            } else {
              String powerString =
                  byteArrayToFormattedString(
                      bytesToProcess,
                      mPowerStartOffset + startByte,
                      mPowerEndOffset + startByte,
                      false);
              txPower = Integer.parseInt(powerString) + mDBmCorrection;
              // make sure it is a signed integer
              if (txPower > 127) {
                txPower -= 256;
              }
              beacon.mTxPower = txPower;
            }
          } catch (NumberFormatException e1) {
            // keep default value
          } catch (NullPointerException e2) {
            // keep default value
          }
        }
      }
    }

    if (parseFailed) {
      beacon = null;
    } else {
      int beaconTypeCode = 0;
      String beaconTypeString =
          byteArrayToFormattedString(
              bytesToProcess,
              mMatchingBeaconTypeCodeStartOffset + startByte,
              mMatchingBeaconTypeCodeEndOffset + startByte,
              false);
      beaconTypeCode = Integer.parseInt(beaconTypeString);
      // TODO: error handling needed on the parse

      int manufacturer = 0;
      String manufacturerString =
          byteArrayToFormattedString(bytesToProcess, startByte, startByte + 1, true);
      manufacturer = Integer.parseInt(manufacturerString);

      String macAddress = null;
      String name = null;
      if (device != null) {
        macAddress = device.getAddress();
        name = device.getName();
      }

      beacon.mIdentifiers = identifiers;
      beacon.mDataFields = dataFields;
      beacon.mRssi = rssi;
      beacon.mBeaconTypeCode = beaconTypeCode;
      if (mServiceUuid != null) {
        beacon.mServiceUuid = (int) mServiceUuid.longValue();
      } else {
        beacon.mServiceUuid = -1;
      }

      beacon.mBluetoothAddress = macAddress;
      beacon.mBluetoothName = name;
      beacon.mManufacturer = manufacturer;
      beacon.mParserIdentifier = mIdentifier;
    }
    return beacon;
  }