content: /**
   * Radius Networks, Inc.
   * http://www.radiusnetworks.com
   *
   * @author David G. Young
   *
   * Licensed to the Apache Software Foundation (ASF) under one
   * or more contributor license agreements.  See the NOTICE file
   * distributed with this work for additional information
   * regarding copyright ownership.  The ASF licenses this file
   * to you under the Apache License, Version 2.0 (the
   * "License"); you may not use this file except in compliance
   * with the License.  You may obtain a copy of the License at
   *
   *   http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing,
   * software distributed under the License is distributed on an
   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
   * KIND, either express or implied.  See the License for the
   * specific language governing permissions and limitations
   * under the License.
   */
  package org.altbeacon.beacon;
  
  import android.os.Parcel;
  import android.os.Parcelable;
  
  import org.altbeacon.beacon.client.BeaconDataFactory;
  import org.altbeacon.beacon.client.NullBeaconDataFactory;
  import org.altbeacon.beacon.distance.DistanceCalculator;
  import org.altbeacon.beacon.logging.LogManager;
  
  import java.io.Serializable;
  import java.util.ArrayList;
  import java.util.Collections;
  
  import java.util.List;
  
  /**
   * The <code>Beacon</code> class represents a single hardware Beacon detected by
   * an Android device.
   *
   * <pre>A Beacon is identified by a unique multi-part identifier, with the first of the ordered
   * identifiers being more significant for the purposes of grouping beacons.
   *
   * A Beacon sends a Bluetooth Low Energy (BLE) advertisement that contains these
   * three identifiers, along with the calibrated tx power (in RSSI) of the
   * Beacon's Bluetooth transmitter.
   *
   * This class may only be instantiated from a BLE packet, and an RSSI measurement for
   * the packet.  The class parses out the identifier, along with the calibrated
   * tx power.  It then uses the measured RSSI and calibrated tx power to do a rough
   * distance measurement (the mDistance field)
   *
   * @author  David G. Young
   * @see     Region#matchesBeacon(Beacon Beacon)
   */
  public class Beacon implements Parcelable, Serializable {
      private static final String TAG = "Beacon";
  
      private static final List<Long> UNMODIFIABLE_LIST_OF_LONG =
              Collections.unmodifiableList(new ArrayList<Long>());
      private static final List<Identifier> UNMODIFIABLE_LIST_OF_IDENTIFIER =
              Collections.unmodifiableList(new ArrayList<Identifier>());
  
      /**
       * Determines whether a the bluetoothAddress (mac address) must be the same for two Beacons
       * to be configured equal.
       */
      protected static boolean sHardwareEqualityEnforced = false;
  
      protected static DistanceCalculator sDistanceCalculator = null;
  
      /**
       * The a list of the multi-part identifiers of the beacon.  Together, these identifiers signify
       * a unique beacon.  The identifiers are ordered by significance for the purpose of grouping
       * beacons
       */
      protected List<Identifier> mIdentifiers;
  
      /**
       * A list of generic non-identifying data fields included in the beacon advertisement.  Data
       * fields are limited to the size of a Java long, or six bytes.
       */
      protected List<Long> mDataFields;
      /**
       * A list of generic non-identifying data fields included in a secondary beacon advertisement
       * and merged into this beacon.  Data fields are limited to the size of a Java long, or six
       * bytes.
       */
      protected List<Long> mExtraDataFields;
  
      /**
       * A double that is an estimate of how far the Beacon is away in meters.   Note that this number
       * fluctuates quite a bit with RSSI, so despite the name, it is not super accurate.
       */
      protected Double mDistance;
      /**
       * The measured signal strength of the Bluetooth packet that led do this Beacon detection.
       */
      protected int mRssi;
      /**
       * The calibrated measured Tx power of the Beacon in RSSI
       * This value is baked into an Beacon when it is manufactured, and
       * it is transmitted with each packet to aid in the mDistance estimate
       */
      protected int mTxPower;
  
      /**
       * The Bluetooth mac address
       */
      protected String mBluetoothAddress;
  
      /**
       * The number of rssi samples available, if known
       */
      private int mRssiMeasurementCount = 0;
  
      /**
       * The number of packets detected in the last cycle
       */
      private int mPacketCount = 0;
  
      /**
       * If multiple RSSI samples were available, this is the running average
       */
      private Double mRunningAverageRssi = null;
  
      /**
       * Used to attach data to individual Beacons, either locally or in the cloud
       */
      protected static BeaconDataFactory beaconDataFactory = new NullBeaconDataFactory();
  
      /**
       * The two byte value indicating the type of beacon that this is, which is used for figuring
       * out the byte layout of the beacon advertisement
       */
      protected int mBeaconTypeCode;
  
      /**
       * A two byte code indicating the beacon manufacturer.  A list of registered manufacturer codes
       * may be found here:
       * https://www.bluetooth.org/en-us/specification/assigned-numbers/company-identifiers
       *
       * If the beacon is a GATT-based beacon, this field will be set to -1
       */
      protected int mManufacturer;
  
      /**
       * A 32 bit service uuid for the beacon
       *
       * This is valid only for GATT-based beacons.   If the beacon is a manufacturer data-based
       * beacon, this field will be -1
       */
  
      protected int mServiceUuid = -1;
  
  
      /**
       * A 128 bit service uuid for the beacon
       *
       * This is valid only for GATT-based beacons.   If the beacon is a manufacturer data-based
       * beacon, this field will be -1
       */
  
      protected byte[] mServiceUuid128Bit = {};
  
      /**
       * The Bluetooth device name.  This is a field transmitted by the remote beacon device separate
       * from the advertisement data
       */
      protected String mBluetoothName;
  
      /**
       * The identifier of the beaconParser used to create this beacon.  Useful for figuring out
       * beacon types.
       */
      protected String mParserIdentifier;
  
      /**
       * An indicator marking this beacon as a potential multi frame beacon.
       *
       * This will be set to true if the beacon was parsed by a BeaconParser which has extra
       * data parsers defined.
       */
      protected boolean mMultiFrameBeacon = false;
  
      /**
       * The timestamp of the first packet detected in milliseconds.
       */
      protected long mFirstCycleDetectionTimestamp = 0L;
  
      /**
       * The timestamp of the last packet detected in milliseconds.
       */
      protected long mLastCycleDetectionTimestamp = 0L;
  
      protected byte[] mLastPacketRawBytes = {};
  
      /**
       * Required for making object Parcelable.  If you override this class, you must provide an
       * equivalent version of this method.
       */
      @Deprecated
      public static final Parcelable.Creator<Beacon> CREATOR
              = new Parcelable.Creator<Beacon>() {
          public Beacon createFromParcel(Parcel in) {
              return new Beacon(in);
          }
  
          public Beacon[] newArray(int size) {
              return new Beacon[size];
          }
      };
  
      /**
       * @deprecated Set the distanceCalculatorFactory method on the Settings class
       * You must define your own factory to co with your DistanceCalculator implementation
       * that returns a new instance of your DistanceCalculator implementation
       * Sets the DistanceCalculator to use with this beacon
       * @param dc
       */
      public static void setDistanceCalculator(DistanceCalculator dc) {
          sDistanceCalculator = dc;
      }
      static void setDistanceCalculatorInternal(DistanceCalculator dc) {
          sDistanceCalculator = dc;
      }
  
      /**
       * @deprecated get the distanceCalculatorFactory method on the Settings class
       * Gets the DistanceCalculator to use with this beacon
       */
      public static DistanceCalculator getDistanceCalculator() {
          return sDistanceCalculator;
      }
  
      /**
       * @deprecated Use the method on the Settings class and call `beaconManger.adjustSettings(settings)`
       * Configures whether a the bluetoothAddress (mac address) must be the same for two Beacons
       * to be configured equal.  This setting applies to all beacon instances in the same process.
       * Defaults to false for backward compatibility.
       *
       * @param e
       */
      @Deprecated
      public static void setHardwareEqualityEnforced(boolean e) {
          sHardwareEqualityEnforced = e;
      }
  
      @Deprecated
      public static boolean getHardwareEqualityEnforced() {
          return sHardwareEqualityEnforced;
      }
  
      /**
       * Required for making Beacon parcelable
       * @param in parcel
       */
      @Deprecated
      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();
          boolean hasServiceUuid128bit = in.readBoolean();
          if (hasServiceUuid128bit) {
              mServiceUuid128Bit = new byte[16];
              for (int i = 0; i < 16; i++) {
                  mServiceUuid128Bit[i]= in.readByte();
              }
          }
          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();
          this.mExtraDataFields = new ArrayList<Long>(extraDataSize);
          for (int i = 0; i < extraDataSize; i++) {
              mExtraDataFields.add(in.readLong());
          }
          mManufacturer = in.readInt();
          mBluetoothName = in.readString();
          mParserIdentifier = in.readString();
          mMultiFrameBeacon = in.readByte() != 0;
          mRunningAverageRssi = (Double) in.readValue(null);
          mRssiMeasurementCount = in.readInt();
          mPacketCount = in.readInt();
          mFirstCycleDetectionTimestamp = in.readLong();
          mLastCycleDetectionTimestamp = in.readLong();
          BeaconManager.setDebug(true);
  
          byte[] bytes = new byte[62];
          try {
              in.readByteArray(bytes); // This sometimes fails even though iterating as below works fine
          }
          catch (RuntimeException e) {
              try {
                  for (int i = 0; i < 62; i++) {
                      byte b = in.readByte();
                      bytes[b] = b;
                  }
              }
              catch (RuntimeException e2) {
                  // do nothing
              }
          }
          mLastPacketRawBytes = bytes;
      }
  
      /**
       * Copy constructor
       * @param otherBeacon
       */
      protected Beacon(Beacon otherBeacon) {
          super();
          mIdentifiers = new ArrayList<>(otherBeacon.mIdentifiers);
          mDataFields = new ArrayList<>(otherBeacon.mDataFields);
          mExtraDataFields = new ArrayList<>(otherBeacon.mExtraDataFields);
          this.mDistance = otherBeacon.mDistance;
          this.mRunningAverageRssi = otherBeacon.mRunningAverageRssi;
          this.mPacketCount = otherBeacon.mPacketCount;
          this.mRssiMeasurementCount = otherBeacon.mRssiMeasurementCount;
          this.mRssi = otherBeacon.mRssi;
          this.mTxPower = otherBeacon.mTxPower;
          this.mBluetoothAddress = otherBeacon.mBluetoothAddress;
          this.mBeaconTypeCode = otherBeacon.getBeaconTypeCode();
          this.mServiceUuid = otherBeacon.getServiceUuid();
          this.mServiceUuid128Bit = otherBeacon.getServiceUuid128Bit();
          this.mBluetoothName = otherBeacon.mBluetoothName;
          this.mParserIdentifier = otherBeacon.mParserIdentifier;
          this.mMultiFrameBeacon = otherBeacon.mMultiFrameBeacon;
          this.mManufacturer = otherBeacon.mManufacturer;
          this.mFirstCycleDetectionTimestamp = otherBeacon.mFirstCycleDetectionTimestamp;
          this.mLastCycleDetectionTimestamp = otherBeacon.mLastCycleDetectionTimestamp;
          this.mLastPacketRawBytes = otherBeacon.mLastPacketRawBytes;
      }
  
      /**
       * Basic constructor that simply allocates fields
       */
      protected Beacon() {
          mIdentifiers = new ArrayList<Identifier>(1);
          mDataFields = new ArrayList<Long>(1);
          mExtraDataFields = new ArrayList<Long>(1);
      }
  
  
      /**
       * Sets the measurement count that went into the rssi sample
       * @param rssiMeasurementCount
       */
      public void setRssiMeasurementCount(int rssiMeasurementCount) {
          mRssiMeasurementCount = rssiMeasurementCount;
      }
  
      /**
       * Returns the number of packet detections in the last ranging cycle
       */
      public int getPacketCount() {
          return mPacketCount;
      }
  
      /**
       * Sets the packet detections in the last ranging cycle
       * @param packetCount
       */
      public void setPacketCount(int packetCount) {
          mPacketCount = packetCount;
      }
  
      /**
       * Returns the timestamp of the first packet detected
       */
      public long getFirstCycleDetectionTimestamp() {
          return mFirstCycleDetectionTimestamp;
      }
  
      /**
       * Sets the timestamp of the first packet detected
       *
       * @param firstCycleDetectionTimestamp
       */
      public void setFirstCycleDetectionTimestamp(long firstCycleDetectionTimestamp) {
          mFirstCycleDetectionTimestamp = firstCycleDetectionTimestamp;
      }
  
      /**
       * Returns the timestamp of the last packet detected
       */
      public long getLastCycleDetectionTimestamp() {
          return mLastCycleDetectionTimestamp;
      }
  
      /**
       * Sets the timestamp of the last packet detected
       *
       * @param lastCycleDetectionTimestamp
       */
      public void setLastCycleDetectionTimestamp(long lastCycleDetectionTimestamp) {
          mLastCycleDetectionTimestamp = lastCycleDetectionTimestamp;
      }
  
      /**
       * Returns the raw bytes of the last detection
       */
      public byte[] getLastPacketRawBytes() {
          return mLastPacketRawBytes;
      }
  
      /**
       * Sets the raw bytes of the last detection
       *
       * @param bytes
       */
      public void setLastPacketRawBytes(byte[] bytes) {
          mLastPacketRawBytes = bytes;
      }
  
      /**
       * Returns the number of packet detections that went in to the runningAverageRssi, if known.
       * If not known or inapplicable for the rssi filter used, this is zero.
       */
      public int getMeasurementCount() {
          return mRssiMeasurementCount;
      }
  
      /**
       * Sets the running average rssi for use in distance calculations
       * @param rssi the running average rssi
       */
      public void setRunningAverageRssi(double rssi) {
          mRunningAverageRssi = rssi;
          mDistance = null; // force calculation of accuracy and proximity next time they are requested
      }
  
      /**
       * @deprecated To be removed in a future release. Use
       * {@link org.altbeacon.beacon.Beacon#getRunningAverageRssi()}
       * instead.
       */
      @Deprecated
      public double getRunningAverageRssi(double rssi) {
          return mRunningAverageRssi = rssi;
      }
  
      /**
       * Returns the running average rssi
       * @return double
       */
      public double getRunningAverageRssi() {
          if (mRunningAverageRssi != null){
              return mRunningAverageRssi;
          }
          return mRssi;
      }
  
      /**
       * Sets the most recently measured rssi for use in distance calculations if a running average is
       * not available
       * @param rssi
       */
      public void setRssi(int rssi) {
          mRssi = rssi;
      }
  
      /**
       * @see #mManufacturer
       */
      public int getManufacturer() {
          return mManufacturer;
      }
  
      /**
       * @see #mServiceUuid
       */
      public int getServiceUuid() {
          return mServiceUuid;
      }
  
      public byte[] getServiceUuid128Bit() {
          return mServiceUuid128Bit;
      }
  
      /**
       * Returns the specified identifier - 0 indexed
       * Note:  to read id1, call getIdentifier(0);
       * @param i - index identfier
       * @return identifier
       */
      public Identifier getIdentifier(int i) {
          return mIdentifiers.get(i);
      }
  
  
      /**
       * Convenience method to get the first identifier
       * @return
       */
      public Identifier getId1() {
          return mIdentifiers.get(0);
      }
  
      /**
       * Convenience method to get the second identifier
       * @return
       */
      public Identifier getId2() {
          return mIdentifiers.get(1);
      }
  
      /**
       * Convenience method to get the third identifier
       * @return
       */
      public Identifier getId3() {
          return mIdentifiers.get(2);
      }
  
      /**
       * Returns the list of data fields transmitted with the advertisement
       * @return dataFields
       */
      public List<Long> getDataFields() {
          if (mDataFields.getClass().isInstance(UNMODIFIABLE_LIST_OF_LONG)) {
              return mDataFields;
          }
          else {
              return Collections.unmodifiableList(mDataFields);
          }
      }
  
      /**
       * Returns the list of data fields transmitted with the advertisement
       * @return dataFields
       */
      public List<Long> getExtraDataFields() {
          if (mExtraDataFields.getClass().isInstance(UNMODIFIABLE_LIST_OF_LONG)) {
              return mExtraDataFields;
          }
          else {
              return Collections.unmodifiableList(mExtraDataFields);
          }
      }
  
      /**
       * Sets extra data fields
       * @param fields
       */
      public void setExtraDataFields(List<Long> fields) {
          mExtraDataFields = fields;
      }
  
      /**
       * Returns the list of identifiers transmitted with the advertisement
       * @return identifier
       */
      public List<Identifier> getIdentifiers() {
          if (mIdentifiers.getClass().isInstance(UNMODIFIABLE_LIST_OF_IDENTIFIER)) {
              return mIdentifiers;
          }
          else {
              return Collections.unmodifiableList(mIdentifiers);
          }
      }
  
  
      /**
       * Provides a calculated estimate of the distance to the beacon based on a running average of
       * the RSSI and the transmitted power calibration value included in the beacon advertisement.
       * This value is specific to the type of Android device receiving the transmission.
       *
       * @see #mDistance
       * @return distance
       */
      public double getDistance() {
          if (mDistance == null) {
              double bestRssiAvailable = mRssi;
              if (mRunningAverageRssi != null) {
                  bestRssiAvailable = mRunningAverageRssi;
              }
              else {
                  LogManager.d(TAG, "Not using running average RSSI because it is null");
              }
              mDistance = calculateDistance(mTxPower, bestRssiAvailable);
          }
          return mDistance;
      }
      /**
       * @see #mRssi
       * @return mRssi
       */
      public int getRssi() {
          return mRssi;
      }
      /**
       * @see #mTxPower
       * @return txPowwer
       */
      public int getTxPower() {
          return mTxPower;
      }
  
      /**
       * @see #mBeaconTypeCode
       * @return beaconTypeCode
       */
      public int getBeaconTypeCode() { return mBeaconTypeCode; }
  
      /**
       * @see #mBluetoothAddress
       * @return mBluetoothAddress
       */
      public String getBluetoothAddress() {
          return mBluetoothAddress;
      }
  
      /**
       * This returns the bluetooth name of the device if detected by the OS
       * from the advertisement data.  This field will never be populated for apps targeting Android
       * SDK 31+ unless the app has obtained BLUETOOTH_CONNECT permission, as that permission is a
       * new requirement from Android to read this field.
       * @see #mBluetoothName
       * @return mBluetoothName
       */
      public String getBluetoothName() {
          return mBluetoothName;
      }
  
      /**
       * @see #mParserIdentifier
       * @return mParserIdentifier
       */
      public String getParserIdentifier() { return mParserIdentifier; }
  
      /**
       * @see #mMultiFrameBeacon
       * @return mMultiFrameBeacon
       */
      public boolean isMultiFrameBeacon() { return mMultiFrameBeacon; }
  
      /**
       * Calculate a hashCode for this beacon
       * @return
       */
      @Override
      public int hashCode() {
          StringBuilder sb = toStringBuilder();
          if (sHardwareEqualityEnforced) {
              sb.append(mBluetoothAddress);
          }
          return sb.toString().hashCode();
      }
  
      /**
       * Two detected beacons are considered equal if they share the same three identifiers, regardless of their mDistance or RSSI.
       */
      @Override
      public boolean equals(Object that) {
          if (!(that instanceof Beacon)) {
              return false;
          }
          Beacon thatBeacon = (Beacon) that;
          if (!this.mIdentifiers.equals(thatBeacon.mIdentifiers)) {
              return false;
          }
          return sHardwareEqualityEnforced ?
                  this.getBluetoothAddress().equals(thatBeacon.getBluetoothAddress()) :
                  true;
      }
  
      /**
       * Requests server-side data for this beacon.  Requires that a BeaconDataFactory be set up with
       * a backend service.
       * @param notifier interface providing a callback when data are available
       */
      public void requestData(BeaconDataNotifier notifier) {
          beaconDataFactory.requestBeaconData(this, notifier);
      }
  
      /**
       * Formats a beacon as a string showing only its unique identifiers
       * @return
       */
      @Override
      public String toString() {
          return toStringBuilder().toString();
      }
  
      private StringBuilder toStringBuilder() {
          final StringBuilder sb = new StringBuilder();
          int i = 1;
          for (Identifier identifier: mIdentifiers) {
              if (i > 1) {
                  sb.append(" ");
              }
              sb.append("id");
              sb.append(i);
              sb.append(": ");
              sb.append(identifier == null ? "null" : identifier.toString());
              i++;
          }
          if (mParserIdentifier != null) {
              sb.append(" type "+mParserIdentifier);
          }
          return sb;
      }
  
      /**
       * Required for making object Parcelable
       */
      @Deprecated
      public int describeContents() {
          return 0;
      }
  
      /**
       * Required for making object Parcelable.  If you override this class, you must override this
       * method if you add any additional fields.
       */
      @Deprecated
      public void writeToParcel(Parcel out, int flags) {
          out.writeInt(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.writeBoolean(mServiceUuid128Bit.length != 0);
          if (mServiceUuid128Bit.length != 0) {
              for (int i = 0; i < 16; i++) {
                  out.writeByte(mServiceUuid128Bit[i]);
              }
          }
  
          out.writeInt(mDataFields.size());
          for (Long dataField: mDataFields) {
              out.writeLong(dataField);
          }
          out.writeInt(mExtraDataFields.size());
          for (Long dataField: mExtraDataFields) {
              out.writeLong(dataField);
          }
          out.writeInt(mManufacturer);
          out.writeString(mBluetoothName);
          out.writeString(mParserIdentifier);
          out.writeByte((byte) (mMultiFrameBeacon ? 1: 0));
          out.writeValue(mRunningAverageRssi);
          out.writeInt(mRssiMeasurementCount);
          out.writeInt(mPacketCount);
          out.writeLong(mFirstCycleDetectionTimestamp);
          out.writeLong(mLastCycleDetectionTimestamp);
          int rawByteCountToWrite = mLastPacketRawBytes.length;
          if (rawByteCountToWrite > 62) {
              rawByteCountToWrite = 62;
          }
          out.writeByteArray(mLastPacketRawBytes, 0, rawByteCountToWrite);
          while (rawByteCountToWrite < 62) {
              out.writeByte((byte) 0x00);
              rawByteCountToWrite += 1;
          }
      }
  
      /**
       * Indicates whether this beacon is an "Extra data beacon," meaning one that has no identifiers
       * but has data fields.
       * @return
       */
      public boolean isExtraBeaconData() {
          return mIdentifiers.size() == 0 && mDataFields.size() != 0;
      }
  
      /**
       * Estimate the distance to the beacon using the DistanceCalculator set on this class.  If no
       * DistanceCalculator has been set, return -1 as the distance.
       * @see org.altbeacon.beacon.distance.DistanceCalculator
       *
       * @param txPower
       * @param bestRssiAvailable
       * @return
       */
      protected static Double calculateDistance(int txPower, double bestRssiAvailable) {
          if (Beacon.getDistanceCalculator() != null) {
              return Beacon.getDistanceCalculator().calculateDistance(txPower, bestRssiAvailable);
          }
          else {
              LogManager.e(TAG, "Distance calculator not set.  Distance will bet set to -1");
              return -1.0;
          }
      }
  
      /**
       * Builder class for Beacon objects. Provides a convenient way to set the various fields of a
       * Beacon
       *
       * <p>Example:
       *
       * <pre>
       * Beacon beacon = new Beacon.Builder()
       *         .setId1(&quot;2F234454-CF6D-4A0F-ADF2-F4911BA9FFA6&quot;)
       *         .setId2("1")
       *         .setId3("2")
       *         .build();
       * </pre>
       */
      public static class Builder {
          protected final Beacon mBeacon;
          private Identifier mId1, mId2, mId3;
  
          /**
           * Creates a builder instance
           */
          public Builder() {
              mBeacon = new Beacon();
          }
  
          /**
           * Builds an instance of this beacon based on parameters set in the Builder
           * @return beacon
           */
          public Beacon build() {
              if (mId1!= null) {
                  mBeacon.mIdentifiers.add(mId1);
                  if (mId2!= null) {
                      mBeacon.mIdentifiers.add(mId2);
                      if (mId3!= null) {
                          mBeacon.mIdentifiers.add(mId3);
                      }
                  }
              }
              return mBeacon;
          }
  
          /**
           * @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());
              setMultiFrameBeacon(beacon.isMultiFrameBeacon());
              setLastPacketRawBytes(beacon.getLastPacketRawBytes());
              return this;
          }
  
          /**
           * @see Beacon#mIdentifiers
           * @param identifiers identifiers to set
           * @return builder
           */
          public Builder setIdentifiers(List<Identifier>identifiers) {
              mId1 = null;
              mId2 = null;
              mId3 = null;
              mBeacon.mIdentifiers = identifiers;
              return this;
          }
  
          /**
           * Convenience method allowing the first beacon identifier to be set as a String.  It will
           * be parsed into an Identifier object
           * @param id1String string to parse into an identifier
           * @return builder
           */
          public Builder setId1(String id1String) {
              mId1 = Identifier.parse(id1String);
              return this;
          }
  
          /**
           * Convenience method allowing the second beacon identifier to be set as a String.  It will
           * be parsed into an Identifier object
           * @param id2String string to parse into an identifier
           * @return builder
           */
          public Builder setId2(String id2String) {
              mId2 = Identifier.parse(id2String);
              return this;
          }
  
          /**
           * Convenience method allowing the third beacon identifier to be set as a String.  It will
           * be parsed into an Identifier object
           * @param id3String string to parse into an identifier
           * @return builder
           */
          public Builder setId3(String id3String) {
              mId3 = Identifier.parse(id3String);
              return this;
          }
  
          /**
           * @see Beacon#mRssi
           * @param rssi
           * @return builder
           */
          public Builder setRssi(int rssi) {
              mBeacon.mRssi = rssi;
              return this;
          }
  
          /**
           * @see Beacon#mRssi
           * @param rssi
           * @return builder
           */
          public Builder setRunningAverageRssi(double rssi) {
              mBeacon.mRunningAverageRssi = rssi;
              return this;
          }
  
          /**
           * @see Beacon#mTxPower
           * @param txPower
           * @return builder
           */
          public Builder setTxPower(int txPower) {
              mBeacon.mTxPower = txPower;
              return this;
          }
  
          /**
           * @see Beacon#mBeaconTypeCode
           * @param beaconTypeCode
           * @return builder
           */
          public Builder setBeaconTypeCode(int beaconTypeCode) {
              mBeacon.mBeaconTypeCode = beaconTypeCode;
              return this;
          }
  
          /**
           * @see Beacon#mServiceUuid
           * @param serviceUuid
           * @return builder
           */
          public Builder setServiceUuid(int serviceUuid) {
              mBeacon.mServiceUuid = serviceUuid;
              return this;
          }
  
          public Builder setServiceUuid128Bit(byte[] serviceUuid128Bit) {
              mBeacon.mServiceUuid128Bit = serviceUuid128Bit;
              return this;
          }
  
          /**
           * @see Beacon#mBluetoothAddress
           * @param bluetoothAddress
           * @return builder
           */
          public Builder setBluetoothAddress(String bluetoothAddress) {
              mBeacon.mBluetoothAddress = bluetoothAddress;
              return this;
          }
  
          /**
           * @see Beacon#mDataFields
           * @param dataFields
           * @return builder
           */
          public Builder setDataFields(List<Long> dataFields) {
              mBeacon.mDataFields = dataFields;
              return this;
          }
  
          /**
           * @see Beacon#mDataFields
           * @param extraDataFields
           * @return builder
           */
          public Builder setExtraDataFields(List<Long> extraDataFields) {
              mBeacon.mExtraDataFields = extraDataFields;
              return this;
          }
  
          /**
           * @see Beacon#mManufacturer
           * @param manufacturer
           * @return builder
           */
          public Builder setManufacturer(int manufacturer) {
              mBeacon.mManufacturer = manufacturer;
              return this;
          }
  
          /**
           * @see Beacon#mBluetoothName
           * @param name
           * @return builder
           */
          public Builder setBluetoothName(String name) {
              mBeacon.mBluetoothName = name;
              return this;
          }
  
          /**
           * @see Beacon#mParserIdentifier
           * @param id
           * @return builder
           */
          public Builder setParserIdentifier(String id) {
              mBeacon.mParserIdentifier = id;
              return this;
          }
  
  
          /**
           * @see Beacon#mMultiFrameBeacon
           * @param multiFrameBeacon
           * @return builder
           */
          public Builder setMultiFrameBeacon(boolean multiFrameBeacon) {
              mBeacon.mMultiFrameBeacon = multiFrameBeacon;
              return this;
          }
  
          /**
           * @see Beacon#mLastPacketRawBytes
           * @param lastPacketRawBytes
           * @return builder
           */
          public Builder setLastPacketRawBytes(byte[] lastPacketRawBytes) {
              mBeacon.mLastPacketRawBytes = lastPacketRawBytes;
              return this;
          }
      }
  
  }
  