/**
   * 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();
  }
 /**
  * 设置距离计算模型
  *
  * @param distanceModelUpdateUrl null 使用asserts/model-distance-calculations.json;
  *     否则使用url指定的网上模型文件,如"http://data.altbeacon.org/android-distance.json"
  */
 public void setDefaultDistanceCalcuator(String distanceModelUpdateUrl) {
   /**
    * 读取应用程序asserts/model-distance-calculations.json,在应用程序私有数据空间写入同名文件。
    * java/org/altbeacon/beacon/distance/ModelSpecificDistanceCalculator.java的loadModelMapFromFile()读取此文件,以构造缺省模型。
    */
   if (distanceModelUpdateUrl == null) {
     try {
       String jsonString = stringFromAssertFile(); // 读取asserts/model-distance-calculations.json
       boolean ok = saveJson(jsonString); // 将距离模型json数据写入应用程序私有空间中的文件:CONFIG_FILE。
       if (ok) {
         LogManager.d(TAG, "setDefaultDistanceCalcuator ok,from asserts/" + CONFIG_FILE);
       } else {
         LogManager.d(TAG, "setDefaultDistanceCalcuator error,from asserts/" + CONFIG_FILE);
       }
     } catch (IOException e) {
       LogManager.d(TAG, "Exception:" + e.toString());
     }
     // 设置一个虚假的url,目的是引起BeaconService中调用ModelSpecificDistanceCalculator()-->loadModelMap()-->在应程序私有空间找CONFIG_FILE
     // 由于以上已经存储了这个文件,因此,绑定BeaconService后,执行上述序列,loadModelMap()能够成功找到该文件。
     // 鉴于此,必须在绑定BeaconService前执行此函数。loadModelMap()仅在第一次调用时才检查此文件,一旦文件已经写入,下一次就不检查了。因此测试时,每次要完全卸载程序,才能验证此程序的逻辑。
     BeaconManager.setDistanceModelUpdateUrl("nodistanceModelUpdateUrl");
   } else { // BeaconService中调用ModelSpecificDistanceCalculator()设置距离计算模型
     BeaconManager.setDistanceModelUpdateUrl(distanceModelUpdateUrl);
     LogManager.d(TAG, "setDefaultDistanceCalcuator, from " + distanceModelUpdateUrl);
   }
 }
 /**
  * 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);
 }
        @Override
        public void getBeacons(Collection<Beacon> beacons) {
          // 防止停止收集beacons前,再次收到此回调,导致重复记录,在开始和结束监控(查找)beacons时设置。
          if (isRecorded) return; // 已经记录了

          // 日志记录和屏幕显示Beacon信息
          // 有可能到达采样周期时,没有找到所有beacons,甚至是0个beacons,因此,应重复记录,一直到采样周期结束。当然是最后更新的有效。
          String str = "beacons=" + beacons.size();
          LogManager.d(TAG, str);
          logToDisplay(str);
          String rssi;
          for (Beacon beacon : beacons) {
            // becaon的两个id(major,minor),rssi及其平均值
            str =
                beacon.getId2()
                    + ":"
                    + beacon.getId3()
                    + "="
                    + beacon.getRssi()
                    + ","
                    + String.format("%.2f", beacon.getRunningAverageRssi());
            LogManager.d(TAG, str);
            logToDisplay(str);

            // 记录至mBeaconsRssi
            rssi =
                beacon.getId2()
                    + "_"
                    + beacon.getId3()
                    + ":"
                    + String.format("%.2f", beacon.getRunningAverageRssi());
            mBeaconsRssi.put(beacon, rssi);
          }

          // 记录参考点的各个beacon的id和rssi平均值
          if ((System.currentTimeMillis() - startSample) >= SamplePeroid) {
            // 将目前定位参考点测量的各个beacons的rssi平均值计入数据库。
            SaveRssiToDb();

            str =
                "记录完毕,定位参考点[" + reference_pointPerf + reference_pointNum + "]" + "各个beacon的rssi平均值";
            LogManager.d(TAG, str);
            logToDisplay(str);
            // 以下必须在UI现成中执行,否则,程序将异常终止。
            runOnUiThread(
                new Runnable() {
                  public void run() {
                    String str = "记录完毕,定位参考点[" + reference_pointPerf + reference_pointNum + "]";
                    Toast.makeText(trainingActivity.this, str, Toast.LENGTH_LONG).show();
                    // 下一个参考点默认名称
                    reference_pointNum++;
                    reference_point_edit.setText(reference_pointPerf + reference_pointNum);

                    // 停止查找beacons
                    onMonitoringStop(null);
                  }
                });
          }
        }
 /**
  * 每个扫描周期结束,根据20秒内各beacon的RSSI平均值计算它的距离,该回调获取这些beacon的距离值 Called once per second (实际上是每扫描周期)
  * to give an estimate of the mDistance to visible beacons
  */
 @Override
 public void didRangeBeaconsInRegion(Collection<Beacon> beacons, Region region) {
   LogManager.d(TAG, "didRangeBeaconsInRegion(),beacons=" + beacons.size());
   for (Beacon beacon : beacons) {
     LogManager.d(TAG, beacon.getId2() + ":" + beacon.getId3() + "," + beacon.getDistance());
   }
   Beacon beacon = mNearestBeacon.getNearestBeacon(mGetBeaconType, beacons);
   mOnNearestBeaconListener.getNearestBeacon(mGetBeaconType, beacon);
 }
 /** Called when no beacons in a Region are visible. */
 @Override
 public void didExitRegion(Region region) {
   LogManager.d(TAG, "didExitRegion(),region uniqueId= " + region.getUniqueId());
   /**
    * Tells the BeaconService to stop looking for beacons that match the passed Region object
    * and providing mDistance information for them.
    */
   try {
     mBeaconManager.stopRangingBeaconsInRegion(ALL_VOLIAM_BEACONS_REGION);
   } catch (RemoteException e) {
     LogManager.d(TAG, "RemoteException:" + e.toString());
   }
 }
 /**
  * Sets the duration in milliseconds spent not scanning between each Bluetooth LE scan cycle when
  * no ranging/monitoring clients are in the background. default 5 minutes
  *
  * @param p (ms)
  */
 public void setBackgroundBetweenScanPeriod(long p) {
   mBeaconManager.setBackgroundBetweenScanPeriod(p);
   try {
     mBeaconManager.updateScanPeriods(); // 保证在下一个循环扫描周期生效
   } catch (RemoteException e) {
     LogManager.d(TAG, "RemoteException:" + e.toString());
   }
 }
 /** Called when at least one beacon in a Region is visible. */
 @Override
 public void didEnterRegion(Region region) {
   LogManager.d(TAG, "didEnterRegion(),region uniqueId= " + region.getUniqueId());
   /**
    * 启动测距修正 Tells the BeaconService to start looking for beacons that match the passed
    * Region object, and providing updates on the estimated mDistance every
    * seconds(实际上是每个扫描周期) while beacons in the Region are visible. Note that the Region's
    * unique identifier must be retained to later call the stopRangingBeaconsInRegion method.
    * this will provide an update once per second with the estimated distance to the beacon
    * in the didRAngeBeaconsInRegion method.
    */
   try {
     mBeaconManager.startRangingBeaconsInRegion(ALL_VOLIAM_BEACONS_REGION);
   } catch (RemoteException e) {
     LogManager.d(TAG, "RemoteException:" + e.toString());
   }
 }
 private void loadDefaultModelMap() {
   mModelMap = new HashMap<AndroidModel, DistanceCalculator>();
   try {
     buildModelMap(stringFromFilePath(CONFIG_FILE));
   } catch (Exception e) {
     LogManager.e(e, TAG, "Cannot build model distance calculations");
   }
 }
  @Override
  protected void onDestroy() {
    LogManager.d(TAG, "onDestroy()");
    super.onDestroy();

    mFindBeacons.closeSearcher();
    loghelper.stop();
  }
 @Override
 public double calculateDistance(int txPower, double rssi) {
   if (mDistanceCalculator == null) {
     LogManager.w(TAG, "distance calculator has not been set");
     return -1.0;
   }
   return mDistanceCalculator.calculateDistance(txPower, rssi);
 }
 /**
  * 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;
   }
 }
 private boolean loadModelMapFromFile() {
   File file = new File(mContext.getFilesDir(), CONFIG_FILE);
   FileInputStream inputStream = null;
   BufferedReader reader = null;
   StringBuilder sb = new StringBuilder();
   try {
     inputStream = new FileInputStream(file);
     reader = new BufferedReader(new InputStreamReader(inputStream));
     String line;
     while ((line = reader.readLine()) != null) {
       sb.append(line).append("\n");
     }
   } catch (FileNotFoundException fnfe) {
     // This occurs on the first time the app is run, no error message necessary.
     return false;
   } catch (IOException e) {
     LogManager.e(e, TAG, "Cannot open distance model file %s", file);
     return false;
   } finally {
     if (reader != null) {
       try {
         reader.close();
       } catch (Exception e2) {
       }
     }
     if (inputStream != null) {
       try {
         inputStream.close();
       } catch (Exception e2) {
       }
     }
   }
   try {
     buildModelMap(sb.toString());
     return true;
   } catch (JSONException e) {
     LogManager.e(
         TAG,
         "Cannot update distance models from online database at %s with JSON",
         e,
         mRemoteUpdateUrlString,
         sb.toString());
     return false;
   }
 }
 /**
  * Called with a state value of MonitorNotifier.INSIDE when at least one beacon in a Region
  * is visible. Called with a state value of MonitorNotifier.OUTSIDE when no beacons in a
  * Region are visible.
  */
 @Override
 public void didDetermineStateForRegion(int state, Region region) {
   LogManager.d(
       TAG,
       "didDetermineStateForRegion() ,region uniqueId= "
           + region.getUniqueId()
           + " state="
           + (state == 1 ? "inside" : "outside"));
 }
  /** 停止查找beacons */
  public void onMonitoringStop(View view) {
    logToDisplay("onMonitoringStop(),stopMonitoringBeaconsInRegion");
    LogManager.d(TAG, "onMonitoringStop(),stopMonitoringBeaconsInRegion");
    mFindBeacons.closeSearcher();
    mStart_btn.setEnabled(true);
    mStop_btn.setEnabled(false);

    // 设置记录标志
    isRecorded = true;
  }
  private boolean saveJson(String jsonString) {

    FileOutputStream outputStream = null;

    try {
      outputStream = mContext.openFileOutput(CONFIG_FILE, Context.MODE_PRIVATE);
      outputStream.write(jsonString.getBytes());
      outputStream.close();
    } catch (Exception e) {
      LogManager.w(e, TAG, "Cannot write updated distance model to local storage");
      return false;
    } finally {
      try {
        if (outputStream != null) outputStream.close();
      } catch (Exception e) {
      }
    }
    LogManager.i(TAG, "Successfully saved new distance model file");
    return true;
  }
 /**
  * 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;
 }
  private DistanceCalculator findCalculatorForModel(AndroidModel model) {
    LogManager.d(
        TAG,
        "Finding best distance calculator for %s, %s, %s, %s",
        model.getVersion(),
        model.getBuildNumber(),
        model.getModel(),
        model.getManufacturer());

    if (mModelMap == null) {
      LogManager.d(TAG, "Cannot get distance calculator because modelMap was never initialized");
      return null;
    }

    int highestScore = 0;
    AndroidModel bestMatchingModel = null;
    for (AndroidModel candidateModel : mModelMap.keySet()) {
      if (candidateModel.matchScore(model) > highestScore) {
        highestScore = candidateModel.matchScore(model);
        bestMatchingModel = candidateModel;
      }
    }
    if (bestMatchingModel != null) {
      LogManager.d(TAG, "found a match with score %s", highestScore);
      LogManager.d(
          TAG,
          "Finding best distance calculator for %s, %s, %s, %s",
          bestMatchingModel.getVersion(),
          bestMatchingModel.getBuildNumber(),
          bestMatchingModel.getModel(),
          bestMatchingModel.getManufacturer());
      mModel = bestMatchingModel;
    } else {
      mModel = mDefaultModel;
      LogManager.w(TAG, "Cannot find match for this device.  Using default");
    }
    return mModelMap.get(mModel);
  }
 /**
  * Called when the beacon service is running and ready to accept your commands through the
  * BeaconManager 开始查找beacon
  */
 @Override
 public void onBeaconServiceConnect() {
   /**
    * Tells the BeaconService to start looking for beacons that match the passed Region
    * object. Note that the Region's unique identifier must be retained保存 to later call the
    * stopMonitoringBeaconsInRegion method.
    */
   try {
     // 通知BeaconService,开始监控特定区域的Beacons,一旦检测到beacons,执行MonitorNotifier接口中的回调(进入,离开,临界)
     mBeaconManager.startMonitoringBeaconsInRegion(ALL_VOLIAM_BEACONS_REGION);
   } catch (RemoteException e) {
     LogManager.d(TAG, "RemoteException:" + e.toString());
   }
 }
  @TargetApi(Build.VERSION_CODES.HONEYCOMB)
  private void requestModelMapFromWeb() {

    if (mContext.checkCallingOrSelfPermission("android.permission.INTERNET")
        != PackageManager.PERMISSION_GRANTED) {
      LogManager.w(
          TAG,
          "App has no android.permission.INTERNET permission.  Cannot check for distance model updates");
      return;
    }

    new ModelSpecificDistanceUpdater(
            mContext,
            mRemoteUpdateUrlString,
            new ModelSpecificDistanceUpdater.CompletionHandler() {
              @Override
              public void onComplete(String body, Exception ex, int code) {
                if (ex != null) {
                  LogManager.w(
                      TAG,
                      "Cannot updated distance models from online database at %s",
                      ex,
                      mRemoteUpdateUrlString);
                } else if (code != 200) {
                  LogManager.w(
                      TAG,
                      "Cannot updated distance models from online database at %s "
                          + "due to HTTP status code %s",
                      mRemoteUpdateUrlString,
                      code);
                } else {
                  LogManager.d(TAG, "Successfully downloaded distance models from online database");
                  try {
                    buildModelMap(body);
                    if (saveJson(body)) {
                      loadModelMapFromFile();
                      mDistanceCalculator = findCalculatorForModel(mRequestedModel);
                      LogManager.i(
                          TAG,
                          "Successfully updated distance model with latest from online database");
                    }
                  } catch (JSONException e) {
                    LogManager.w(e, TAG, "Cannot parse json from downloaded distance model");
                  }
                }
              }
            })
        .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
  }
  /** 开始查找附近beacons */
  public void onMonitoringStart(View view) {
    logToDisplay("onMonitoringStart(),startMonitoringBeaconsInRegion");
    LogManager.d(TAG, "onMonitoringStart(),startMonitoringBeaconsInRegion");

    // 根据编辑框,设置前台扫描周期,default 1.1s
    onForegroundScanPeriod(null);

    // 根据编辑框,设置rssi采样周期,即,计算该时间段内的平均RSSI(首末各去掉10%),缺省是20秒(20000毫秒)
    onSamplePeriod(null);

    mFindBeacons.openSearcher();
    mStart_btn.setEnabled(false);
    mStop_btn.setEnabled(true);

    // 记录开始采样时间
    startSample = System.currentTimeMillis();

    // 设置记录标志
    isRecorded = false;
  }
  @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;
  }
  /**
   * Defines a beacon field parsing algorithm based on a string designating the zero-indexed offsets
   * to bytes within a BLE advertisement.
   *
   * <p>If you want to see examples of how other folks have set up BeaconParsers for different kinds
   * of beacons, try doing a Google search for "getBeaconParsers" (include the quotes in the
   * search.)
   *
   * <p>Four prefixes are allowed in the string:
   *
   * <pre>
   *   m - matching byte sequence for this beacon type to parse (exactly one required)
   *   s - ServiceUuid for this beacon type to parse (optional, only for Gatt-based beacons)
   *   i - identifier (at least one required, multiple allowed)
   *   p - power calibration field (exactly one required)
   *   d - data field (optional, multiple allowed)
   *   x - extra layout.  Signifies that the layout is secondary to a primary layout with the same
   *       matching byte sequence (or ServiceUuid).  Extra layouts do not require power or
   *       identifier fields and create Beacon objects without identifiers.
   * </pre>
   *
   * <p>Each prefix is followed by a colon, then an inclusive decimal byte offset for the field from
   * the beginning of the advertisement. In the case of the m prefix, an = sign follows the byte
   * offset, followed by a big endian hex representation of the bytes that must be matched for this
   * beacon type. When multiple i or d entries exist in the string, they will be added in order of
   * definition to the identifier or data array for the beacon when parsing the beacon
   * advertisement. Terms are separated by commas.
   *
   * <p>All offsets from the start of the advertisement are relative to the first byte of the two
   * byte manufacturer code. The manufacturer code is therefore always at position 0-1
   *
   * <p>All data field and identifier expressions may be optionally suffixed with the letter l,
   * which indicates the field should be parsed as little endian. If not present, the field will be
   * presumed to be big endian. Note: serviceUuid fields are always little endian.
   *
   * <p>Identifier fields may be optionally suffixed with the letter v, which indicates the field is
   * variable length, and may be shorter than the declared length if the parsed PDU for the
   * advertisement is shorter than needed to parse the full identifier.
   *
   * <p>If the expression cannot be parsed, a <code>BeaconLayoutException</code> is thrown.
   *
   * <p>Example of a parser string for AltBeacon: </pre>
   *
   * "m:2-3=beac,i:4-19,i:20-21,i:22-23,p:24-24,d:25-25" </pre>
   *
   * <p>This signifies that the beacon type will be decoded when an advertisement is found with
   * 0xbeac in bytes 2-3, and a three-part identifier will be pulled out of bytes 4-19, bytes 20-21
   * and bytes 22-23, respectively. A signed power calibration value will be pulled out of byte 24,
   * and a data field will be pulled out of byte 25. Note: bytes 0-1 of the BLE manufacturer
   * advertisements are the two byte manufacturer code. Generally you should not match on these two
   * bytes when using a BeaconParser, because it will limit your parser to matching only a
   * transmitter made by a specific manufacturer. Software and operating systems that scan for
   * beacons typically ignore these two bytes, allowing beacon manufacturers to use their own
   * company code assigned by Bluetooth SIG. The default parser implementation will already pull out
   * this company code and store it in the beacon.mManufacturer field. Matcher expressions should
   * therefore start with "m2-3:" followed by the multi-byte hex value that signifies the beacon
   * type.
   *
   * @param beaconLayout
   * @return the BeaconParser instance
   */
  public BeaconParser setBeaconLayout(String beaconLayout) {

    Log.d(TAG, "Parsing beacon layout: " + beaconLayout);

    String[] terms = beaconLayout.split(",");
    mExtraFrame = false; // this is not an extra frame by default

    for (String term : terms) {
      boolean found = false;

      Matcher matcher = I_PATTERN.matcher(term);
      while (matcher.find()) {
        found = true;
        try {
          int startOffset = Integer.parseInt(matcher.group(1));
          int endOffset = Integer.parseInt(matcher.group(2));
          Boolean littleEndian = matcher.group(3).contains(LITTLE_ENDIAN_SUFFIX);
          mIdentifierLittleEndianFlags.add(littleEndian);
          Boolean variableLength = matcher.group(3).contains(VARIABLE_LENGTH_SUFFIX);
          mIdentifierVariableLengthFlags.add(variableLength);
          mIdentifierStartOffsets.add(startOffset);
          mIdentifierEndOffsets.add(endOffset);
        } catch (NumberFormatException e) {
          throw new BeaconLayoutException("Cannot parse integer byte offset in term: " + term);
        }
      }
      matcher = D_PATTERN.matcher(term);
      while (matcher.find()) {
        found = true;
        try {
          int startOffset = Integer.parseInt(matcher.group(1));
          int endOffset = Integer.parseInt(matcher.group(2));
          Boolean littleEndian = matcher.group(3).contains("l");
          mDataLittleEndianFlags.add(littleEndian);
          mDataStartOffsets.add(startOffset);
          mDataEndOffsets.add(endOffset);
        } catch (NumberFormatException e) {
          throw new BeaconLayoutException("Cannot parse integer byte offset in term: " + term);
        }
      }
      matcher = P_PATTERN.matcher(term);
      while (matcher.find()) {
        found = true;
        try {
          int startOffset = Integer.parseInt(matcher.group(1));
          int endOffset = Integer.parseInt(matcher.group(2));
          int dBmCorrection = 0;
          if (matcher.group(3) != null) {
            dBmCorrection = Integer.parseInt(matcher.group(3));
          }
          mDBmCorrection = dBmCorrection;
          mPowerStartOffset = startOffset;
          mPowerEndOffset = endOffset;
        } catch (NumberFormatException e) {
          throw new BeaconLayoutException(
              "Cannot parse integer power byte offset in term: " + term);
        }
      }
      matcher = M_PATTERN.matcher(term);
      while (matcher.find()) {
        found = true;
        try {
          int startOffset = Integer.parseInt(matcher.group(1));
          int endOffset = Integer.parseInt(matcher.group(2));
          mMatchingBeaconTypeCodeStartOffset = startOffset;
          mMatchingBeaconTypeCodeEndOffset = endOffset;
        } catch (NumberFormatException e) {
          throw new BeaconLayoutException("Cannot parse integer byte offset in term: " + term);
        }
        String hexString = matcher.group(3);
        try {
          mMatchingBeaconTypeCode = Long.decode("0x" + hexString);
        } catch (NumberFormatException e) {
          throw new BeaconLayoutException(
              "Cannot parse beacon type code: " + hexString + " in term: " + term);
        }
      }
      matcher = S_PATTERN.matcher(term);
      while (matcher.find()) {
        found = true;
        try {
          int startOffset = Integer.parseInt(matcher.group(1));
          int endOffset = Integer.parseInt(matcher.group(2));
          mServiceUuidStartOffset = startOffset;
          mServiceUuidEndOffset = endOffset;
        } catch (NumberFormatException e) {
          throw new BeaconLayoutException("Cannot parse integer byte offset in term: " + term);
        }
        String hexString = matcher.group(3);
        try {
          mServiceUuid = Long.decode("0x" + hexString);
        } catch (NumberFormatException e) {
          throw new BeaconLayoutException(
              "Cannot parse serviceUuid: " + hexString + " in term: " + term);
        }
      }
      matcher = X_PATTERN.matcher(term);
      while (matcher.find()) {
        found = true;
        mExtraFrame = true;
      }

      if (!found) {
        LogManager.d(TAG, "cannot parse term %s", term);
        throw new BeaconLayoutException("Cannot parse beacon layout term: " + term);
      }
    }
    if (!mExtraFrame) {
      // extra frames do not have to have identifiers or power fields, but other types do
      if (mIdentifierStartOffsets.size() == 0 || mIdentifierEndOffsets.size() == 0) {
        throw new BeaconLayoutException(
            "You must supply at least one identifier offset with a prefix of 'i'");
      }
      if (mPowerStartOffset == null || mPowerEndOffset == null) {
        throw new BeaconLayoutException("You must supply a power byte offset with a prefix of 'p'");
      }
    }
    if (mMatchingBeaconTypeCodeStartOffset == null || mMatchingBeaconTypeCodeEndOffset == null) {
      throw new BeaconLayoutException(
          "You must supply a matching beacon type expression with a prefix of 'm'");
    }
    mLayoutSize = calculateLayoutSize();
    return this;
  }
  /**
   * 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;
  }
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    // 建议使用org.altbeacon.beacon.logging.LogManager.javaLogManager输出日志,altbeacon就是使用这种机制,便于发布版本时,减少输出日志信息。
    // 输出所有ERROR(Log.e()), WARN(Log.w()), INFO(Log.i()), DEBUG(Log.d()), VERBOSE(Log.v())
    // 对应日志级别由高到低
    LogManager.setLogger(Loggers.verboseLogger());

    // 全部不输出,在release版本中设置
    // LogManager.setLogger(Loggers.empty());

    // 输出ERROR(Log.e()), WARN(Log.w()),缺省状态,仅输出错误和警告信息,即输出警告级别以上的日志
    // LogManager.setLogger(Loggers.warningLogger());

    // 试验日志输出
    //        LogManager.e(TAG,"Error");
    //        LogManager.w(TAG,"Warn");
    //        LogManager.i(TAG,"info");
    //        LogManager.d(TAG,"debug");
    //        LogManager.v(TAG,"verbose");

    LogManager.d(TAG, "onCreate");
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    // 日志文件
    start_logfile = (Button) findViewById(R.id.start_log);
    end_logfile = (Button) findViewById(R.id.end_log);

    // 设置SD卡中的日志文件,sd卡根目录/rssiRecord/mydistance.log
    loghelper = LogcatHelper.getInstance(this, "rssiRecord", "mydistance.log");
    // 设置SD卡中的日志文件,sd卡根目录/mydistance.log
    // loghelper = LogcatHelper.getInstance(this,"","mydistance.log");

    // 打印D级以上(包括D,I,W,E,F)的TAG,其它tag不打印
    // Logformat = TAG + ":D *:S";

    // 打印D级以上的TAG,和LogcatHelper全部,其它tag不打印
    // Logformat = TAG + ":D LogcatHelper:V *:S";

    // 打印D以上的TAG和RunningAverageRssiFilter,其他tag不打印(*:S)
    // Logformat = TAG + ":D RunningAverageRssiFilter:D *:S";

    // 打印D以上的TAG和RssiDbManager,其他tag不打印(*:S)
    Logformat = TAG + ":D RssiDbManager:D *:S";

    // 打印D以上的FindBeacons,其他tag不打印(*:S)
    // Logformat = "FindBeacons:D *:S";

    // Logformat = "RangedBeacon:V *:S";

    // 打印所有日志, priority=V | D | I | W | E ,级别由低到高
    // Logformat = "";

    // 日志文件
    loghelper.start(Logformat);

    // "开始记录日志"按钮失效,此时已经开始记录日志
    start_logfile.setEnabled(false);

    // 开始/停止监控(查找)beacons
    mStart_btn = (Button) findViewById(R.id.Mstart);
    mStop_btn = (Button) findViewById(R.id.Mstop);
    mStop_btn.setEnabled(false);

    // 获取FindBeacons唯一实例
    mFindBeacons = FindBeacons.getInstance(this);

    // 设置默认前台扫描周期,default 1.1s
    ScanPeriod_edit = (EditText) findViewById(R.id.ScanPeriod_edit);
    ScanPeriod_edit.setText("1.1");
    onForegroundScanPeriod(null);

    // rssi采样周期,即,计算该时间段内的平均RSSI(首末各去掉10%),缺省是20秒(20000毫秒)
    SamplePeriod_edit = (EditText) findViewById(R.id.SamplePeriod_edit);
    SamplePeriod_edit.setText("60"); // 在此设定停留时间,默认为1分钟。
    onSamplePeriod(null);

    // 定位参考点名称
    reference_point_edit = (EditText) findViewById(R.id.RPname);
    reference_point_edit.setText(reference_pointPerf + reference_pointNum);

    // 数据库管理
    mRssiDbManager = new RssiDbManager(trainingActivity.this);

    // 设置获取附近所有beacons的监听对象,在每个扫描周期结束,通过该接口获取找到的所有beacons
    mFindBeacons.setBeaconsListener(mBeaconsListener);

    // 查看手机蓝牙是否可用,若当前状态为不可用,则默认调用意图请求打开系统蓝牙
    mFindBeacons.checkBLEEnable();

    logToDisplay("Mstart,Mstop分别代表查找beacon的开始和结束");
  }