/**
  * 在sd卡下创建crash文件保存路径
  *
  * @return null:创建不成功,String:crash文件保存路径
  */
 private String createCrashReportDir() {
   String workDir = XConfiguration.getInstance().getWorkDirectory();
   if (XStringUtils.isEmptyString(workDir)) {
     XLog.e(CLASS_NAME, "Failed to get workDir at:" + workDir);
     return null;
   }
   File saveDir = new File(workDir, CRASH_REPORT_FILE_PATH);
   String saveDirPath = saveDir.getAbsolutePath();
   if (saveDir.exists() || saveDir.mkdirs()) {
     return saveDirPath;
   }
   XLog.e(CLASS_NAME, "Failed to create crash log directory at:" + saveDirPath);
   return null;
 }
 /**
  * 拨打指定号码的电话
  *
  * @param[in] phoneNumber 要拨打的电话号码
  * @return 拨打电话是否成功
  */
 private boolean initiateVoiceCall(String phoneNumber) {
   try {
     if (isLegalPhoneNum(phoneNumber)) {
       Intent intent = new Intent(Intent.ACTION_CALL);
       intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
       intent.setData(Uri.parse(WebView.SCHEME_TEL + phoneNumber));
       mContext.startActivity(intent);
       return true;
     }
   } catch (ActivityNotFoundException e) {
     XLog.e(CLASS_NAME, e.toString());
   } catch (SecurityException e) {
     XLog.e(CLASS_NAME, e.toString());
   }
   return false;
 }
 /** 删除电池监听器,反注册广播接收器 */
 private void removeBatteryListener() {
   if (null != mBroadcastReceiver) {
     try {
       getContext().unregisterReceiver(mBroadcastReceiver);
       mBroadcastReceiver = null;
     } catch (Exception e) {
       XLog.e(CLASS_NAME, "Error unregistering battery receiver: " + e.getMessage(), e);
     }
   }
 }
 /**
  * 是否传播按键事件。 在软键盘隐藏的时候记录系统时间,当点击手机按键的时候,再取一次系统时间,比较两次时间差
  * 如果差值小于某一值(一个经验值,目前是100毫秒)就认为是在很短时间内响应了手机按键,此时 认为手机按键的操作是关闭软键盘,则不需要再传播事件,如果大于该值,则认为是需要执行手机
  * 按键事件,则需要传播以便执行js
  *
  * <p>为什么要根据临界值去判断? 因为,在软键盘弹出后,我们点击返回键,需要经过一下流程: 软键盘关闭->软键盘将返回事件继续传播->xFace响应返回键事件
  * 那么这一段事件是系统在处理,所以关闭软键盘到响应返回键这个时间很短
  *
  * <p>如果点击屏幕空白处将软键盘关闭,再点返回键,在这一流程中,由于是人为的关闭了软键盘和人为 的点击了返回键,所以这个时间比起系统自己来处理要长得多。
  * 所以才根据临界时间来判断是否是关闭软键盘的操作。
  *
  * @return true 传播 false 不传播
  */
 public boolean isPropagateEvent() {
   long currentSystime = 0;
   currentSystime = System.currentTimeMillis();
   XLog.d(
       CLASS_NAME,
       "The time difference is : " + (currentSystime - mCurrentSysTimeWhenSoftKeyboardHidden));
   // 计算两次时间差,小于临界值就认为,本次事件是关闭软键盘,那么就不传播事件了
   if (currentSystime - mCurrentSysTimeWhenSoftKeyboardHidden < CRITICAL) {
     return false;
   }
   return true;
 }
  @Override
  /**
   * 监听measurement事件,根据view的高度判断软键盘是否弹出
   *
   * @param widthMeasureSpec
   * @param heightMeasureSpec
   */
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    int width, height;

    height = MeasureSpec.getSize(heightMeasureSpec);
    width = MeasureSpec.getSize(widthMeasureSpec);

    // 初始状态
    if (mOldHeight == 0 || mOldHeight == height) {
      XLog.d(CLASS_NAME, "Ignore this event");
    }
    // 转屏
    else if (mScreenHeight == width) {
      int tmp_var = mScreenHeight;
      mScreenHeight = mScreenWidth;
      mScreenWidth = tmp_var;
      XLog.i(CLASS_NAME, "Orientation Change");
    }
    // 软键盘隐藏了
    else if (height > mOldHeight) {
      XLog.i(CLASS_NAME, "Soft Keyboard Hidden");
      mCurrentSysTimeWhenSoftKeyboardHidden = System.currentTimeMillis();
    }

    // 软键盘弹出
    else if (height < mOldHeight) {
      XLog.i(CLASS_NAME, "Soft Keyboard Shown");
    }
    mOldHeight = height;
    mOldWidth = width;
  }
  /**
   * 查找指定匹配的通话记录
   *
   * @param[in] comparisonCallRecord 待匹配的通话记录
   * @param[in] startIndex 要查找的开始索引
   * @param[in] endIndex 要查找的结束位置的索引
   * @return 返回按照匹配的通话记录查找的所有通话记录的JSON数组
   */
  private JSONArray findCallRecords(JSONObject comparisonCallRecord, int startIndex, int endIndex) {
    JSONArray result = new JSONArray();
    if (null != comparisonCallRecord) {
      try {
        String callRecordId = comparisonCallRecord.getString("callRecordId");
        String callRecordAddress = comparisonCallRecord.getString("callRecordAddress");
        String callRecordName = comparisonCallRecord.getString("callRecordName");
        String callRecordType = comparisonCallRecord.getString("callRecordType");
        long startTime = comparisonCallRecord.getLong("startTime");
        long durationSeconds = comparisonCallRecord.getLong("durationSeconds");

        ArrayList<String> projections = new ArrayList<String>();
        projections.add(CallLog.Calls._ID);
        projections.add(CallLog.Calls.NUMBER);
        projections.add(CallLog.Calls.CACHED_NAME);

        ArrayList<String> projectionsValue = new ArrayList<String>();
        projectionsValue.add(callRecordId);
        projectionsValue.add(callRecordAddress);
        projectionsValue.add(callRecordName);

        StringBuilder selection = XUtils.constructSelectionStatement(projections, projectionsValue);
        String selectionStr =
            buildSelectionStr(selection, startTime, durationSeconds, callRecordType);
        Cursor cursor =
            mContentResolver.query(
                CallLog.Calls.CONTENT_URI,
                null,
                selectionStr,
                null,
                CallLog.Calls.DEFAULT_SORT_ORDER);
        if (null == cursor) {
          return result;
        }
        int count = endIndex - startIndex + 1;
        if (count > 0) {
          if (cursor.moveToPosition(startIndex)) {
            do {
              JSONObject callRecord = getCallRecordFromCursor(cursor);
              result.put(callRecord);
              count--;
            } while (cursor.moveToNext() && count > 0);
          }
        }
        cursor.close();
      } catch (JSONException e) {
        XLog.e(CLASS_NAME, e.toString());
      }
    }
    return result;
  }
 /**
  * 根据当前的电池状态信息构建json对象
  *
  * @param batteryIntent 可以获取当前电池状态信息的intent
  * @return 包含电池信息的json对象
  */
 private JSONObject getBatteryInfo(Intent batteryIntent) {
   JSONObject obj = new JSONObject();
   try {
     obj.put("level", batteryIntent.getIntExtra(android.os.BatteryManager.EXTRA_LEVEL, 0));
     obj.put(
         "isPlugged",
         batteryIntent.getIntExtra(android.os.BatteryManager.EXTRA_PLUGGED, -1) > 0
             ? true
             : false);
   } catch (JSONException e) {
     XLog.e(CLASS_NAME, e.getMessage(), e);
   }
   return obj;
 }
 /**
  * 得到指定通话记录类型和id的通话记录
  *
  * @param[in] callRecordType 通话记录类型
  * @param[in] callRecordIndex 通话记录的索引
  * @return 返回通话记录的JSON对象
  */
 private JSONObject getCallRecord(String callRecordType, int recordIndex) {
   int callType = getCallRecordType(callRecordType);
   Cursor c =
       mContentResolver.query(
           CallLog.Calls.CONTENT_URI,
           new String[] {
             CallLog.Calls.NUMBER,
             CallLog.Calls._ID,
             CallLog.Calls.CACHED_NAME,
             CallLog.Calls.TYPE,
             CallLog.Calls.DATE,
             CallLog.Calls.DURATION
           },
           CallLog.Calls.TYPE + "=" + callType,
           null,
           CallLog.Calls.DEFAULT_SORT_ORDER);
   String callRecordAddress = "";
   String callRecordId = "";
   String callRecordName = "";
   long durationSeconds = 0;
   long startTime = 0;
   JSONObject callRecord = new JSONObject();
   if (null != c) {
     if (c.moveToPosition(recordIndex)) {
       callRecordAddress = c.getString(0);
       callRecordId = c.getString(1);
       callRecordName = c.getString(2);
       startTime = c.getLong(4);
       durationSeconds = c.getLong(5);
     }
     c.close();
     try {
       callRecord.put("callRecordAddress", callRecordAddress);
       callRecord.put("callRecordId", callRecordId);
       callRecord.put("callRecordName", callRecordName);
       callRecord.put("durationSeconds", durationSeconds);
       callRecord.put("startTime", startTime);
       callRecord.put("callRecordType", callRecordType);
     } catch (JSONException e) {
       XLog.e(CLASS_NAME, e.getMessage());
     }
   }
   return callRecord;
 }
 /** 通过Cursor得到通话记录的JSON对象 */
 private JSONObject getCallRecordFromCursor(Cursor cursor) {
   String callRecordId = cursor.getString(cursor.getColumnIndex(CallLog.Calls._ID));
   String callRecordAddress = cursor.getString(cursor.getColumnIndex(CallLog.Calls.NUMBER));
   String callRecordName = cursor.getString(cursor.getColumnIndex(CallLog.Calls.CACHED_NAME));
   int callRecordTypeInt = cursor.getInt(cursor.getColumnIndex(CallLog.Calls.TYPE));
   long startTime = cursor.getLong(cursor.getColumnIndex(CallLog.Calls.DATE));
   long durationSeconds = cursor.getLong(cursor.getColumnIndex(CallLog.Calls.DURATION));
   JSONObject callRecord = new JSONObject();
   try {
     callRecord.put("callRecordId", callRecordId);
     callRecord.put("callRecordAddress", callRecordAddress);
     callRecord.put("callRecordName", callRecordName);
     callRecord.put("callRecordType", getCallRecordTypeStr(callRecordTypeInt));
     callRecord.put("startTime", startTime);
     callRecord.put("durationSeconds", durationSeconds);
   } catch (JSONException e) {
     XLog.e(CLASS_NAME, e.toString());
   }
   return callRecord;
 }