@Test
 public void gattBeaconFieldsGetUpdated() {
   Beacon beacon = getGattBeacon();
   Beacon beaconUpdate = getGattBeaconUpdate();
   Beacon extraDataBeacon = getGattBeaconExtraData();
   GattBeaconTracker tracker = new GattBeaconTracker();
   tracker.track(beacon);
   Beacon trackedBeacon = tracker.track(beaconUpdate);
   assertEquals("rssi should be updated", beaconUpdate.getRssi(), trackedBeacon.getRssi());
   assertEquals(
       "data fields should be updated",
       beaconUpdate.getDataFields(),
       trackedBeacon.getDataFields());
 }
 /**
  * @param beacon the beacon whose fields we should copy to this beacon builder
  * @return
  */
 public Builder copyBeaconFields(Beacon beacon) {
   setIdentifiers(beacon.getIdentifiers());
   setBeaconTypeCode(beacon.getBeaconTypeCode());
   setDataFields(beacon.getDataFields());
   setBluetoothAddress(beacon.getBluetoothAddress());
   setBluetoothName(beacon.getBluetoothName());
   setExtraDataFields(beacon.getExtraDataFields());
   setManufacturer(beacon.getManufacturer());
   setTxPower(beacon.getTxPower());
   setRssi(beacon.getRssi());
   setServiceUuid(beacon.getServiceUuid());
   return this;
 }
 @Test
 public void gattBeaconExtraDataAreNotOverwritten() {
   Beacon beacon = getGattBeacon();
   Beacon extraDataBeacon = getGattBeaconExtraData();
   GattBeaconTracker tracker = new GattBeaconTracker();
   tracker.track(beacon);
   tracker.track(extraDataBeacon);
   Beacon trackedBeacon = tracker.track(beacon);
   assertEquals(
       "extra data should not be overwritten",
       extraDataBeacon.getDataFields(),
       trackedBeacon.getExtraDataFields());
 }
 @Test
 public void gattBeaconExtraDataGetUpdated() {
   Beacon beacon = getGattBeacon();
   Beacon extraDataBeacon = getGattBeaconExtraData();
   Beacon extraDataBeacon2 = getGattBeaconExtraData2();
   GattBeaconTracker tracker = new GattBeaconTracker();
   tracker.track(beacon);
   tracker.track(extraDataBeacon);
   tracker.track(extraDataBeacon2);
   Beacon trackedBeacon = tracker.track(beacon);
   assertEquals(
       "extra data is updated",
       extraDataBeacon2.getDataFields(),
       trackedBeacon.getExtraDataFields());
 }
  /**
   * Get BLE advertisement bytes for a Beacon
   *
   * @param beacon the beacon containing the data to be transmitted
   * @return the byte array of the advertisement
   */
  @TargetApi(Build.VERSION_CODES.GINGERBREAD)
  public byte[] getBeaconAdvertisementData(Beacon beacon) {
    byte[] advertisingBytes;

    if (beacon.getIdentifiers().size() != getIdentifierCount()) {
      throw new IllegalArgumentException(
          "Beacon has "
              + beacon.getIdentifiers().size()
              + " identifiers but format requires "
              + getIdentifierCount());
    }

    int lastIndex = -1;
    if (mMatchingBeaconTypeCodeEndOffset != null && mMatchingBeaconTypeCodeEndOffset > lastIndex) {
      lastIndex = mMatchingBeaconTypeCodeEndOffset;
    }
    if (mPowerEndOffset != null && mPowerEndOffset > lastIndex) {
      lastIndex = mPowerEndOffset;
    }
    for (int identifierNum = 0;
        identifierNum < this.mIdentifierEndOffsets.size();
        identifierNum++) {
      if (this.mIdentifierEndOffsets.get(identifierNum) != null
          && this.mIdentifierEndOffsets.get(identifierNum) > lastIndex) {
        lastIndex = this.mIdentifierEndOffsets.get(identifierNum);
      }
    }
    for (int identifierNum = 0; identifierNum < this.mDataEndOffsets.size(); identifierNum++) {
      if (this.mDataEndOffsets.get(identifierNum) != null
          && this.mDataEndOffsets.get(identifierNum) > lastIndex) {
        lastIndex = this.mDataEndOffsets.get(identifierNum);
      }
    }

    // we must adjust the lastIndex to account for variable length identifiers, if there are any.
    int adjustedIdentifiersLength = 0;
    for (int identifierNum = 0;
        identifierNum < this.mIdentifierStartOffsets.size();
        identifierNum++) {
      if (mIdentifierVariableLengthFlags.get(identifierNum)) {
        int declaredIdentifierLength =
            (this.mIdentifierEndOffsets.get(identifierNum)
                - this.mIdentifierStartOffsets.get(identifierNum)
                + 1);
        int actualIdentifierLength = beacon.getIdentifier(identifierNum).getByteCount();
        adjustedIdentifiersLength += actualIdentifierLength;
        adjustedIdentifiersLength -= declaredIdentifierLength;
      }
    }
    lastIndex += adjustedIdentifiersLength;

    advertisingBytes = new byte[lastIndex + 1 - 2];
    long beaconTypeCode = this.getMatchingBeaconTypeCode();

    // set type code
    for (int index = this.mMatchingBeaconTypeCodeStartOffset;
        index <= this.mMatchingBeaconTypeCodeEndOffset;
        index++) {
      byte value =
          (byte)
              (this.getMatchingBeaconTypeCode()
                      >> (8 * (this.mMatchingBeaconTypeCodeEndOffset - index))
                  & 0xff);
      advertisingBytes[index - 2] = value;
    }

    // set identifiers
    for (int identifierNum = 0;
        identifierNum < this.mIdentifierStartOffsets.size();
        identifierNum++) {
      byte[] identifierBytes =
          beacon
              .getIdentifier(identifierNum)
              .toByteArrayOfSpecifiedEndianness(
                  !this.mIdentifierLittleEndianFlags.get(identifierNum));

      // If the identifier we are trying to stuff into the space is different than the space
      // available
      // adjust it
      if (identifierBytes.length < getIdentifierByteCount(identifierNum)) {
        if (!mIdentifierVariableLengthFlags.get(identifierNum)) {
          // Pad it, but only if this is not a variable length identifier
          if (mIdentifierLittleEndianFlags.get(identifierNum)) {
            // this is little endian.  Pad at the end of the array
            identifierBytes = Arrays.copyOf(identifierBytes, getIdentifierByteCount(identifierNum));
          } else {
            // this is big endian.  Pad at the beginning of the array
            byte[] newIdentifierBytes = new byte[getIdentifierByteCount(identifierNum)];
            System.arraycopy(
                identifierBytes,
                0,
                newIdentifierBytes,
                getIdentifierByteCount(identifierNum) - identifierBytes.length,
                identifierBytes.length);
            identifierBytes = newIdentifierBytes;
          }
        }
        LogManager.d(
            TAG,
            "Expanded identifier because it is too short.  It is now: "
                + byteArrayToString(identifierBytes));
      } else if (identifierBytes.length > getIdentifierByteCount(identifierNum)) {
        if (mIdentifierLittleEndianFlags.get(identifierNum)) {
          // Truncate it at the beginning for big endian
          identifierBytes =
              Arrays.copyOfRange(
                  identifierBytes,
                  getIdentifierByteCount(identifierNum) - identifierBytes.length,
                  getIdentifierByteCount(identifierNum));
        } else {
          // Truncate it at the end for little endian
          identifierBytes = Arrays.copyOf(identifierBytes, getIdentifierByteCount(identifierNum));
        }
        LogManager.d(
            TAG,
            "Truncated identifier because it is too long.  It is now: "
                + byteArrayToString(identifierBytes));
      } else {
        LogManager.d(TAG, "Identifier size is just right: " + byteArrayToString(identifierBytes));
      }
      for (int index = this.mIdentifierStartOffsets.get(identifierNum);
          index <= this.mIdentifierStartOffsets.get(identifierNum) + identifierBytes.length - 1;
          index++) {
        advertisingBytes[index - 2] =
            (byte) identifierBytes[index - this.mIdentifierStartOffsets.get(identifierNum)];
      }
    }

    // set power
    for (int index = this.mPowerStartOffset; index <= this.mPowerEndOffset; index++) {
      advertisingBytes[index - 2] =
          (byte) (beacon.getTxPower() >> (8 * (index - this.mPowerStartOffset)) & 0xff);
    }

    // set data fields
    for (int dataFieldNum = 0; dataFieldNum < this.mDataStartOffsets.size(); dataFieldNum++) {
      long dataField = beacon.getDataFields().get(dataFieldNum);

      for (int index = this.mDataStartOffsets.get(dataFieldNum);
          index <= this.mDataEndOffsets.get(dataFieldNum);
          index++) {
        int endianCorrectedIndex = index;
        if (this.mDataLittleEndianFlags.get(dataFieldNum)) {
          endianCorrectedIndex = this.mDataEndOffsets.get(dataFieldNum) - index;
        }
        advertisingBytes[endianCorrectedIndex - 2] =
            (byte) (dataField >> (8 * (index - this.mDataStartOffsets.get(dataFieldNum))) & 0xff);
      }
    }
    return advertisingBytes;
  }