Browse Source

修改bug 修改人脸识别的操作
提交内容 修改人脸识别的操作

Bitliker 7 years ago
parent
commit
8119e7e99b
56 changed files with 6378 additions and 0 deletions
  1. 1 0
      app_modular/facesdk/.gitignore
  2. 28 0
      app_modular/facesdk/build.gradle
  3. BIN
      app_modular/facesdk/libs/FaceSDK.jar
  4. BIN
      app_modular/facesdk/libs/ast.jar
  5. BIN
      app_modular/facesdk/libs/baidu_license.jar
  6. 25 0
      app_modular/facesdk/proguard-rules.pro
  7. 17 0
      app_modular/facesdk/src/main/AndroidManifest.xml
  8. BIN
      app_modular/facesdk/src/main/assets/align_model.binary
  9. BIN
      app_modular/facesdk/src/main/assets/blur.binary
  10. BIN
      app_modular/facesdk/src/main/assets/facedetect.binary
  11. BIN
      app_modular/facesdk/src/main/assets/occlu.binary
  12. BIN
      app_modular/facesdk/src/main/assets/score.binary
  13. 206 0
      app_modular/facesdk/src/main/java/com/baidu/aip/FaceConfig.java
  14. 252 0
      app_modular/facesdk/src/main/java/com/baidu/aip/FaceDetector.java
  15. 32 0
      app_modular/facesdk/src/main/java/com/baidu/aip/FaceEnvironment.java
  16. 88 0
      app_modular/facesdk/src/main/java/com/baidu/aip/FaceSDKManager.java
  17. 81 0
      app_modular/facesdk/src/main/java/com/baidu/aip/ImageFrame.java
  18. 513 0
      app_modular/facesdk/src/main/java/com/baidu/aip/excep/activity/DetectLoginActivity.java
  19. 543 0
      app_modular/facesdk/src/main/java/com/baidu/aip/excep/activity/FaceDetectActivity.java
  20. 257 0
      app_modular/facesdk/src/main/java/com/baidu/aip/excep/activity/RealTimeDetectFaceActivty.java
  21. 136 0
      app_modular/facesdk/src/main/java/com/baidu/aip/excep/utils/ExifUtil.java
  22. 145 0
      app_modular/facesdk/src/main/java/com/baidu/aip/excep/utils/ImageSaveUtil.java
  23. 278 0
      app_modular/facesdk/src/main/java/com/baidu/aip/excep/utils/ImageUtil.java
  24. 123 0
      app_modular/facesdk/src/main/java/com/baidu/aip/excep/widget/BrightnessTools.java
  25. 139 0
      app_modular/facesdk/src/main/java/com/baidu/aip/excep/widget/DensityUtils.java
  26. 236 0
      app_modular/facesdk/src/main/java/com/baidu/aip/excep/widget/FaceRoundView.java
  27. 86 0
      app_modular/facesdk/src/main/java/com/baidu/aip/excep/widget/WaveHelper.java
  28. 334 0
      app_modular/facesdk/src/main/java/com/baidu/aip/excep/widget/WaveView.java
  29. 31 0
      app_modular/facesdk/src/main/java/com/baidu/aip/face/ArgbPool.java
  30. 108 0
      app_modular/facesdk/src/main/java/com/baidu/aip/face/CameraImageSource.java
  31. 47 0
      app_modular/facesdk/src/main/java/com/baidu/aip/face/DetectRegionProcessor.java
  32. 91 0
      app_modular/facesdk/src/main/java/com/baidu/aip/face/FaceCropper.java
  33. 206 0
      app_modular/facesdk/src/main/java/com/baidu/aip/face/FaceDetectManager.java
  34. 267 0
      app_modular/facesdk/src/main/java/com/baidu/aip/face/FaceFilter.java
  35. 19 0
      app_modular/facesdk/src/main/java/com/baidu/aip/face/FaceProcessor.java
  36. 121 0
      app_modular/facesdk/src/main/java/com/baidu/aip/face/FileImageSource.java
  37. 52 0
      app_modular/facesdk/src/main/java/com/baidu/aip/face/ImageSource.java
  38. 17 0
      app_modular/facesdk/src/main/java/com/baidu/aip/face/OnFrameAvailableListener.java
  39. 80 0
      app_modular/facesdk/src/main/java/com/baidu/aip/face/PreviewView.java
  40. 217 0
      app_modular/facesdk/src/main/java/com/baidu/aip/face/TexturePreviewView.java
  41. 415 0
      app_modular/facesdk/src/main/java/com/baidu/aip/face/camera/Camera1Control.java
  42. 769 0
      app_modular/facesdk/src/main/java/com/baidu/aip/face/camera/Camera2Control.java
  43. 156 0
      app_modular/facesdk/src/main/java/com/baidu/aip/face/camera/CameraView.java
  44. 135 0
      app_modular/facesdk/src/main/java/com/baidu/aip/face/camera/ICameraControl.java
  45. 8 0
      app_modular/facesdk/src/main/java/com/baidu/aip/face/camera/PermissionCallback.java
  46. BIN
      app_modular/facesdk/src/main/jniLibs/arm64-v8a/libFaceSDK.so
  47. BIN
      app_modular/facesdk/src/main/jniLibs/arm64-v8a/libbaidu_license.so
  48. BIN
      app_modular/facesdk/src/main/jniLibs/armeabi-v7a/libFaceSDK.so
  49. BIN
      app_modular/facesdk/src/main/jniLibs/armeabi-v7a/libbaidu_license.so
  50. BIN
      app_modular/facesdk/src/main/jniLibs/x86/libFaceSDK.so
  51. BIN
      app_modular/facesdk/src/main/jniLibs/x86/libbaidu_license.so
  52. 33 0
      app_modular/facesdk/src/main/res/layout/activity_login_detected.xml
  53. BIN
      app_modular/facesdk/src/main/res/mipmap-xxhdpi/detect_close.png
  54. BIN
      app_modular/facesdk/src/main/res/mipmap-xxhdpi/icon_success.png
  55. 11 0
      app_modular/facesdk/src/main/res/values/dimens.xml
  56. 75 0
      app_modular/facesdk/src/main/res/values/strings.xml

+ 1 - 0
app_modular/facesdk/.gitignore

@@ -0,0 +1 @@
+/build

+ 28 - 0
app_modular/facesdk/build.gradle

@@ -0,0 +1,28 @@
+apply plugin: 'com.android.library'
+
+android {
+    compileSdkVersion rootProject.ext.android.compileSdkVersion
+    buildToolsVersion rootProject.ext.android.buildToolsVersion
+
+    defaultConfig {
+        minSdkVersion rootProject.ext.android.minSdkVersion
+        targetSdkVersion rootProject.ext.android.targetSdkVersion
+        versionCode 1
+        versionName "1.0"
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+}
+
+
+dependencies {
+    compile fileTree(dir: 'libs', include: ['*.jar'])
+    compile project(':apputils')
+
+
+
+}

BIN
app_modular/facesdk/libs/FaceSDK.jar


BIN
app_modular/facesdk/libs/ast.jar


BIN
app_modular/facesdk/libs/baidu_license.jar


+ 25 - 0
app_modular/facesdk/proguard-rules.pro

@@ -0,0 +1,25 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /Users/baidu/IDE/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile

+ 17 - 0
app_modular/facesdk/src/main/AndroidManifest.xml

@@ -0,0 +1,17 @@
+<!--
+  ~ Copyright (C) 2017 Baidu, Inc. All Rights Reserved.
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+
+    package="demo.face.aip.baidu.com.facesdk">
+
+    <uses-permission android:name="android.permission.CAMERA" />
+    <application
+        android:allowBackup="true"
+        android:supportsRtl="true">
+
+        <activity android:name="com.baidu.aip.excep.activity.RealTimeDetectFaceActivty"
+            android:label="刷脸打卡" />
+    </application>
+
+</manifest>

BIN
app_modular/facesdk/src/main/assets/align_model.binary


BIN
app_modular/facesdk/src/main/assets/blur.binary


BIN
app_modular/facesdk/src/main/assets/facedetect.binary


BIN
app_modular/facesdk/src/main/assets/occlu.binary


BIN
app_modular/facesdk/src/main/assets/score.binary


+ 206 - 0
app_modular/facesdk/src/main/java/com/baidu/aip/FaceConfig.java

@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2018 Baidu, Inc. All Rights Reserved.
+ */
+package com.baidu.aip;
+
+import java.io.Serializable;
+
+/**
+ * 人脸检测参数配置类
+ */
+public class FaceConfig implements Serializable {
+
+    private static final String TAG = FaceConfig.class.getSimpleName();
+
+    // 人脸检测参数
+    /**
+     * 图像光照阀值
+     */
+    public float brightnessValue = FaceEnvironment.VALUE_BRIGHTNESS;
+    /**
+     * 图像模糊阀值
+     */
+    public float blurnessValue = FaceEnvironment.VALUE_BLURNESS;
+    /**
+     * 图像中人脸遮挡阀值
+     */
+    public float occlusionValue = FaceEnvironment.VALUE_OCCLUSION;
+    /**
+     * 图像中人脸抬头低头角度阀值
+     */
+    public int headPitchValue = FaceEnvironment.VALUE_HEAD_PITCH;
+    /**
+     * 图像中人脸左右角度阀值
+     */
+    public int headYawValue = FaceEnvironment.VALUE_HEAD_YAW;
+    /**
+     * 图像中人脸偏头阀值
+     */
+    public int headRollValue = FaceEnvironment.VALUE_HEAD_ROLL;
+    /**
+     * 裁剪图像中人脸时的大小
+     */
+    public int cropFaceValue = FaceEnvironment.VALUE_CROP_FACE_SIZE;
+    /**
+     * 图像能被检测出人脸的最小人脸值
+     */
+    public int minFaceSize = FaceEnvironment.VALUE_MIN_FACE_SIZE;
+    /**
+     * 图像能被检测出人脸阀值
+     */
+    public float notFaceValue = FaceEnvironment.VALUE_NOT_FACE_THRESHOLD;
+    /**
+     * 人脸采集图片数量阀值
+     */
+    public int maxCropImageNum = FaceEnvironment.VALUE_MAX_CROP_IMAGE_NUM;
+    /**
+     * 是否进行人脸图片质量检测
+     */
+    public boolean isCheckFaceQuality = FaceEnvironment.VALUE_IS_CHECK_QUALITY;
+    /**
+     * 是否开启提示音
+     */
+    public boolean isSound = true;
+    /**
+     * 是否进行检测
+     */
+    public boolean isVerifyLive = true;
+    /**
+     * 人脸检测时开启的进程数,建议为CPU核数
+     */
+    public int faceDecodeNumberOfThreads = 0;
+    /**
+     * 是否随机活体检测动作
+     */
+    public boolean isLivenessRandom = false;
+    /**
+     * 随机活体检测动作数
+     */
+    public int livenessRandomCount = FaceEnvironment.VALUE_LIVENESS_DEFAULT_RANDOM_COUNT;
+
+
+    public FaceConfig() {
+    }
+
+    public float getBrightnessValue() {
+        return brightnessValue;
+    }
+
+    public void setBrightnessValue(float brightnessValue) {
+        this.brightnessValue = brightnessValue;
+    }
+
+    public float getBlurnessValue() {
+        return blurnessValue;
+    }
+
+    public void setBlurnessValue(float blurnessValue) {
+        this.blurnessValue = blurnessValue;
+    }
+
+    public float getOcclusionValue() {
+        return occlusionValue;
+    }
+
+    public void setOcclusionValue(float occlusionValue) {
+        this.occlusionValue = occlusionValue;
+    }
+
+    public int getHeadPitchValue() {
+        return headPitchValue;
+    }
+
+    public void setHeadPitchValue(int headPitchValue) {
+        this.headPitchValue = headPitchValue;
+    }
+
+    public int getHeadYawValue() {
+        return headYawValue;
+    }
+
+    public void setHeadYawValue(int headYawValue) {
+        this.headYawValue = headYawValue;
+    }
+
+    public int getHeadRollValue() {
+        return headRollValue;
+    }
+
+    public void setHeadRollValue(int headRollValue) {
+        this.headRollValue = headRollValue;
+    }
+
+    public int getCropFaceValue() {
+        return cropFaceValue;
+    }
+
+    public void setCropFaceValue(int cropFaceValue) {
+        this.cropFaceValue = cropFaceValue;
+    }
+
+    public int getMinFaceSize() {
+        return minFaceSize;
+    }
+
+    public void setMinFaceSize(int minFaceSize) {
+        this.minFaceSize = minFaceSize;
+    }
+
+    public float getNotFaceValue() {
+        return notFaceValue;
+    }
+
+    public void setNotFaceValue(float notFaceValue) {
+        this.notFaceValue = notFaceValue;
+    }
+
+    public int getMaxCropImageNum() {
+        return maxCropImageNum;
+    }
+
+    public void setMaxCropImageNum(int maxCropImageNum) {
+        this.maxCropImageNum = maxCropImageNum;
+    }
+
+    public boolean isCheckFaceQuality() {
+        return isCheckFaceQuality;
+    }
+
+    public void setCheckFaceQuality(boolean checkFaceQuality) {
+        isCheckFaceQuality = checkFaceQuality;
+    }
+
+    public boolean isSound() {
+        return isSound;
+    }
+
+    public void setSound(boolean sound) {
+        isSound = sound;
+    }
+
+    public boolean isLivenessRandom() {
+        return isLivenessRandom;
+    }
+
+    public void setLivenessRandom(boolean livenessRandom) {
+        isLivenessRandom = livenessRandom;
+    }
+
+
+    public boolean isVerifyLive() {
+        return isVerifyLive;
+    }
+
+    public void setVerifyLive(boolean verifyLive) {
+        isVerifyLive = verifyLive;
+    }
+
+    public int getFaceDecodeNumberOfThreads() {
+        return faceDecodeNumberOfThreads;
+    }
+
+    public void setFaceDecodeNumberOfThreads(int faceDecodeNumberOfThreads) {
+        this.faceDecodeNumberOfThreads = faceDecodeNumberOfThreads;
+    }
+
+}

+ 252 - 0
app_modular/facesdk/src/main/java/com/baidu/aip/FaceDetector.java

@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2017 Baidu, Inc. All Rights Reserved.
+ */
+package com.baidu.aip;
+
+import com.baidu.idl.facesdk.FaceInfo;
+import com.baidu.idl.facesdk.FaceSDK;
+import com.baidu.idl.facesdk.FaceTracker;
+
+import android.content.Context;
+import android.support.annotation.FloatRange;
+import android.support.annotation.IntRange;
+
+public class FaceDetector {
+
+    /**
+     * 检测结果代码 成功
+     */
+    public static final int DETECT_CODE_OK = FaceTracker.ErrCode.OK.ordinal();
+    public static final int DETECT_CODE_PITCH_OUT_OF_DOWN_MAX_RANGE =
+            FaceTracker.ErrCode.PITCH_OUT_OF_DOWN_MAX_RANGE.ordinal();
+    public static final int DETECT_CODE_PITCH_OUT_OF_UP_MAX_RANGE =
+            FaceTracker.ErrCode.PITCH_OUT_OF_UP_MAX_RANGE.ordinal();
+    public static final int DETECT_CODE_YAW_OUT_OF_LEFT_MAX_RANGE =
+            FaceTracker.ErrCode.YAW_OUT_OF_LEFT_MAX_RANGE.ordinal();
+    public static final int DETECT_CODE_YAW_OUT_OF_RIGHT_MAX_RANGE =
+            FaceTracker.ErrCode.YAW_OUT_OF_RIGHT_MAX_RANGE.ordinal();
+    public static final int DETECT_CODE_POOR_ILLUMINATION =
+            FaceTracker.ErrCode.POOR_ILLUMINATION.ordinal();
+    public static final int DETECT_CODE_FACE_NOT_DETECTED =
+            FaceTracker.ErrCode.NO_FACE_DETECTED.ordinal();
+    /**
+     * 检测结果代码 没有检测到人脸
+     */
+    public static final int DETECT_CODE_NO_FACE_DETECTED =
+            FaceTracker.ErrCode.NO_FACE_DETECTED.ordinal();
+
+    /**
+     * 默认非人脸阈值
+     */
+    public static final float DEFAULT_NOT_FACE_THRESHOLD = 0.8f;
+
+    /**
+     * 默认最小人脸,小于此值的人脸将检测不出来
+     */
+    public static final int DEFAULT_MIN_FACE_SIZE = 80;
+
+    /**
+     * 默认光照阈值
+     */
+
+    public static final float DEFAULT_ILLUMINATION_THRESHOLD = 40.0f;
+    /**
+     * 模糊值,范围为0-1.数值越大,条件越宽松。图像可能越模糊。
+     */
+
+    public static final float DEFAULT_BLURRINESS_THRESHOLD = 0.3f;
+    public static final float DEFAULT_OCCULTATION_THRESHOLD = 0.5f;
+    public static final int DEFAULT_HEAD_ANGLE = 45;
+
+    private FaceTracker mFaceTracker;
+    private static FaceDetector sInstance;
+
+    public static void init(Context context, String appId, String licenseFileName) {
+        if (sInstance == null) {
+            sInstance = new FaceDetector(context, appId, licenseFileName);
+        }
+    }
+
+    public static FaceDetector getInstance() {
+        return sInstance;
+    }
+
+    private FaceDetector(Context context, String appId, String licenseFileName) {
+        mFaceTracker = new FaceTracker(context);
+        mFaceTracker.set_isFineAlign(false);
+        mFaceTracker.set_isVerifyLive(false);
+        mFaceTracker.set_isCheckQuality(false);
+        mFaceTracker.set_notFace_thr(DEFAULT_NOT_FACE_THRESHOLD);
+        mFaceTracker.set_min_face_size(DEFAULT_MIN_FACE_SIZE);
+        mFaceTracker.set_cropFaceSize(DEFAULT_MIN_FACE_SIZE );
+        mFaceTracker.set_illum_thr(DEFAULT_ILLUMINATION_THRESHOLD);
+        mFaceTracker.set_blur_thr(DEFAULT_BLURRINESS_THRESHOLD);
+        mFaceTracker.set_occlu_thr(DEFAULT_OCCULTATION_THRESHOLD);
+        mFaceTracker.set_max_reg_img_num(1);
+        mFaceTracker.set_eulur_angle_thr(
+                DEFAULT_HEAD_ANGLE,
+                DEFAULT_HEAD_ANGLE,
+                DEFAULT_HEAD_ANGLE
+        );
+        // 检测人脸间隔时间,时间越短,人脸进入画面越快被检测到
+     //   mFaceTracker.set_detection_frame_interval(10);
+        // 人脸检测到后追踪的时间间隔
+     //   mFaceTracker.set_intervalTime(300);
+        // 根据设备的cpu核心数设定人脸sdk使用的线程数,如双核设置为2,四核设置为4
+        FaceSDK.setNumberOfThreads(4);
+    }
+
+    /**
+     * 根据设备的cpu核心数设定人脸sdk使用的线程数,如双核设置为2,四核设置为4
+     * @param numberOfThreads
+     */
+    public void setNumberOfThreads(int numberOfThreads) {
+        FaceSDK.setNumberOfThreads(numberOfThreads);
+    }
+
+    /**
+     * 设置人脸概率阈值。范围是0-1。1是最严格,基本不存在?
+     *
+     * @param threshold 人脸概率阈值
+     */
+    public void setNotFaceThreshold(float threshold) {
+        mFaceTracker.set_notFace_thr(threshold);
+    }
+
+    /**
+     * 设置最小检测人脸(两个眼睛之间的距离)小于此值的人脸检测不出来。范围为80-200。该值会严重影响检测性能。
+     * 设置为100的性能损耗大概是200的4倍。所以在满足要求的前提下尽量设置大一些。默认值为 @see (DEFAULT_MIN_FACE_SIZE)
+     *
+     * @param faceSize 最小可检测人脸大小。
+     */
+    public void setMinFaceSize(@IntRange(from = 80, to = 200) int faceSize) {
+        mFaceTracker.set_min_face_size(faceSize);
+    }
+
+    /** 设置最低光照强度(YUV中的Y分量)取值范围0-255,建议值大于40.
+     * @param threshold 最低光照强度。
+     */
+    public void setIlluminationThreshold(float threshold) {
+        mFaceTracker.set_illum_thr(threshold);
+    }
+
+    /**
+     * 设置模糊度。取值范围为0-1;0表示特别清晰,1表示,特别模糊。默认值为 @see(DEFAULT_BLURRINESS_THRESHOLD)。
+     *
+     * @param threshold 模糊度
+     */
+    public void setBlurrinessThreshold(@FloatRange(from = 0, to = 1) float threshold) {
+        mFaceTracker.set_blur_thr(threshold);
+    }
+
+    public void setOcclulationThreshold(float threshold) {
+        mFaceTracker.set_occlu_thr(threshold);
+    }
+
+    /**
+     * 设置是否检测质量
+     *
+     * @param checkQuality 是否检测质量
+     */
+    public void setCheckQuality(boolean checkQuality) {
+        mFaceTracker.set_isCheckQuality(checkQuality);
+    }
+
+    // yaw 左右
+    // pitch 上下
+    // roll 扭头
+
+    /**
+     * 设置头部欧拉角,大于这个值的人脸将不能识别。
+     *
+     * @param yaw   左右摇头的角度。
+     * @param roll  顺时针扭头的角度
+     * @param pitch 上下点头的角度。
+     */
+    public void setEulerAngleThreshold(int yaw, int roll, int pitch) {
+        mFaceTracker.set_eulur_angle_thr(yaw, roll, pitch);
+    }
+
+    /**
+     * 检测间隔设置,单位ms.该值控制检测间隔。值越大,检测时间越长,性能消耗越低。值越小,能更快的检测到人脸。
+     * @param interval 间隔时间,单位ms;
+     */
+    public void setDetectInterval(int interval) {
+        mFaceTracker.set_detection_frame_interval(interval);
+    }
+
+    public void setTrackInterval(int interval) {
+        mFaceTracker.set_intervalTime(interval);
+    }
+
+    /**
+     * 进行人脸检测。返回检测结果代码。如果返回值为DETECT_CODE_OK 可调用 getTrackedFaces 获取人脸相关信息。
+     *
+     * @param argb   人脸argb_8888图片。
+     * @param width  图片宽度
+     * @param height 图片高度
+     *
+     * @return 检测结果代码。
+     */
+    public int detect(int[] argb, int width, int height) {
+        return this.mFaceTracker
+//                .prepare_data_for_verify(argb, height, width, FaceSDK.ImgType.ARGB.ordinal(),
+//                        FaceTracker.ActionType.RECOGNIZE.ordinal());
+                .prepare_max_face_data_for_verify(argb, height, width, FaceSDK.ImgType.ARGB.ordinal(),
+                        FaceTracker.ActionType.RECOGNIZE.ordinal());
+    }
+
+    /**
+     * 进行人脸检测。返回检测结果代码。如果返回值为DETECT_CODE_OK 可调用 getTrackedFaces 获取人脸相关信息。
+     *
+     * @param imageFrame 人脸图片帧
+     *
+     * @return 检测结果代码。
+     */
+    public int detect(ImageFrame imageFrame) {
+        return detect(imageFrame.getArgb(), imageFrame.getWidth(), imageFrame.getHeight());
+    }
+
+    /**
+     * yuv图片转换为相应的argb;
+     *
+     * @param yuv      yuv_420p图片
+     * @param width    图片宽度
+     * @param height   图片高度
+     * @param argb     接收argb用得 int数组
+     * @param rotation yuv图片的旋转角度
+     * @param mirror   是否为镜像
+     */
+    public static void yuvToARGB(byte[] yuv, int width, int height, int[] argb, int rotation, int mirror) {
+        FaceSDK.getARGBFromYUVimg(yuv, argb, width, height, rotation, mirror);
+    }
+
+    /**
+     * 获取当前跟踪的人脸信息。
+     *
+     * @return 返回人脸信息,没有时返回null
+     */
+    public FaceInfo[] getTrackedFaces() {
+        return mFaceTracker.get_TrackedFaceInfo();
+    }
+
+    /**
+     * 获取当前跟踪的人脸信息。只返回一个。
+     *
+     * @return 返回人脸信息,没有时返回null
+     */
+    public FaceInfo getTrackedFace() {
+        FaceInfo[] faces = mFaceTracker.get_TrackedFaceInfo();
+        if (faces != null && faces.length > 0) {
+            return mFaceTracker.get_TrackedFaceInfo()[0];
+        }
+        return null;
+    }
+
+    /**
+     * 重置跟踪人脸。下次将重新开始跟踪。
+     */
+    public void clearTrackedFaces() {
+        mFaceTracker.clearTrackedFaces();
+    }
+}

+ 32 - 0
app_modular/facesdk/src/main/java/com/baidu/aip/FaceEnvironment.java

@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2018 Baidu, Inc. All Rights Reserved.
+ */
+package com.baidu.aip;
+
+/**
+ * SDK全局配置信息
+ */
+public class FaceEnvironment {
+
+    // SDK基本信息
+    public static final String TAG = "Baidu-IDL-FaceSDK";
+    public static final String OS = "android";
+    public static final String SDK_VERSION = "3.1.0.0";
+    public static final int AG_ID = 3;
+
+    // SDK配置参数
+    public static final float VALUE_BRIGHTNESS = 40f;
+    public static final float VALUE_BLURNESS = 0.5f;
+    public static final float VALUE_OCCLUSION = 0.5f;
+    public static final int VALUE_HEAD_PITCH = 45;
+    public static final int VALUE_HEAD_YAW = 45;
+    public static final int VALUE_HEAD_ROLL = 45;
+    public static final int VALUE_CROP_FACE_SIZE = 100;
+    public static final int VALUE_MIN_FACE_SIZE = 100;
+    public static final float VALUE_NOT_FACE_THRESHOLD = 0.6f;
+    public static final boolean VALUE_IS_CHECK_QUALITY = true;
+    public static final int VALUE_DECODE_THREAD_NUM = 2;
+    public static final int VALUE_LIVENESS_DEFAULT_RANDOM_COUNT = 3;
+    public static final int VALUE_MAX_CROP_IMAGE_NUM = 1;
+
+}

+ 88 - 0
app_modular/facesdk/src/main/java/com/baidu/aip/FaceSDKManager.java

@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2018 Baidu, Inc. All Rights Reserved.
+ */
+package com.baidu.aip;
+
+import android.content.Context;
+
+import com.baidu.idl.facesdk.FaceRecognize;
+import com.baidu.idl.facesdk.FaceSDK;
+import com.baidu.idl.facesdk.FaceTracker;
+
+public class FaceSDKManager {
+    private FaceTracker faceTracker;
+    private FaceRecognize faceRecognize;
+
+    private static class HolderClass {
+        private static final FaceSDKManager instance = new FaceSDKManager();
+    }
+
+    public static FaceSDKManager getInstance() {
+        return HolderClass.instance;
+    }
+
+    private FaceSDKManager() {
+    }
+
+    /**
+     * FaceSDK 初始化,用户可以根据自己的需求实例化FaceTracker 和 FaceRecognize ,具体功能参考文档
+     *
+     * @param context
+     */
+    public void init(Context context, String licenseId, String licenseFileName) {
+      /*  FaceSDK.initLicense(context, "faceexample-face-android",
+                "idl-license.faceexample-face-android", true); */
+        FaceSDK.initLicense(context, licenseId,
+                licenseFileName, true);
+        FaceSDK.initModel(context);
+        //  FaceSDK.initModel(context,
+        //        FaceSDK.AlignMethodType.CDNN,
+        //      FaceSDK.ParsMethodType.NOT_USE);
+        getFaceTracker(context);
+        getFaceRecognize(context);
+    }
+
+    /**
+     * 初始化FaceTracker,成功之后可以直接使用实例方法
+     *
+     * @param context
+     * @return
+     */
+    public FaceTracker getFaceTracker(Context context) {
+        if (faceTracker == null) {
+            faceTracker = new FaceTracker(context);
+            faceTracker.set_isFineAlign(false);
+            faceTracker.set_isFineAlign(false);
+            faceTracker.set_isVerifyLive(true);
+            faceTracker.set_DetectMethodType(1);
+            faceTracker.set_isCheckQuality(FaceEnvironment.VALUE_IS_CHECK_QUALITY);
+            faceTracker.set_notFace_thr(FaceEnvironment.VALUE_NOT_FACE_THRESHOLD);
+            faceTracker.set_min_face_size(FaceEnvironment.VALUE_MIN_FACE_SIZE);
+            faceTracker.set_cropFaceSize(FaceEnvironment.VALUE_CROP_FACE_SIZE);
+            faceTracker.set_illum_thr(FaceEnvironment.VALUE_BRIGHTNESS);
+            faceTracker.set_blur_thr(FaceEnvironment.VALUE_BLURNESS);
+            faceTracker.set_occlu_thr(FaceEnvironment.VALUE_OCCLUSION);
+            faceTracker.set_max_reg_img_num(FaceEnvironment.VALUE_MAX_CROP_IMAGE_NUM);
+            faceTracker.set_eulur_angle_thr(
+                    FaceEnvironment.VALUE_HEAD_PITCH,
+                    FaceEnvironment.VALUE_HEAD_YAW,
+                    FaceEnvironment.VALUE_HEAD_ROLL
+            );
+            faceTracker.set_track_by_detection_interval(800);
+            return faceTracker;
+        }
+        return faceTracker;
+    }
+
+    /**
+     * 初始化FaceRecognize,成功之后可用直接使用实例方法
+     *
+     * @param context
+     */
+    public FaceRecognize getFaceRecognize(Context context) {
+        if (faceRecognize == null) {
+            faceRecognize = new FaceRecognize(context);
+        }
+        return faceRecognize;
+    }
+}

+ 81 - 0
app_modular/facesdk/src/main/java/com/baidu/aip/ImageFrame.java

@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2017 Baidu, Inc. All Rights Reserved.
+ */
+package com.baidu.aip;
+
+import com.baidu.aip.face.ArgbPool;
+
+/**
+ * 该类封装了一帧图片。
+ */
+public class ImageFrame {
+    /**
+     * argb数据
+     */
+    private int[] argb;
+    /**
+     * 图片宽度
+     */
+    private int width;
+    /**
+     * 图片调试
+     */
+    private int height;
+    private ArgbPool pool;
+    private boolean retained = false;
+
+    public ImageFrame() {
+
+    }
+
+    public ImageFrame(int[] argb, int width, int height) {
+        this.argb = argb;
+        this.width = width;
+        this.height = height;
+    }
+
+    public void setPool(ArgbPool pool) {
+        this.pool = pool;
+    }
+
+    public ArgbPool getPool() {
+        return pool;
+    }
+
+
+    public int[] getArgb() {
+        return argb;
+    }
+
+    public void setArgb(int[] argb) {
+        this.argb = argb;
+    }
+
+    public int getWidth() {
+        return width;
+    }
+
+    public void setWidth(int width) {
+        this.width = width;
+    }
+
+    public int getHeight() {
+        return height;
+    }
+
+    public void setHeight(int height) {
+        this.height = height;
+    }
+
+    // TODO
+    public void retain() {
+        this.retained = true;
+    }
+
+    public void release() {
+//        if (!retained) {
+//            pool.release(argb);//TODO
+//        }
+        retained = false;
+    }
+}

+ 513 - 0
app_modular/facesdk/src/main/java/com/baidu/aip/excep/activity/DetectLoginActivity.java

@@ -0,0 +1,513 @@
+/*
+ * Copyright (C) 2017 Baidu, Inc. All Rights Reserved.
+ */
+package com.baidu.aip.excep.activity;
+
+import android.Manifest;
+import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Message;
+import android.support.annotation.RequiresApi;
+import android.support.v4.app.ActivityCompat;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.view.WindowManager;
+import android.widget.RelativeLayout;
+
+import com.baidu.aip.FaceSDKManager;
+import com.baidu.aip.ImageFrame;
+import com.baidu.aip.excep.utils.Base64Util;
+import com.baidu.aip.excep.widget.BrightnessTools;
+import com.baidu.aip.excep.widget.FaceRoundView;
+import com.baidu.aip.excep.widget.WaveHelper;
+import com.baidu.aip.excep.widget.WaveView;
+import com.baidu.aip.face.CameraImageSource;
+import com.baidu.aip.face.DetectRegionProcessor;
+import com.baidu.aip.face.FaceDetectManager;
+import com.baidu.aip.face.FaceFilter;
+import com.baidu.aip.face.PreviewView;
+import com.baidu.aip.face.camera.ICameraControl;
+import com.baidu.aip.face.camera.PermissionCallback;
+import com.baidu.idl.facesdk.FaceInfo;
+import com.modular.apputils.activity.BaseNetActivity;
+
+import java.lang.ref.WeakReference;
+
+import demo.face.aip.baidu.com.facesdk.R;
+
+/**
+ * 实时检测调用identify进行人脸识别,MainActivity未给出改示例的入口,开发者可以在MainActivity调用
+ * Intent intent = new Intent(MainActivity.this, DetectLoginActivity.class);
+ * startActivity(intent);
+ */
+public abstract class DetectLoginActivity extends BaseNetActivity {
+
+    private final static int MSG_INITVIEW = 1001;
+    private final static int MSG_DETECTTIME = 1002;
+    private PreviewView previewView;
+    private View mInitView;
+    //  private TextureView textureView;
+    public FaceRoundView rectView;
+    private boolean mGoodDetect = false;
+    private static final double ANGLE = 15;
+    private boolean mDetectStoped = false;
+    private Handler mHandler;
+    //  private boolean mReDetect = true;
+    private String mCurTips;
+    //  private ProgressBar mProgress;
+    public boolean mUploading = false;
+    private long mLastTipsTime = 0;
+    public int mDetectCount = 0;
+    private int mCurFaceId = -1;
+
+    private FaceDetectManager faceDetectManager;
+    private DetectRegionProcessor cropProcessor = new DetectRegionProcessor();
+    private WaveHelper mWaveHelper;
+    private WaveView mWaveview;
+    private int mBorderColor = Color.parseColor("#28FFFFFF");
+    private int mBorderWidth = 0;
+    private int mScreenW;
+    private int mScreenH;
+
+
+    @Override
+    protected int getLayoutId() {
+        return R.layout.activity_login_detected;
+    }
+
+    @Override
+    protected void init() throws Exception {
+        faceDetectManager = new FaceDetectManager(this);
+        initScreen();
+        initView();
+        mHandler = new InnerHandler(this);
+        mHandler.sendEmptyMessageDelayed(MSG_INITVIEW, 500);
+    }
+
+    private void initScreen() {
+        WindowManager manager = getWindowManager();
+        DisplayMetrics outMetrics = new DisplayMetrics();
+        manager.getDefaultDisplay().getMetrics(outMetrics);
+        mScreenW = outMetrics.widthPixels;
+        mScreenH = outMetrics.heightPixels;
+    }
+
+    private void initView() {
+
+        mInitView = findViewById(R.id.camera_layout);
+        previewView = (PreviewView) findViewById(R.id.preview_view);
+
+        rectView = (FaceRoundView) findViewById(R.id.rect_view);
+        final CameraImageSource cameraImageSource = new CameraImageSource(this);
+        cameraImageSource.setPreviewView(previewView);
+
+        faceDetectManager.setImageSource(cameraImageSource);
+        faceDetectManager.setOnFaceDetectListener(new FaceDetectManager.OnFaceDetectListener() {
+            @Override
+            public void onDetectFace(final int retCode, FaceInfo[] infos, ImageFrame frame) {
+                if (mUploading) {
+                    return;
+                }
+                String str = "";
+                if (retCode == 0) {
+                    if (infos != null && infos[0] != null) {
+                        FaceInfo info = infos[0];
+                        boolean distance = false;
+                        if (info != null && frame != null) {
+                            if (info.mWidth >= (0.9 * frame.getWidth())) {
+                                distance = false;
+                                str = getResources().getString(R.string.detect_zoom_out);
+                            } else if (info.mWidth <= 0.4 * frame.getWidth()) {
+                                distance = false;
+                                str = getResources().getString(R.string.detect_zoom_in);
+                            } else {
+                                distance = true;
+                            }
+                        }
+                        boolean headUpDown;
+                        if (info != null) {
+                            if (info.headPose[0] >= ANGLE) {
+                                headUpDown = false;
+                                str = getResources().getString(R.string.detect_head_up);
+                            } else if (info.headPose[0] <= -ANGLE) {
+                                headUpDown = false;
+                                str = getResources().getString(R.string.detect_head_down);
+                            } else {
+                                headUpDown = true;
+                            }
+
+                            boolean headLeftRight;
+                            if (info.headPose[1] >= ANGLE) {
+                                headLeftRight = false;
+                                str = getResources().getString(R.string.detect_head_left);
+                            } else if (info.headPose[1] <= -ANGLE) {
+                                headLeftRight = false;
+                                str = getResources().getString(R.string.detect_head_right);
+                            } else {
+                                headLeftRight = true;
+                            }
+
+                            if (distance && headUpDown && headLeftRight) {
+                                mGoodDetect = true;
+                            } else {
+                                mGoodDetect = false;
+                            }
+
+                        }
+                    }
+                } else if (retCode == 1) {
+                    str = getResources().getString(R.string.detect_head_up);
+                } else if (retCode == 2) {
+                    str = getResources().getString(R.string.detect_head_down);
+                } else if (retCode == 3) {
+                    str = getResources().getString(R.string.detect_head_left);
+                } else if (retCode == 4) {
+                    str = getResources().getString(R.string.detect_head_right);
+                } else if (retCode == 5) {
+                    str = getResources().getString(R.string.detect_low_light);
+                } else if (retCode == 6) {
+                    str = getResources().getString(R.string.detect_face_in);
+                } else if (retCode == 7) {
+                    str = getResources().getString(R.string.detect_face_in);
+                } else if (retCode == 10) {
+                    str = getResources().getString(R.string.detect_keep);
+                } else if (retCode == 11) {
+                    str = getResources().getString(R.string.detect_occ_right_eye);
+                } else if (retCode == 12) {
+                    str = getResources().getString(R.string.detect_occ_left_eye);
+                } else if (retCode == 13) {
+                    str = getResources().getString(R.string.detect_occ_nose);
+                } else if (retCode == 14) {
+                    str = getResources().getString(R.string.detect_occ_mouth);
+                } else if (retCode == 15) {
+                    str = getResources().getString(R.string.detect_right_contour);
+                } else if (retCode == 16) {
+                    str = getResources().getString(R.string.detect_left_contour);
+                } else if (retCode == 17) {
+                    str = getResources().getString(R.string.detect_chin_contour);
+                }
+
+                boolean faceChanged = true;
+                if (infos != null && infos[0] != null) {
+                    Log.d("DetectLogin", "face id is:" + infos[0].face_id);
+                    if (infos[0].face_id == mCurFaceId) {
+                        faceChanged = false;
+                    } else {
+                        faceChanged = true;
+                    }
+                    mCurFaceId = infos[0].face_id;
+                }
+
+                if (faceChanged) {
+                    showProgressBar(false);
+                }
+
+                final int resultCode = retCode;
+                if (!(mGoodDetect && retCode == 0)) {
+                    if (faceChanged) {
+                        showProgressBar(false);
+                    }
+                }
+
+                mCurTips = str;
+                runOnUiThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        if ((System.currentTimeMillis() - mLastTipsTime) > 1000) {
+                            rectView.onRefreshTipsView(mCurTips);
+                            mLastTipsTime = System.currentTimeMillis();
+                        }
+                        if (mGoodDetect && resultCode == 0) {
+                            rectView.onRefreshTipsView("");
+                            showProgressBar(true);
+                        }
+                    }
+                });
+
+                if (infos == null) {
+                    mGoodDetect = false;
+                }
+
+
+            }
+        });
+        faceDetectManager.setOnTrackListener(new FaceFilter.OnTrackListener() {
+            @Override
+            public void onTrack(FaceFilter.TrackedModel trackedModel) {
+                if (trackedModel.meetCriteria() && mGoodDetect) {
+                    upload(trackedModel);
+                    mGoodDetect = false;
+                }
+            }
+        });
+
+        cameraImageSource.getCameraControl().setPermissionCallback(new PermissionCallback() {
+            @Override
+            public boolean onRequestPermission() {
+                ActivityCompat.requestPermissions(DetectLoginActivity.this,
+                        new String[]{Manifest.permission.CAMERA}, 100);
+                return true;
+            }
+        });
+
+        rectView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
+            @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
+            @Override
+            public void onGlobalLayout() {
+                start();
+                rectView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+            }
+        });
+        ICameraControl control = cameraImageSource.getCameraControl();
+        control.setPreviewView(previewView);
+        // 设置检测裁剪处理器
+        faceDetectManager.addPreProcessor(cropProcessor);
+
+        int orientation = getResources().getConfiguration().orientation;
+        boolean isPortrait = (orientation == Configuration.ORIENTATION_PORTRAIT);
+
+        if (isPortrait) {
+            previewView.setScaleType(PreviewView.ScaleType.FIT_WIDTH);
+        } else {
+            previewView.setScaleType(PreviewView.ScaleType.FIT_HEIGHT);
+        }
+        int rotation = getWindowManager().getDefaultDisplay().getRotation();
+        cameraImageSource.getCameraControl().setDisplayOrientation(rotation);
+        //   previewView.getTextureView().setScaleX(-1);
+        initSDK();
+    }
+
+    private void initWaveview(Rect rect) {
+        RelativeLayout rootView = (RelativeLayout) findViewById(R.id.root_view);
+
+        RelativeLayout.LayoutParams waveParams = new RelativeLayout.LayoutParams(
+                rect.width(), rect.height());
+
+        waveParams.setMargins(rect.left, rect.top, rect.left, rect.top);
+        waveParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT, RelativeLayout.TRUE);
+        waveParams.addRule(RelativeLayout.ALIGN_PARENT_TOP, RelativeLayout.TRUE);
+
+        mWaveview = new WaveView(this);
+        rootView.addView(mWaveview, waveParams);
+
+        // mWaveview = (WaveView) findViewById(R.id.wave);
+        mWaveHelper = new WaveHelper(mWaveview);
+
+        mWaveview.setShapeType(WaveView.ShapeType.CIRCLE);
+        mWaveview.setWaveColor(
+                Color.parseColor("#28FFFFFF"),
+                Color.parseColor("#3cFFFFFF"));
+
+        mBorderColor = Color.parseColor("#28f16d7a");
+        mWaveview.setBorder(mBorderWidth, mBorderColor);
+    }
+
+    private void visibleView() {
+        mInitView.setVisibility(View.INVISIBLE);
+    }
+
+    private void initBrightness() {
+        int brightness = BrightnessTools.getScreenBrightness(DetectLoginActivity.this);
+        if (brightness < 200) {
+            BrightnessTools.setBrightness(this, 200);
+        }
+    }
+
+
+    private void initSDK() {
+        FaceSDKManager.getInstance().getFaceTracker(this).set_min_face_size(200);
+        FaceSDKManager.getInstance().getFaceTracker(this).set_isCheckQuality(true);
+        // 该角度为商学,左右,偏头的角度的阀值,大于将无法检测出人脸,为了在1:n的时候分数高,注册尽量使用比较正的人脸,可自行条件角度
+        FaceSDKManager.getInstance().getFaceTracker(this).set_eulur_angle_thr(15, 15, 15);
+        FaceSDKManager.getInstance().getFaceTracker(this).set_isVerifyLive(true);
+
+        initBrightness();
+    }
+
+
+    private void start() {
+
+        Rect dRect = rectView.getFaceRoundRect();
+
+        //   RectF newDetectedRect = new RectF(detectedRect);
+        int preGap = getResources().getDimensionPixelOffset(R.dimen.preview_margin);
+        int w = getResources().getDimensionPixelOffset(R.dimen.detect_out);
+
+        int orientation = getResources().getConfiguration().orientation;
+        boolean isPortrait = (orientation == Configuration.ORIENTATION_PORTRAIT);
+        if (isPortrait) {
+            // 检测区域矩形宽度
+            int rWidth = mScreenW - 2 * preGap;
+            // 圆框宽度
+            int dRectW = dRect.width();
+            // 检测矩形和圆框偏移
+            int h = (rWidth - dRectW) / 2;
+            //  Log.d("liujinhui hi is:", " h is:" + h + "d is:" + (dRect.left - 150));
+            int rLeft = w;
+            int rRight = rWidth - w;
+            int rTop = dRect.top - h - preGap + w;
+            int rBottom = rTop + rWidth - w;
+
+            //  Log.d("liujinhui", " rLeft is:" + rLeft + "rRight is:" + rRight + "rTop is:" + rTop + "rBottom is:" + rBottom);
+            RectF newDetectedRect = new RectF(rLeft, rTop, rRight, rBottom);
+            cropProcessor.setDetectedRect(newDetectedRect);
+        } else {
+            int rLeft = mScreenW / 2 - mScreenH / 2 + w;
+            int rRight = mScreenW / 2 + mScreenH / 2 + w;
+            int rTop = 0;
+            int rBottom = mScreenH;
+
+            RectF newDetectedRect = new RectF(rLeft, rTop, rRight, rBottom);
+            cropProcessor.setDetectedRect(newDetectedRect);
+        }
+
+
+        faceDetectManager.start();
+        initWaveview(dRect);
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        faceDetectManager.stop();
+        mDetectStoped = true;
+        if (mWaveview != null) {
+            mWaveview.setVisibility(View.GONE);
+            mWaveHelper.cancel();
+        }
+    }
+
+    /**
+     * 参考https://ai.baidu.com/docs#/Face-API/top 人脸识别接口
+     * 无需知道uid,如果同一个人多次注册,可能返回任意一个帐号的uid
+     * 建议上传人脸到自己的服务器,在服务器端调用https://aip.baidubce.com/rest/2.0/face/v3/search,比对分数阀值(如:80分),
+     * 认为登录通过
+     * group_id	是	string	用户组id(由数字、字母、下划线组成),长度限制128B,如果需要查询多个用户组id,用逗号分隔
+     * image	是	string	图像base64编码,每次仅支持单张图片,图片编码后大小不超过10M
+     * <p>
+     * 返回登录认证的参数给客户端
+     *
+     * @param model
+     */
+    private void upload(FaceFilter.TrackedModel model) {
+        if (mUploading) {
+            return;
+        }
+        mUploading = true;
+        if (model.getEvent() != FaceFilter.Event.OnLeave) {
+            mDetectCount++;
+            final Bitmap face = model.cropFace();
+            String base64Url = Base64Util.bitmapToBase64(face);
+            uploadData(base64Url);
+//            try {
+
+
+//                final File file = File.createTempFile(UUID.randomUUID().toString() + "", ".jpg");
+//                ImageUtil.resize(face, file, 200, 200);
+//                ImageSaveUtil.saveCameraBitmap(DetectLoginActivity.this, face, "head_tmp.jpg");
+//                readFile(file);
+//            } catch (IOException e) {
+//                e.printStackTrace();
+//            } catch (ArrayIndexOutOfBoundsException e) {
+//                e.printStackTrace();
+//            }
+        } else {
+            showProgressBar(false);
+            mUploading = false;
+        }
+    }
+
+    public void showProgressBar(final boolean show) {
+        runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                rectView.onRefreshSuccessView(show);
+                if (show) {
+                    if (mWaveview != null) {
+                        mWaveview.setVisibility(View.VISIBLE);
+                        mWaveHelper.start();
+                    }
+                } else {
+                    if (mWaveview != null) {
+                        mWaveview.setVisibility(View.GONE);
+                        mWaveHelper.cancel();
+                    }
+                }
+
+            }
+        });
+    }
+
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        if (mWaveview != null) {
+            mWaveHelper.cancel();
+            mWaveview.setVisibility(View.GONE);
+        }
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        if (mDetectStoped) {
+            faceDetectManager.start();
+            mDetectStoped = false;
+        }
+
+    }
+
+
+    private static class InnerHandler extends Handler {
+        private WeakReference<DetectLoginActivity> mWeakReference;
+
+        public InnerHandler(DetectLoginActivity activity) {
+            super();
+            this.mWeakReference = new WeakReference<DetectLoginActivity>(activity);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            if (mWeakReference == null || mWeakReference.get() == null) {
+                return;
+            }
+            DetectLoginActivity activity = mWeakReference.get();
+            if (activity == null) {
+                return;
+            }
+            if (msg == null) {
+                return;
+
+            }
+            switch (msg.what) {
+                case MSG_INITVIEW:
+                    activity.visibleView();
+                    break;
+                case MSG_DETECTTIME:
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
+
+//    public void showSuccessView(boolean show) {
+//        LogUtil.i("gong", "进来了" + show);
+//        rectView.onRefreshSuccessView(show);
+//    }
+
+    public void reBrushes() {
+        mUploading = false;
+        showProgressBar(false);
+    }
+
+    public abstract void uploadData(String faceBase64);
+}

+ 543 - 0
app_modular/facesdk/src/main/java/com/baidu/aip/excep/activity/FaceDetectActivity.java

@@ -0,0 +1,543 @@
+/*
+ * Copyright (C) 2018 Baidu, Inc. All Rights Reserved.
+ */
+package com.baidu.aip.excep.activity;
+
+
+import android.Manifest;
+import android.annotation.TargetApi;
+import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.support.v4.app.ActivityCompat;
+import android.support.v7.app.AppCompatActivity;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.view.WindowManager;
+import android.widget.RelativeLayout;
+
+import com.baidu.aip.FaceSDKManager;
+import com.baidu.aip.ImageFrame;
+import com.baidu.aip.excep.utils.ImageSaveUtil;
+import com.baidu.aip.excep.widget.BrightnessTools;
+import com.baidu.aip.excep.widget.FaceRoundView;
+import com.baidu.aip.excep.widget.WaveHelper;
+import com.baidu.aip.excep.widget.WaveView;
+import com.baidu.aip.face.CameraImageSource;
+import com.baidu.aip.face.DetectRegionProcessor;
+import com.baidu.aip.face.FaceDetectManager;
+import com.baidu.aip.face.FaceFilter;
+import com.baidu.aip.face.PreviewView;
+import com.baidu.aip.face.camera.ICameraControl;
+import com.baidu.aip.face.camera.PermissionCallback;
+import com.baidu.idl.facesdk.FaceInfo;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.lang.ref.WeakReference;
+
+import demo.face.aip.baidu.com.facesdk.R;
+
+
+/**
+ * 实时检测调用identify进行人脸识别,MainActivity未给出改示例的入口,开发者可以在MainActivity调用
+ * Intent intent = new Intent(MainActivity.this, FaceDetectActivity.class);
+ * startActivity(intent);
+ */
+public class FaceDetectActivity extends AppCompatActivity {
+
+    private static final int MSG_INITVIEW = 1001;
+    private static final int MSG_BEGIN_DETECT = 1002;
+    private PreviewView previewView;
+    private View mInitView;
+    private FaceRoundView rectView;
+    private boolean mGoodDetect = false;
+    private static final double ANGLE = 15;
+    private boolean mDetectStoped = false;
+    private Handler mHandler;
+    private String mCurTips;
+    private boolean mUploading = false;
+    private long mLastTipsTime = 0;
+    private int mCurFaceId = -1;
+
+    private FaceDetectManager faceDetectManager;
+    private DetectRegionProcessor cropProcessor = new DetectRegionProcessor();
+    private WaveHelper mWaveHelper;
+    private WaveView mWaveview;
+    private int mBorderColor = Color.parseColor("#28FFFFFF");
+    private int mBorderWidth = 0;
+    private int mScreenW;
+    private int mScreenH;
+    private boolean mSavedBmp = false;
+    // 开始人脸检测
+    private boolean mBeginDetect = false;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_login_detected);
+        faceDetectManager = new FaceDetectManager(this);
+        initScreen();
+        initView();
+        mHandler = new InnerHandler(this);
+        mHandler.sendEmptyMessageDelayed(MSG_INITVIEW, 500);
+        mHandler.sendEmptyMessageDelayed(MSG_BEGIN_DETECT, 500);
+    }
+
+    private void initScreen() {
+        WindowManager manager = getWindowManager();
+        DisplayMetrics outMetrics = new DisplayMetrics();
+        manager.getDefaultDisplay().getMetrics(outMetrics);
+        mScreenW = outMetrics.widthPixels;
+        mScreenH = outMetrics.heightPixels;
+    }
+
+    private void initView() {
+
+        mInitView = findViewById(R.id.camera_layout);
+        previewView = (PreviewView) findViewById(R.id.preview_view);
+
+        rectView = (FaceRoundView) findViewById(R.id.rect_view);
+        final CameraImageSource cameraImageSource = new CameraImageSource(this);
+        cameraImageSource.setPreviewView(previewView);
+
+        faceDetectManager.setImageSource(cameraImageSource);
+        faceDetectManager.setOnFaceDetectListener(new FaceDetectManager.OnFaceDetectListener() {
+            @Override
+            public void onDetectFace(final int retCode, FaceInfo[] infos, ImageFrame frame) {
+
+
+                if (mUploading) {
+                    //   Log.d("DetectLoginActivity", "is uploading ,not detect time");
+                    return;
+                }
+                //  Log.d("DetectLoginActivity", "retCode is:" + retCode);
+                String str = "";
+                if (retCode == 0) {
+                    if (infos != null && infos[0] != null) {
+                        FaceInfo info = infos[0];
+                        boolean distance = false;
+                        if (info != null && frame != null) {
+                            if (info.mWidth >= (0.9 * frame.getWidth())) {
+                                distance = false;
+                                str = getResources().getString(R.string.detect_zoom_out);
+                            } else if (info.mWidth <= 0.4 * frame.getWidth()) {
+                                distance = false;
+                                str = getResources().getString(R.string.detect_zoom_in);
+                            } else {
+                                distance = true;
+                            }
+                        }
+                        boolean headUpDown;
+                        if (info != null) {
+                            if (info.headPose[0] >= ANGLE) {
+                                headUpDown = false;
+                                str = getResources().getString(R.string.detect_head_up);
+                            } else if (info.headPose[0] <= -ANGLE) {
+                                headUpDown = false;
+                                str = getResources().getString(R.string.detect_head_down);
+                            } else {
+                                headUpDown = true;
+                            }
+
+                            boolean headLeftRight;
+                            if (info.headPose[1] >= ANGLE) {
+                                headLeftRight = false;
+                                str = getResources().getString(R.string.detect_head_left);
+                            } else if (info.headPose[1] <= -ANGLE) {
+                                headLeftRight = false;
+                                str = getResources().getString(R.string.detect_head_right);
+                            } else {
+                                headLeftRight = true;
+                            }
+
+                            if (distance && headUpDown && headLeftRight) {
+                                mGoodDetect = true;
+                            } else {
+                                mGoodDetect = false;
+                            }
+
+                        }
+                    }
+                } else if (retCode == 1) {
+                    str = getResources().getString(R.string.detect_head_up);
+                } else if (retCode == 2) {
+                    str = getResources().getString(R.string.detect_head_down);
+                } else if (retCode == 3) {
+                    str = getResources().getString(R.string.detect_head_left);
+                } else if (retCode == 4) {
+                    str = getResources().getString(R.string.detect_head_right);
+                } else if (retCode == 5) {
+                    str = getResources().getString(R.string.detect_low_light);
+                } else if (retCode == 6) {
+                    str = getResources().getString(R.string.detect_face_in);
+                } else if (retCode == 7) {
+                    str = getResources().getString(R.string.detect_face_in);
+                } else if (retCode == 10) {
+                    str = getResources().getString(R.string.detect_keep);
+                } else if (retCode == 11) {
+                    str = getResources().getString(R.string.detect_occ_right_eye);
+                } else if (retCode == 12) {
+                    str = getResources().getString(R.string.detect_occ_left_eye);
+                } else if (retCode == 13) {
+                    str = getResources().getString(R.string.detect_occ_nose);
+                } else if (retCode == 14) {
+                    str = getResources().getString(R.string.detect_occ_mouth);
+                } else if (retCode == 15) {
+                    str = getResources().getString(R.string.detect_right_contour);
+                } else if (retCode == 16) {
+                    str = getResources().getString(R.string.detect_left_contour);
+                } else if (retCode == 17) {
+                    str = getResources().getString(R.string.detect_chin_contour);
+                }
+
+                boolean faceChanged = true;
+                if (infos != null && infos[0] != null) {
+                    Log.d("DetectLogin", "face id is:" + infos[0].face_id);
+                    if (infos[0].face_id == mCurFaceId) {
+                        faceChanged = false;
+                    } else {
+                        faceChanged = true;
+                    }
+                    mCurFaceId = infos[0].face_id;
+                }
+
+                if (faceChanged) {
+                    showProgressBar(false);
+                    onRefreshSuccessView(false);
+                }
+
+                final int resultCode = retCode;
+                if (!(mGoodDetect && retCode == 0)) {
+                    if (faceChanged) {
+                        showProgressBar(false);
+                        onRefreshSuccessView(false);
+                    }
+                }
+
+
+                mCurTips = str;
+                runOnUiThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        if ((System.currentTimeMillis() - mLastTipsTime) > 1000) {
+                            rectView.onRefreshTipsView(mCurTips);
+                            mLastTipsTime = System.currentTimeMillis();
+                        }
+                        if (mGoodDetect && resultCode == 0) {
+                            rectView.onRefreshTipsView("");
+                            onRefreshSuccessView(true);
+                            showProgressBar(true);
+                        }
+                    }
+                });
+
+                if (infos == null) {
+                    mGoodDetect = false;
+                }
+
+
+            }
+        });
+        faceDetectManager.setOnTrackListener(new FaceFilter.OnTrackListener() {
+            @Override
+            public void onTrack(FaceFilter.TrackedModel trackedModel) {
+                if (trackedModel.meetCriteria() && mGoodDetect) {
+                    // upload(trackedModel);
+                    mGoodDetect = false;
+                    if (!mSavedBmp && mBeginDetect) {
+                        if (saveFaceBmp(trackedModel)) {
+                            setResult(RESULT_OK);
+                            finish();
+                        }
+                    }
+                }
+            }
+        });
+
+        cameraImageSource.getCameraControl().setPermissionCallback(new PermissionCallback() {
+            @Override
+            public boolean onRequestPermission() {
+                ActivityCompat.requestPermissions(FaceDetectActivity.this,
+                        new String[]{Manifest.permission.CAMERA}, 100);
+                return true;
+            }
+        });
+
+        rectView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
+            @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+            @Override
+            public void onGlobalLayout() {
+                start();
+                rectView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+            }
+        });
+        ICameraControl control = cameraImageSource.getCameraControl();
+        control.setPreviewView(previewView);
+        // 设置检测裁剪处理器
+        faceDetectManager.addPreProcessor(cropProcessor);
+
+        int orientation = getResources().getConfiguration().orientation;
+        boolean isPortrait = (orientation == Configuration.ORIENTATION_PORTRAIT);
+
+        if (isPortrait) {
+            previewView.setScaleType(PreviewView.ScaleType.FIT_WIDTH);
+        } else {
+            previewView.setScaleType(PreviewView.ScaleType.FIT_HEIGHT);
+        }
+        int rotation = getWindowManager().getDefaultDisplay().getRotation();
+        cameraImageSource.getCameraControl().setDisplayOrientation(rotation);
+        //   previewView.getTextureView().setScaleX(-1);
+
+        // mProgress = (ProgressBar) findViewById(R.id.progress_bar);
+        init();
+    }
+
+    private void initWaveview(Rect rect) {
+        RelativeLayout rootView = (RelativeLayout) findViewById(R.id.root_view);
+
+        RelativeLayout.LayoutParams waveParams = new RelativeLayout.LayoutParams(
+                rect.width(), rect.height());
+
+        waveParams.setMargins(rect.left, rect.top, rect.left, rect.top);
+        waveParams.addRule(RelativeLayout.ALIGN_PARENT_LEFT, RelativeLayout.TRUE);
+        waveParams.addRule(RelativeLayout.ALIGN_PARENT_TOP, RelativeLayout.TRUE);
+
+        mWaveview = new WaveView(this);
+        rootView.addView(mWaveview, waveParams);
+
+        // mWaveview = (WaveView) findViewById(R.id.wave);
+        mWaveHelper = new WaveHelper(mWaveview);
+
+        mWaveview.setShapeType(WaveView.ShapeType.CIRCLE);
+        mWaveview.setWaveColor(
+                Color.parseColor("#28FFFFFF"),
+                Color.parseColor("#3cFFFFFF"));
+
+//        mWaveview.setWaveColor(
+//                Color.parseColor("#28f16d7a"),
+//                Color.parseColor("#3cf16d7a"));
+
+        mBorderColor = Color.parseColor("#28f16d7a");
+        mWaveview.setBorder(mBorderWidth, mBorderColor);
+    }
+
+    private void visibleView() {
+        mInitView.setVisibility(View.INVISIBLE);
+    }
+
+    private boolean saveFaceBmp(FaceFilter.TrackedModel model) {
+
+        final Bitmap face = model.cropFace();
+        if (face != null) {
+            Log.d("save", "save bmp");
+            ImageSaveUtil.saveCameraBitmap(FaceDetectActivity.this, face, "head_tmp.jpg");
+        }
+        String filePath = ImageSaveUtil.loadCameraBitmapPath(this, "head_tmp.jpg");
+        final File file = new File(filePath);
+        if (!file.exists()) {
+            return false;
+        }
+        boolean saved = false;
+        try {
+            byte[] buf = readFile(file);
+            if (buf.length > 0) {
+                saved = true;
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        if (!saved) {
+            Log.d("fileSize", "file size >=-99");
+        } else {
+            mSavedBmp = true;
+        }
+        return saved;
+    }
+
+    private void initBrightness() {
+        int brightness = BrightnessTools.getScreenBrightness(FaceDetectActivity.this);
+        if (brightness < 200) {
+            BrightnessTools.setBrightness(this, 200);
+        }
+    }
+
+
+    private void init() {
+
+        FaceSDKManager.getInstance().getFaceTracker(this).set_min_face_size(200);
+        FaceSDKManager.getInstance().getFaceTracker(this).set_isCheckQuality(true);
+        // 该角度为商学,左右,偏头的角度的阀值,大于将无法检测出人脸,为了在1:n的时候分数高,注册尽量使用比较正的人脸,可自行条件角度
+        FaceSDKManager.getInstance().getFaceTracker(this).set_eulur_angle_thr(15, 15, 15);
+        FaceSDKManager.getInstance().getFaceTracker(this).set_isVerifyLive(true);
+        FaceSDKManager.getInstance().getFaceTracker(this).set_notFace_thr(0.2f);
+        FaceSDKManager.getInstance().getFaceTracker(this).set_occlu_thr(0.1f);
+
+        initBrightness();
+    }
+
+    private void start() {
+
+        Rect dRect = rectView.getFaceRoundRect();
+
+        //   RectF newDetectedRect = new RectF(detectedRect);
+        int preGap = getResources().getDimensionPixelOffset(R.dimen.preview_margin);
+        int w = getResources().getDimensionPixelOffset(R.dimen.detect_out);
+
+        int orientation = getResources().getConfiguration().orientation;
+        boolean isPortrait = (orientation == Configuration.ORIENTATION_PORTRAIT);
+        if (isPortrait) {
+            // 检测区域矩形宽度
+            int rWidth = mScreenW - 2 * preGap;
+            // 圆框宽度
+            int dRectW = dRect.width();
+            // 检测矩形和圆框偏移
+            int h = (rWidth - dRectW) / 2;
+            //  Log.d("liujinhui hi is:", " h is:" + h + "d is:" + (dRect.left - 150));
+            int rLeft = w;
+            int rRight = rWidth - w;
+            int rTop = dRect.top - h - preGap + w;
+            int rBottom = rTop + rWidth - w;
+
+            //  Log.d("liujinhui", " rLeft is:" + rLeft + "rRight is:" + rRight + "rTop is:" + rTop + "rBottom is:" + rBottom);
+            RectF newDetectedRect = new RectF(rLeft, rTop, rRight, rBottom);
+            cropProcessor.setDetectedRect(newDetectedRect);
+        } else {
+            int rLeft = mScreenW / 2 - mScreenH / 2 + w;
+            int rRight = mScreenW / 2 + mScreenH / 2 + w;
+            int rTop = 0;
+            int rBottom = mScreenH;
+
+            RectF newDetectedRect = new RectF(rLeft, rTop, rRight, rBottom);
+            cropProcessor.setDetectedRect(newDetectedRect);
+        }
+
+
+        faceDetectManager.start();
+        initWaveview(dRect);
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        faceDetectManager.stop();
+        mDetectStoped = true;
+        onRefreshSuccessView(false);
+        if (mWaveview != null) {
+            mWaveview.setVisibility(View.GONE);
+            mWaveHelper.cancel();
+        }
+    }
+
+    private void showProgressBar(final boolean show) {
+        runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                if (show) {
+                    if (mWaveview != null) {
+                        mWaveview.setVisibility(View.VISIBLE);
+                        mWaveHelper.start();
+                    }
+                } else {
+                    if (mWaveview != null) {
+                        mWaveview.setVisibility(View.GONE);
+                        mWaveHelper.cancel();
+                    }
+                }
+
+            }
+        });
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        if (mWaveview != null) {
+            mWaveHelper.cancel();
+            mWaveview.setVisibility(View.GONE);
+        }
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        if (mDetectStoped) {
+            faceDetectManager.start();
+            mDetectStoped = false;
+        }
+
+    }
+
+    private void onRefreshSuccessView(final boolean isShow) {
+
+        runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                rectView.onRefreshSuccessView(isShow);
+            }
+        });
+    }
+
+    private static class InnerHandler extends Handler {
+        private WeakReference<FaceDetectActivity> mWeakReference;
+
+        public InnerHandler(FaceDetectActivity activity) {
+            super();
+            this.mWeakReference = new WeakReference<FaceDetectActivity>(activity);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            if (mWeakReference == null || mWeakReference.get() == null) {
+                return;
+            }
+            FaceDetectActivity activity = mWeakReference.get();
+            if (activity == null) {
+                return;
+            }
+            if (msg == null) {
+                return;
+
+            }
+            switch (msg.what) {
+                case MSG_INITVIEW:
+                    activity.visibleView();
+                    break;
+                case MSG_BEGIN_DETECT:
+                    activity.mBeginDetect = true;
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
+    public static byte[] readFile(File file) throws IOException {
+        // Open file
+
+        RandomAccessFile f = new RandomAccessFile(file, "r");
+        try {
+            // Get and check length
+            long longlength = f.length();
+            int length = (int) longlength;
+
+            if (length != longlength) {
+                throw new IOException("File size >= 2 GB");
+            }
+            // Read file and return data
+            byte[] data = new byte[length];
+            f.readFully(data);
+            return data;
+        } finally {
+            f.close();
+        }
+    }
+}

+ 257 - 0
app_modular/facesdk/src/main/java/com/baidu/aip/excep/activity/RealTimeDetectFaceActivty.java

@@ -0,0 +1,257 @@
+package com.baidu.aip.excep.activity;
+
+import android.os.Looper;
+import android.text.TextUtils;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.baidu.aip.excep.model.FaceVerify;
+import com.baidu.aip.excep.utils.FaceConfig;
+import com.common.LogUtil;
+import com.common.data.JSONUtil;
+import com.common.data.ListUtils;
+import com.core.app.MyApplication;
+import com.core.model.User;
+import com.core.utils.CommonUtil;
+import com.me.network.app.http.HttpClient;
+import com.me.network.app.http.Method;
+import com.me.network.app.http.rx.Result2Listener;
+import com.me.network.app.http.rx.ResultSubscriber;
+import com.modular.apputils.widget.VeriftyDialog;
+
+public class RealTimeDetectFaceActivty extends DetectLoginActivity {
+    private final int MAX_DETECT_COUNT = 2;
+
+    @Override
+    protected String getBaseUrl() {
+        return "https://aip.baidubce.com/";
+    }
+
+    @Override
+    public void uploadData(final String faceBase64) {
+        final String master = CommonUtil.getEnuu(ct);
+        final String imid = MyApplication.getInstance().getLoginUserId();
+        runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                verify(faceBase64, master, imid);
+            }
+        });
+    }
+
+
+    private void showRegisterDialog(final String faceBase64, final String group_id_list, final String user_id) {
+        new VeriftyDialog.Builder(this)
+                .setCanceledOnTouchOutside(false)
+                .setContent("您未录入人脸照片,点击\"确认\"录入系统")
+                .build(new VeriftyDialog.OnDialogClickListener() {
+                    @Override
+                    public void result(boolean clickSure) {
+                        if (clickSure) {
+                            String company = CommonUtil.getSharedPreferences(ct, "erp_commpany");
+                            String master = CommonUtil.getSharedPreferences(ct, "Master_ch");
+                            String name = CommonUtil.getName();
+                            User user = MyApplication.getInstance().mLoginUser;
+                            String phone = null;
+                            if (user != null) {
+                                phone = user.getTelephone();
+                            }
+                            String userInfo = company + "_" + master + "_" + phone + "_" + name;//公司名_帐套名_电话_姓名
+                            register(faceBase64, group_id_list, user_id, userInfo);
+                        } else {
+                            setResult(0);
+                            finish();
+                        }
+                    }
+                });
+
+    }
+
+    //验证和注册错误
+    private void showErrorDialog(String result) {
+        String message = "";
+        if (result.contains("liveness check fail")) {
+            message = "不能拿照片骗我哦";
+        } else {
+            message = result;
+        }
+        mUploading = true;
+        new VeriftyDialog.Builder(this)
+                .setCanceledOnTouchOutside(false)
+                .setContent(message)
+                .setSureText("再试一次")
+                .setShowCancel(true)
+                .build(new VeriftyDialog.OnDialogClickListener() {
+                    @Override
+                    public void result(boolean clickSure) {
+                        if (clickSure) {
+                            reBrushes();
+                        } else {
+                            finish();
+                        }
+                    }
+                });
+    }
+
+    /**
+     * 校验身份
+     *
+     * @param faceBase64    脸部信息
+     * @param group_id_list 组Id
+     * @param user_id       用户Id
+     */
+    private void verify(final String faceBase64, final String group_id_list, final String user_id) {
+        showProgress();
+        LogUtil.i("gong", "isMain" + (Looper.getMainLooper() == Looper.myLooper()));
+        mUploading = true;
+        FaceConfig.loadToken(new FaceConfig.FaceTokenListener() {
+            @Override
+            public void callBack(String accessToken) {
+                LogUtil.i("gong", "accessToken=" + accessToken);
+                httpClient.Api().send(new HttpClient.Builder()
+                        .url("rest/2.0/face/v3/search")
+                        .add("access_token", accessToken)
+                        .header("Content-Type", "application/json")
+                        .add("image", faceBase64)
+                        .add("image_type", "BASE64")
+                        .add("liveness_control", "NORMAL")
+                        .add("user_id", user_id)
+                        .isDebug(true)
+                        .add("group_id_list", group_id_list)
+                        .method(Method.POST).build(), new ResultSubscriber<>(new Result2Listener<Object>() {
+                    @Override
+                    public void onResponse(Object o) {
+
+                        try {
+                            handleDetectResult(o.toString(), faceBase64, group_id_list, user_id);
+                        } catch (Exception e) {
+                            e.printStackTrace();
+                        }
+                        dismissProgress();
+                    }
+
+                    @Override
+                    public void onFailure(Object t) {
+                        LogUtil.i("gong", "onFailure=" + t.toString());
+                        dismissProgress();
+                        detectError();
+                    }
+                }));
+            }
+        });
+    }
+
+    private void register(final String faceBase64, final String group_id, final String user_id, final String user_info) {
+        showProgress();
+        mUploading = true;
+        FaceConfig.loadToken(new FaceConfig.FaceTokenListener() {
+            @Override
+            public void callBack(String accessToken) {
+                LogUtil.i("gong", "accessToken=" + accessToken);
+                httpClient.Api().send(new HttpClient.Builder()
+                        .url("rest/2.0/face/v3/faceset/user/add")
+                        .add("access_token", accessToken)
+                        .header("Content-Type", "application/json")
+                        .add("image", faceBase64)
+                        .add("image_type", "BASE64")
+                        .add("quality_control", "NORMAL")
+                        .add("liveness_control", "NORMAL")
+                        .add("user_id", user_id)
+                        .add("user_info", user_info)
+                        .isDebug(true)
+                        .add("group_id", group_id)
+                        .method(Method.POST).build(), new ResultSubscriber<>(new Result2Listener<Object>() {
+                    @Override
+                    public void onResponse(Object o) {
+                        LogUtil.i("gong", "verify onResponse=" + o.toString());
+                        try {
+                            JSONObject object = JSON.parseObject(o.toString());
+                            String error_msg = JSONUtil.getText(object, "error_msg");
+                            if (TextUtils.isEmpty(error_msg) || error_msg.equals("SUCCESS")) {
+                                setResult(RESULT_OK);
+                                finish();
+                            } else {
+                                showErrorDialog(error_msg);
+                                return;
+                            }
+                        } catch (Exception e) {
+                        }
+                        reBrushes();
+                        dismissProgress();
+                    }
+
+                    @Override
+                    public void onFailure(Object t) {
+                        dismissProgress();
+                        LogUtil.i("gong", "verify onFailure=" + t.toString());
+                    }
+                }));
+            }
+        });
+    }
+
+    /**
+     * 处理人脸扫描
+     *
+     * @param messgae
+     * @throws Exception
+     */
+    private void handleDetectResult(String messgae, String faceBase64, final String group_id_list, final String user_id) throws Exception {
+        LogUtil.i("gong", "onResponse=" + messgae);
+        JSONObject object = JSON.parseObject(messgae);
+        String error_msg = JSONUtil.getText(object, "error_msg");
+        if (TextUtils.isEmpty(error_msg) || error_msg.equals("SUCCESS")) {
+            JSONObject result = JSONUtil.getJSONObject(object, "result");
+            JSONArray user_list = JSONUtil.getJSONArray(result, "user_list");
+            if (ListUtils.isEmpty(user_list)) {
+                //验证成功,但是没有注册时候
+                showRegisterDialog(faceBase64, group_id_list, user_id);
+            } else {
+                FaceVerify mFaceVerify = new FaceVerify();
+                for (int i = 0; i < user_list.size(); i++) {
+                    JSONObject userObject = user_list.getJSONObject(i);
+                    float score = JSONUtil.getFloat(userObject, "score");
+                    if (mFaceVerify.getScore() < score) {
+                        mFaceVerify.setScore(score);
+                        mFaceVerify.setUserId(JSONUtil.getText(userObject, "user_id"));
+                        mFaceVerify.setUserInfo(JSONUtil.getText(userObject, "user_info"));
+                        mFaceVerify.setGroupId(JSONUtil.getText(userObject, "group_id"));
+                        if (mFaceVerify.isPass()) {
+                            break;
+                        }
+                    }
+                }
+                if (mFaceVerify.isPass()) {
+                    okAndEnd();
+                } else {
+                    detectError();
+                }
+            }
+        } else {
+            detectError();
+        }
+    }
+
+    /**
+     * 操作成功并退出
+     */
+    public void okAndEnd() {
+        setResult(RESULT_OK);
+        finish();
+    }
+
+    /**
+     * 人脸识别失败
+     */
+    public void detectError() {
+        if (mDetectCount >= MAX_DETECT_COUNT) {
+            showErrorDialog("抱歉,没认出你哦");
+            mDetectCount = 0;
+        } else {
+            reBrushes();
+        }
+    }
+
+
+}

+ 136 - 0
app_modular/facesdk/src/main/java/com/baidu/aip/excep/utils/ExifUtil.java

@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2017 Baidu, Inc. All Rights Reserved.
+ */
+package com.baidu.aip.excep.utils;
+
+import android.media.ExifInterface;
+import android.util.Log;
+
+public class ExifUtil {
+    private static final String TAG = "CameraExif";
+
+    public static int exifToDegrees(int exifOrientation) {
+        if (exifOrientation == ExifInterface.ORIENTATION_ROTATE_90) {
+            return 90;
+        } else if (exifOrientation == ExifInterface.ORIENTATION_ROTATE_180) {
+            return 180;
+        } else if (exifOrientation == ExifInterface.ORIENTATION_ROTATE_270) {
+            return 270;
+        }
+        return 0;
+    }
+
+    // Returns the degrees in clockwise. Values are 0, 90, 180, or 270.
+    public static int getOrientation(byte[] jpeg) {
+        if (jpeg == null) {
+            return 0;
+        }
+
+        int offset = 0;
+        int length = 0;
+
+        // ISO/IEC 10918-1:1993(E)
+        while (offset + 3 < jpeg.length && (jpeg[offset++] & 0xFF) == 0xFF) {
+            int marker = jpeg[offset] & 0xFF;
+
+            // Check if the marker is a padding.
+            if (marker == 0xFF) {
+                continue;
+            }
+            offset++;
+
+            // Check if the marker is SOI or TEM.
+            if (marker == 0xD8 || marker == 0x01) {
+                continue;
+            }
+            // Check if the marker is EOI or SOS.
+            if (marker == 0xD9 || marker == 0xDA) {
+                break;
+            }
+
+            // Get the length and check if it is reasonable.
+            length = pack(jpeg, offset, 2, false);
+            if (length < 2 || offset + length > jpeg.length) {
+                Log.e(TAG, "Invalid length");
+                return 0;
+            }
+
+            // Break if the marker is EXIF in APP1.
+            if (marker == 0xE1 && length >= 8
+                    && pack(jpeg, offset + 2, 4, false) == 0x45786966
+                    && pack(jpeg, offset + 6, 2, false) == 0) {
+                offset += 8;
+                length -= 8;
+                break;
+            }
+
+            // Skip other markers.
+            offset += length;
+            length = 0;
+        }
+
+        // JEITA CP-3451 Exif Version 2.2
+        if (length > 8) {
+            // Identify the byte order.
+            int tag = pack(jpeg, offset, 4, false);
+            if (tag != 0x49492A00 && tag != 0x4D4D002A) {
+                Log.e(TAG, "Invalid byte order");
+                return 0;
+            }
+            boolean littleEndian = (tag == 0x49492A00);
+
+            // Get the offset and check if it is reasonable.
+            int count = pack(jpeg, offset + 4, 4, littleEndian) + 2;
+            if (count < 10 || count > length) {
+                Log.e(TAG, "Invalid offset");
+                return 0;
+            }
+            offset += count;
+            length -= count;
+
+            // Get the count and go through all the elements.
+            count = pack(jpeg, offset - 2, 2, littleEndian);
+            while (count-- > 0 && length >= 12) {
+                // Get the tag and check if it is orientation.
+                tag = pack(jpeg, offset, 2, littleEndian);
+                if (tag == 0x0112) {
+                    // We do not really care about type and count, do we?
+                    int orientation = pack(jpeg, offset + 8, 2, littleEndian);
+                    switch (orientation) {
+                        case 1:
+                            return 0;
+                        case 3:
+                            return 180;
+                        case 6:
+                            return 90;
+                        case 8:
+                            return 270;
+                        default:
+                            return 0;
+                    }
+                }
+                offset += 12;
+                length -= 12;
+            }
+        }
+
+        Log.i(TAG, "Orientation not found");
+        return 0;
+    }
+
+    private static int pack(byte[] bytes, int offset, int length,
+                            boolean littleEndian) {
+        int step = 1;
+        if (littleEndian) {
+            offset += length - 1;
+            step = -1;
+        }
+
+        int value = 0;
+        while (length-- > 0) {
+            value = (value << 8) | (bytes[offset] & 0xFF);
+            offset += step;
+        }
+        return value;
+    }
+}

+ 145 - 0
app_modular/facesdk/src/main/java/com/baidu/aip/excep/utils/ImageSaveUtil.java

@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2017 Baidu, Inc. All Rights Reserved.
+ */
+package com.baidu.aip.excep.utils;
+
+
+import android.app.ActivityManager;
+import android.app.ActivityManager.MemoryInfo;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+
+public class ImageSaveUtil {
+
+   // public static String FACE_PATH = "/sdcard/facesdk/";
+
+    public static String saveCameraBitmap(Context context, Bitmap image, String filename) {
+        if (image == null) {
+            return "";
+        }
+        String fullPath = "";
+        FileOutputStream fileOS = null;
+        try {
+            if (isMemeryOk(context)) {
+                File file = new File(context.getFilesDir(), filename);
+               // if (file.exists()) {
+                //    file.delete();
+              //  }
+                fileOS = new FileOutputStream(file);
+                Uri.fromFile(file);
+                image.compress(Bitmap.CompressFormat.JPEG, 100, fileOS);
+                fullPath = file.getAbsolutePath();
+            } else {
+                fullPath = "";
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+        } finally {
+            if (fileOS != null) {
+                try {
+                    fileOS.close();
+                } catch (IOException e) {
+                    // TODO Auto-generated catch block
+                    e.printStackTrace();
+                }
+            }
+        }
+        return fullPath;
+    }
+
+    public static String loadCameraBitmapPath(Context context, String filename) {
+        String filePath = context.getFilesDir() + File.separator + filename;
+        File path = new File(filePath);
+        if (!path.exists()) {
+            filePath = "";
+        }
+        return filePath;
+    }
+
+    public static Bitmap loadCameraBitmap(Context context, String filename) {
+        Bitmap image = null;
+        String filePath = context.getFilesDir() + File.separator + filename;
+        File path = new File(filePath);
+        if (!path.exists()) {
+            filePath = "";
+        }
+
+        File file = new File(filePath);
+        InputStream is = null;
+        try {
+            is = new FileInputStream(file);
+            BitmapFactory.Options opts = new BitmapFactory.Options();
+            opts.inPreferredConfig = Bitmap.Config.RGB_565;
+            image = BitmapFactory.decodeStream(is, null, opts);
+
+        } catch (java.io.FileNotFoundException e) {
+            e.printStackTrace();
+        } catch (OutOfMemoryError e) {
+            e.printStackTrace();
+        } catch (Exception e) {
+            e.printStackTrace();
+        } finally {
+            if (is != null) {
+                try {
+                    is.close();
+                } catch (java.io.IOException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+        return image;
+    }
+
+    public static Bitmap loadBitmapFromPath(Context context, String filePath) {
+        Bitmap image = null;
+        File path = new File(filePath);
+        if (!path.exists()) {
+            return null;
+        }
+
+        InputStream is = null;
+        try {
+            is = new FileInputStream(path);
+            BitmapFactory.Options opts = new BitmapFactory.Options();
+            opts.inPreferredConfig = Bitmap.Config.RGB_565;
+            image = BitmapFactory.decodeStream(is, null, opts);
+
+        } catch (java.io.FileNotFoundException e) {
+            e.printStackTrace();
+        } catch (OutOfMemoryError e) {
+            e.printStackTrace();
+        } catch (Exception e) {
+            e.printStackTrace();
+        } finally {
+            if (is != null) {
+                try {
+                    is.close();
+                } catch (java.io.IOException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+        return image;
+    }
+
+    public static boolean isMemeryOk(Context context) { // 获取android当前可用内存大小
+
+        ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+        MemoryInfo mi = new MemoryInfo();
+        am.getMemoryInfo(mi);
+        // mi.availMem; 当前系统的可用内存
+        long total = mi.availMem;
+
+        return total > 9999 ? true : false;
+    }
+
+}

+ 278 - 0
app_modular/facesdk/src/main/java/com/baidu/aip/excep/utils/ImageUtil.java

@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) 2017 Baidu, Inc. All Rights Reserved.
+ */
+package com.baidu.aip.excep.utils;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.media.ExifInterface;
+import android.text.TextUtils;
+import android.util.Base64;
+
+public class ImageUtil {
+
+    public static void resize(Bitmap bitmap, File outputFile, int maxWidth, int maxHeight) {
+        try {
+            int bitmapWidth = bitmap.getWidth();
+            int bitmapHeight = bitmap.getHeight();
+            // 图片大于最大高宽,按大的值缩放
+            if (bitmapWidth > maxHeight || bitmapHeight > maxWidth) {
+                float widthScale = maxWidth * 1.0f / bitmapWidth;
+                float heightScale = maxHeight * 1.0f / bitmapHeight;
+
+                float scale = Math.min(widthScale, heightScale);
+                Matrix matrix = new Matrix();
+                matrix.postScale(scale, scale);
+                bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmapWidth, bitmapHeight, matrix, false);
+            }
+            // save image
+            FileOutputStream out = new FileOutputStream(outputFile);
+            try {
+                bitmap.compress(Bitmap.CompressFormat.JPEG, 80, out);
+            } catch (Exception e) {
+                e.printStackTrace();
+            } finally {
+                try {
+                    out.close();
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public static void resize(String inputPath, String outputPath, int dstWidth, int dstHeight) {
+        try {
+            int inWidth;
+            int inHeight;
+
+            // decode image size (decode metadata only, not the whole image)
+            BitmapFactory.Options options = new BitmapFactory.Options();
+            options.inJustDecodeBounds = true;
+            BitmapFactory.decodeFile(inputPath, options);
+
+            // save width and height
+            inWidth = options.outWidth;
+            inHeight = options.outHeight;
+            Matrix m = new Matrix();
+            ExifInterface exif = new ExifInterface(inputPath);
+            int rotation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
+            if (rotation != 0) {
+                m.preRotate(ExifUtil.exifToDegrees(rotation));
+            }
+
+            int maxPreviewImageSize = Math.max(dstWidth, dstHeight);
+            int size = Math.min(options.outWidth, options.outHeight);
+            size = Math.min(size, maxPreviewImageSize);
+
+            options = new BitmapFactory.Options();
+            options.inSampleSize = ImageUtil.calculateInSampleSize(options, size, size);
+            options.inScaled = true;
+            options.inDensity = options.outWidth;
+            options.inTargetDensity = size * options.inSampleSize;
+
+            Bitmap roughBitmap = BitmapFactory.decodeFile(inputPath, options);
+            roughBitmap = Bitmap.createBitmap(roughBitmap, 0, 0, roughBitmap.getWidth(),
+                    roughBitmap.getHeight(), m, false);
+            // save image
+            FileOutputStream out = new FileOutputStream(outputPath);
+            try {
+                roughBitmap.compress(Bitmap.CompressFormat.JPEG, 80, out);
+            } catch (Exception e) {
+                e.printStackTrace();
+            } finally {
+                try {
+                    out.close();
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public static int calculateInSampleSize(
+            BitmapFactory.Options options, int reqWidth, int reqHeight) {
+        // Raw height and width of image
+        final int height = options.outHeight;
+        final int width = options.outWidth;
+        int inSampleSize = 1;
+
+        if (height > reqHeight || width > reqWidth) {
+
+            final int halfHeight = height / 2;
+            final int halfWidth = width / 2;
+            // Calculate the largest inSampleSize value that is a power of 2 and keeps both
+            // height and width larger than the requested height and width.
+            while ((halfHeight / inSampleSize) >= reqHeight
+                    && (halfWidth / inSampleSize) >= reqWidth) {
+                inSampleSize *= 2;
+            }
+        }
+
+        return inSampleSize;
+    }
+
+    public static void saveBitmap(String outputPath, Bitmap bitmap) {
+        // save image
+        FileOutputStream out = null;
+        try {
+            out = new FileOutputStream(outputPath);
+            bitmap.compress(Bitmap.CompressFormat.JPEG, 100, out);
+        } catch (Exception e) {
+            e.printStackTrace();
+        } finally {
+            try {
+                if (out != null) {
+                    out.close();
+                }
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    /**
+     * @param context
+     * @param bitmap
+     * @return
+     */
+    public static Bitmap loadZoomBitmap(Context context, Bitmap bitmap) {
+        if (bitmap == null) {
+            return null;
+        }
+        int bmpHeight = bitmap.getHeight();
+        int bmpWidth = bitmap.getWidth();
+        int newHeight = 0;
+        int zoom = 1;
+        if (bmpWidth > 200) {
+            zoom = bmpWidth / 200;
+            newHeight = bmpHeight / zoom;
+        } else {
+            zoom = 1;
+            newHeight = bmpHeight;
+        }
+        if (zoom <= 1) {
+            return bitmap;
+        }
+        Bitmap bm = Bitmap.createScaledBitmap(bitmap, 200, newHeight, true);
+        if (bitmap != null) {
+            bitmap.recycle();
+        }
+        return bm;
+    }
+
+    public static Bitmap base642bitmap(String image) {
+        if (TextUtils.isEmpty(image)) {
+            return null;
+        }
+        byte[] bytes = Base64.decode(image.getBytes(), Base64.DEFAULT);
+
+        Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
+
+        return bitmap;
+    }
+
+    /**
+     * 转换图片成圆形
+     *
+     * @param bitmap 传入Bitmap对象
+     * @return
+     */
+    public static Bitmap toRoundBitmap(Bitmap bitmap) {
+        int width = bitmap.getWidth();
+        int height = bitmap.getHeight();
+        float roundPx;
+        float left;
+        float top;
+        float right;
+        float bottom;
+        float dstLeft;
+        float dstTop;
+        float dstRight;
+        float dstBottom;
+        if (width <= height) {
+            roundPx = width / 2;
+            top = 0;
+            bottom = width;
+            left = 0;
+            right = width;
+            height = width;
+            dstLeft = 0;
+            dstTop = 0;
+            dstRight = width;
+            dstBottom = width;
+        } else {
+            roundPx = height / 2;
+            float clip = (width - height) / 2;
+            left = clip;
+            right = width - clip;
+            top = 0;
+            bottom = height;
+            width = height;
+            dstLeft = 0;
+            dstTop = 0;
+            dstRight = height;
+            dstBottom = height;
+        }
+
+        Bitmap output = Bitmap.createBitmap(width,
+                height, Config.ARGB_8888);
+        Canvas canvas = new Canvas(output);
+
+        final int color = 0xff424242;
+        final Paint paint = new Paint();
+        final Rect src = new Rect((int) left, (int) top, (int) right, (int) bottom);
+        final Rect dst = new Rect((int) dstLeft, (int) dstTop, (int) dstRight, (int) dstBottom);
+        final RectF rectF = new RectF(dst);
+
+        paint.setAntiAlias(true);
+
+        canvas.drawARGB(0, 0, 0, 0);
+        paint.setColor(color);
+        canvas.drawRoundRect(rectF, roundPx, roundPx, paint);
+
+
+        paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
+        canvas.drawBitmap(bitmap, src, dst, paint);
+        return output;
+    }
+
+
+//    public static Bitmap getBase64Image(HashMap<String, String> imageMap) {
+//
+//        Set<Map.Entry<String, String>> sets = imageMap.entrySet();
+//        Bitmap bmp = null;
+//        for (Map.Entry<String, String> entry : sets) {
+//            bmp = base64ToBitmap(entry.getValue());
+//        }
+//        return bmp;
+//    }
+
+    public static Bitmap cropFaceImg(Bitmap bitmap, int needW) {
+        Bitmap face = null;
+        int x = bitmap.getWidth();
+        int y = bitmap.getHeight();
+        int left = (x - needW) / 2;
+        int top = (y - needW) / 2;
+        face = Bitmap.createBitmap(bitmap, left, top, needW, needW);
+
+        return face;
+    }
+}

+ 123 - 0
app_modular/facesdk/src/main/java/com/baidu/aip/excep/widget/BrightnessTools.java

@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2017 Baidu, Inc. All Rights Reserved.
+ */
+package com.baidu.aip.excep.widget;
+
+
+import android.app.Activity;
+import android.content.ContentResolver;
+import android.net.Uri;
+import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
+import android.util.Log;
+import android.view.WindowManager;
+
+
+public class BrightnessTools {
+
+    /**
+     * 判断是否开启了自动亮度调节
+     */
+
+    public static boolean isAutoBrightness(ContentResolver aContentResolver) {
+        boolean automicBrightness = false;
+        try {
+            automicBrightness = Settings.System.getInt(aContentResolver,
+                    Settings.System.SCREEN_BRIGHTNESS_MODE) == Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC;
+        } catch (SettingNotFoundException e) {
+            e.printStackTrace();
+        }
+        return automicBrightness;
+    }
+
+    /**
+     * 获取屏幕的亮度
+     *
+     * @param activity
+     * @return
+     */
+    public static int getScreenBrightness(Activity activity) {
+        int nowBrightnessValue = 0;
+        ContentResolver resolver = activity.getContentResolver();
+        try {
+            nowBrightnessValue = Settings.System.getInt(
+                    resolver, Settings.System.SCREEN_BRIGHTNESS);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return nowBrightnessValue;
+    }
+
+    /**
+     * 设置亮度
+     *
+     * @param activity
+     * @param brightness
+     */
+    public static void setBrightness(Activity activity, int brightness) {
+        // Settings.System.putInt(activity.getContentResolver(),
+
+        // Settings.System.SCREEN_BRIGHTNESS_MODE,
+
+        // Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
+
+        WindowManager.LayoutParams lp = activity.getWindow().getAttributes();
+
+        lp.screenBrightness = Float.valueOf(brightness) * (1f / 255f);
+        Log.d("lxy", "set  lp.screenBrightness == " + lp.screenBrightness);
+
+        activity.getWindow().setAttributes(lp);
+    }
+
+    // 那么,能设置了,但是为什么还是会出现,设置了,没反映呢?
+
+    // 嘿嘿,那是因为,开启了自动调节功能了,那如何关闭呢?这才是最重要的:
+
+    /**
+     * 停止自动亮度调节
+     *
+     * @param activity
+     */
+    public static void stopAutoBrightness(Activity activity) {
+
+        Settings.System.putInt(activity.getContentResolver(),
+
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+
+                Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
+    }
+
+
+    /**
+     * 开启亮度自动调节
+     *
+     * @param activity
+     */
+    public static void startAutoBrightness(Activity activity) {
+
+        Settings.System.putInt(activity.getContentResolver(),
+
+                Settings.System.SCREEN_BRIGHTNESS_MODE,
+
+                Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC);
+    }
+
+    // 至此,应该说操作亮度的差不多都有了,结束!
+    // 哎,本来认为是应该结束了,但是悲剧得是,既然像刚才那样设置的话,只能在当前的activity中有作用,一段退出的时候,会发现毫无作用,悲剧,原来是忘记了保存了。汗!
+
+    /**
+     * 保存亮度设置状态
+     *
+     * @param resolver
+     * @param brightness
+     */
+    public static void saveBrightness(ContentResolver resolver, int brightness) {
+        Uri uri = Settings.System
+                .getUriFor("screen_brightness");
+
+        Settings.System.putInt(resolver, "screen_brightness",
+                brightness);
+        // resolver.registerContentObserver(uri, true, myContentObserver);
+        resolver.notifyChange(uri, null);
+    }
+}

+ 139 - 0
app_modular/facesdk/src/main/java/com/baidu/aip/excep/widget/DensityUtils.java

@@ -0,0 +1,139 @@
+package com.baidu.aip.excep.widget;
+/*
+ * Copyright (C) 2018 Baidu, Inc. All Rights Reserved.
+ */
+import android.content.Context;
+import android.os.Build;
+import android.text.TextUtils;
+
+/**
+ * 显示设备信息工具类
+ */
+public final class DensityUtils {
+
+    /**
+     * 四舍五入
+     */
+    private static final float DOT_FIVE = 0.5f;
+    /**
+     * portrait degree:90
+     */
+    private static final int PORTRAIT_DEGREE_90 = 90;
+
+    /**
+     * portrait degree:270
+     */
+    private static final int PORTRAIT_DEGREE_270 = 270;
+
+    /**
+     * Private constructor to prohibit nonsense instance creation.
+     */
+    private DensityUtils() {
+    }
+
+    /**
+     * sp转px.
+     *
+     * @param context context
+     * @param spValue spValue
+     * @return 换算后的px值
+     */
+    public static int sp2px(Context context, float spValue) {
+        final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
+        return (int) (spValue * fontScale + 0.5f);
+    }
+
+    /**
+     * dip转换成px
+     *
+     * @param context Context
+     * @param dip     dip Value
+     * @return 换算后的px值
+     */
+    public static int dip2px(Context context, float dip) {
+        float density = getDensity(context);
+        return (int) (dip * density + DensityUtils.DOT_FIVE);
+    }
+
+    /**
+     * px转换成dip
+     *
+     * @param context Context
+     * @param px      px Value
+     * @return 换算后的dip值
+     */
+    public static int px2dip(Context context, float px) {
+        float density = getDensity(context);
+        return (int) (px / density + DOT_FIVE);
+    }
+
+    /**
+     * 得到显示宽度
+     *
+     * @param context Context
+     * @return 宽度
+     */
+    public static int getDisplayWidth(Context context) {
+        return context.getResources().getDisplayMetrics().widthPixels;
+    }
+
+    /**
+     * 得到显示高度
+     *
+     * @param context Context
+     * @return 高度
+     */
+    public static int getDisplayHeight(Context context) {
+        return context.getResources().getDisplayMetrics().heightPixels;
+    }
+
+    /**
+     * 得到显示密度
+     *
+     * @param context Context
+     * @return 密度
+     */
+    public static float getDensity(Context context) {
+        return context.getResources().getDisplayMetrics().density;
+    }
+
+    /**
+     * 得到DPI
+     *
+     * @param context Context
+     * @return DPI
+     */
+    public static int getDensityDpi(Context context) {
+        return context.getResources().getDisplayMetrics().densityDpi;
+    }
+
+
+
+    /**
+     * 判断当前Android系统摄像头旋转多少度
+     *
+     * @return 当前Android系统能竖着屏幕,
+     * 正常应该旋转90度,
+     * 但是斐讯i700v、夏新A862W、桑菲V8526需要旋转270度
+     */
+    public static int getPortraitDegree() {
+        int degree = PORTRAIT_DEGREE_90;
+        // 为了更好的扩展更多的特殊设置型号,将要比较的设备型号提成一个数组,遍历这个数据。
+        for (String model : BUILD_MODELS) {
+            if (TextUtils.equals(model, Build.MODEL)) {
+                degree = PORTRAIT_DEGREE_270;
+                break;
+            }
+        }
+        return degree;
+    }
+
+    /**
+     * 需要比较的设置型号
+     */
+    private static final String[] BUILD_MODELS = {
+            "i700v", //斐讯i700v
+            "A862W", //夏新A862W
+            "V8526"  //桑菲V8526
+    };
+}

+ 236 - 0
app_modular/facesdk/src/main/java/com/baidu/aip/excep/widget/FaceRoundView.java

@@ -0,0 +1,236 @@
+package com.baidu.aip.excep.widget;
+/*
+ * Copyright (C) 2017 Baidu, Inc. All Rights Reserved.
+ */
+
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Typeface;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.View;
+
+import com.common.system.DisplayUtil;
+
+import demo.face.aip.baidu.com.facesdk.R;
+
+/**
+ * 人脸检测区域View
+ */
+public class FaceRoundView extends View{
+
+        public static final float SURFACE_HEIGHT = 1000f;
+        public static final float SURFACE_RATIO = 0.75f;
+        public static final float WIDTH_SPACE_RATIO = 0.33f;
+        public static final float HEIGHT_RATIO = 0.1f;
+        public static final float HEIGHT_EXT_RATIO = 0.2f;
+        public static final int CIRCLE_SPACE = 2;
+        public static final int PATH_SPACE = 16;
+        public static final int PATH_SMALL_SPACE = 12;
+        public static final int PATH_WIDTH = 6;
+
+        public final int COLOR_BG = Color.parseColor("#FFFFFF");
+        public final int COLOR_RECT = COLOR_BG;
+        public final int COLOR_ROUND = Color.parseColor("#DCDCDC");
+        public final int COLOR_ROUND_PASS = Color.parseColor("#10B0D9");
+
+
+        private Paint mBGPaint;//全局背景
+        private Paint mPathPaint;//扫描区外框,就是那个圈外面的
+        private Paint mFaceRoundPaint;
+
+        private Paint mTextTopPaint;//画上面的提示语
+        private Paint mTextBottomPaint;//画下面的提示语
+        private Paint mTextTipPaint;//画提示语
+        private Paint mArcTipPaint;//画提示语的背景
+
+        private RectF mOvalRect;
+        private Rect mFaceRect;
+        private Rect mFaceDetectRect;
+
+        private float mX;//原点
+        private float mY;//原点
+        private float mR;//半径
+        private boolean mIsSuccess = false;
+
+        private float mTipY;
+        private float mTipX;
+        private String mTipMessage;
+        private float mTopTextX;//
+        private float mTopTextY;//
+        private float mBottomTextX;//
+        private float mBottomTextY;//
+        private final Bitmap mSuccessBitmap;
+
+        public FaceRoundView(Context context, AttributeSet attrs) {
+            super(context, attrs);
+            setLayerType(View.LAYER_TYPE_SOFTWARE, null);
+            float pathWidth = DensityUtils.dip2px(context, PATH_WIDTH);
+
+            mBGPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+            mBGPaint.setColor(COLOR_BG);
+            mBGPaint.setStyle(Paint.Style.STROKE);
+            mBGPaint.setAntiAlias(true);
+            mBGPaint.setDither(true);
+
+            mPathPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+            mPathPaint.setColor(COLOR_ROUND);
+            mPathPaint.setStrokeWidth(pathWidth);
+            mPathPaint.setStyle(Paint.Style.STROKE);
+            mPathPaint.setAntiAlias(true);
+            mPathPaint.setDither(true);
+
+
+            mFaceRoundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+            mFaceRoundPaint.setColor(COLOR_ROUND);
+            mFaceRoundPaint.setStyle(Paint.Style.FILL);
+            mFaceRoundPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
+            mFaceRoundPaint.setAntiAlias(true);
+            mFaceRoundPaint.setDither(true);
+
+
+            mTextBottomPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+            mTextBottomPaint.setColor(Color.parseColor("#FF10B0D9"));
+            mTextBottomPaint.setTextSize(DensityUtils.dip2px(context, 17));
+
+            mTextTopPaint = new Paint(mTextBottomPaint);
+            mTextTopPaint.setColor(Color.parseColor("#FF616161"));
+            mTextTopPaint.setTypeface(Typeface.DEFAULT_BOLD);
+            mTextTopPaint.setTextSize(DensityUtils.dip2px(context, 20));
+
+            mTextTipPaint = new Paint(mTextBottomPaint);
+            mTextTipPaint.setColor(Color.WHITE);
+            mTextTipPaint.setTextSize(DensityUtils.dip2px(context, 12));
+
+            mArcTipPaint = new Paint();
+            mArcTipPaint.setColor(Color.parseColor("#646464"));
+            mArcTipPaint.setColor(Color.BLACK);
+            mArcTipPaint.setAntiAlias(true);//取消锯齿
+            mArcTipPaint.setStyle(Paint.Style.FILL);//设置画圆弧的画笔的属性为描边(空心),个人喜欢叫它描边,叫空心有点会引起歧义
+            mArcTipPaint.setStrokeWidth(pathWidth);
+
+            mSuccessBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_success);
+
+        }
+
+
+        public void onRefreshTipsView(String message) {
+            mTipMessage=message;
+            if (!TextUtils.isEmpty(message)) {
+                if (mTipY == 0) {
+                    float mTipHeight = mTextTipPaint.getFontMetrics().top - mTextTipPaint.getFontMetrics().bottom + DensityUtils.dip2px(getContext(), 10);
+                    mTipY = (mY - mR) + mTipHeight + DisplayUtil.dip2px(getContext(), 40);
+                }
+                float mTipWidth = mTextTipPaint.measureText(message);
+                mTipX = mX - mTipWidth / 2;
+                postInvalidate();
+            }
+        }
+
+        public void onRefreshSuccessView(boolean mIsSuccess) {
+            this.mIsSuccess = mIsSuccess;
+            mPathPaint.setColor(mIsSuccess ? COLOR_ROUND_PASS : COLOR_ROUND);
+            postInvalidate();
+        }
+
+
+
+        public float getRound() {
+            return mR;
+        }
+
+        public Rect getFaceRoundRect() {
+            return mFaceRect;
+        }
+
+        @Override
+        protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+            float canvasWidth = right - left;
+            float canvasHeight = bottom - top;
+
+            float x = canvasWidth / 2;
+            float y = (canvasHeight / 2) - ((canvasHeight / 2) * HEIGHT_RATIO);
+            float r = (canvasWidth / 2) - ((canvasWidth / 2) * WIDTH_SPACE_RATIO);
+            if (mFaceRect == null) {
+                mFaceRect = new Rect((int) (x - r),
+                        (int) (y - r),
+                        (int) (x + r),
+                        (int) (y + r));
+            }
+            if (mFaceDetectRect == null) {
+                float hr = r + (r * HEIGHT_EXT_RATIO);
+                mFaceDetectRect = new Rect((int) (x - r),
+                        (int) (y - hr),
+                        (int) (x + r),
+                        (int) (y + hr));
+            }
+
+            if (mOvalRect == null) {
+                mOvalRect = new RectF(mX - mR, mY - mR,
+                        mX + mR, mY + mR);
+            }
+
+            mX = x;
+            mY = y;
+            mR = r;
+            mBGPaint.setStrokeWidth((top - bottom) / 2 - mR);
+
+            mTopTextX = mX - mTextTopPaint.measureText("赏个脸呗") / 2;
+            mTopTextY = mY - mR - DensityUtils.dip2px(getContext(), 20);
+            mBottomTextX = mX - mTextBottomPaint.measureText("拿起手机,眨眨眼") / 2;
+            mBottomTextY = mY + mR + DensityUtils.dip2px(getContext(), 40);
+
+
+        }
+
+        @Override
+        public void onDraw(Canvas canvas) {
+            super.onDraw(canvas);
+            //画背景
+//        canvas.drawColor(Color.WHITE);
+//        canvas.drawARGB();
+            canvas.drawPaint(mBGPaint);
+//        canvas.drawCircle(mX, mY, getHeight() / 2, mBGPaint);
+            //画外框
+            canvas.drawCircle(mX, mY, mR - CIRCLE_SPACE + mPathPaint.getStrokeWidth(), mPathPaint);
+            canvas.drawCircle(mX, mY, mR, mFaceRoundPaint);
+
+            //画文字
+            canvas.drawText("赏个脸呗!", mTopTextX, mTopTextY, mTextTopPaint);
+            canvas.drawText("拿起手机,眨眨眼", mBottomTextX, mBottomTextY, mTextBottomPaint);
+
+            //画提示语
+//        canvas.drawArc(new RectF(mX, mY, 200, 200), -90, 120, true, mArcTipPaint);
+            if (!TextUtils.isEmpty(mTipMessage)){
+                canvas.drawText(mTipMessage, mTipX,mTipY,mTextTipPaint);
+            }
+            //画成功标识
+            if (mIsSuccess) {
+                canvas.drawBitmap(mSuccessBitmap, mX - mSuccessBitmap.getWidth() / 2, mY - mSuccessBitmap.getHeight() / 2, mPathPaint);
+            }
+        }
+
+
+        public static Rect getPreviewDetectRect(int w, int pw, int ph) {
+            float round = (w / 2) - ((w / 2) * WIDTH_SPACE_RATIO);
+            float x = pw / 2;
+            float y = (ph / 2) - ((ph / 2) * HEIGHT_RATIO);
+            float r = (pw / 2) > round ? round : (pw / 2);
+            float hr = r + (r * HEIGHT_EXT_RATIO);
+            Rect rect = new Rect((int) (x - r),
+                    (int) (y - hr),
+                    (int) (x + r),
+                    (int) (y + hr));
+            return rect;
+        }
+
+}

+ 86 - 0
app_modular/facesdk/src/main/java/com/baidu/aip/excep/widget/WaveHelper.java

@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2017 Baidu, Inc. All Rights Reserved.
+ */
+package com.baidu.aip.excep.widget;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.LinearInterpolator;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 人脸识别登录波纹动画
+ *
+ * @author liujinhui
+ * @date 2017/11/28
+ */
+
+public class WaveHelper {
+    private WaveView mWaveView;
+    private AnimatorSet mAnimatorSet;
+
+    /**
+     * 构造
+     *
+     * @param waveView
+     */
+    public WaveHelper(WaveView waveView) {
+        mWaveView = waveView;
+        initAnimation();
+    }
+
+    /**
+     * 动画开始
+     */
+    public void start() {
+        mWaveView.setShowWave(true);
+        if (mAnimatorSet != null) {
+            mAnimatorSet.start();
+        }
+    }
+
+    /**
+     * 动画初始化
+     */
+    private void initAnimation() {
+        List<Animator> animators = new ArrayList<>();
+
+        ObjectAnimator waveShiftAnim = ObjectAnimator.ofFloat(
+                mWaveView, "waveShiftRatio", 0f, 1f);
+        waveShiftAnim.setRepeatCount(ValueAnimator.INFINITE);
+        waveShiftAnim.setDuration(500);
+        waveShiftAnim.setInterpolator(new LinearInterpolator());
+        animators.add(waveShiftAnim);
+
+        ObjectAnimator waterLevelAnim = ObjectAnimator.ofFloat(
+                mWaveView, "waterLevelRatio", 0.25f, 0.25f);
+        waterLevelAnim.setDuration(1000);
+        waterLevelAnim.setInterpolator(new DecelerateInterpolator());
+        animators.add(waterLevelAnim);
+
+        ObjectAnimator amplitudeAnim = ObjectAnimator.ofFloat(
+                mWaveView, "amplitudeRatio", 0.05f, 0.05f);
+        amplitudeAnim.setRepeatCount(ValueAnimator.REVERSE);
+        amplitudeAnim.setRepeatMode(ValueAnimator.REVERSE);
+        amplitudeAnim.setDuration(1000);
+        amplitudeAnim.setInterpolator(new LinearInterpolator());
+        animators.add(amplitudeAnim);
+
+        mAnimatorSet = new AnimatorSet();
+        mAnimatorSet.playTogether(animators);
+    }
+
+    /**
+     * 动画取消
+     */
+    public void cancel() {
+        if (mAnimatorSet != null) {
+            mAnimatorSet.end();
+        }
+    }
+}

+ 334 - 0
app_modular/facesdk/src/main/java/com/baidu/aip/excep/widget/WaveView.java

@@ -0,0 +1,334 @@
+/*
+ * Copyright (C) 2017 Baidu, Inc. All Rights Reserved.
+ */
+package com.baidu.aip.excep.widget;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Paint.Style;
+import android.graphics.Shader;
+import android.util.AttributeSet;
+import android.view.View;
+
+/**
+ * sin函数绘制人脸识别登录波浪纹
+ *
+ * @author liujinhui
+ * @date 2017/11/28
+ */
+
+public class WaveView extends View {
+
+    private static final float DEFAULT_AMPLITUDE_RATIO = 0.05f;
+    private static final float DEFAULT_WATER_LEVEL_RATIO = 0.5f;
+    private static final float DEFAULT_WAVE_LENGTH_RATIO = 1.0f;
+    private static final float DEFAULT_WAVE_SHIFT_RATIO = 0.0f;
+    private static final int DEFAULT_STROKE_WIDTH = 2;
+
+    private static final int DEFAULT_BEHIND_WAVE_COLOR = Color.parseColor("#28FFFFFF");
+    private static final int DEFAULT_FRONT_WAVE_COLOR = Color.parseColor("#3CFFFFFF");
+    private static final ShapeType DEFAULT_WAVE_SHAPE = ShapeType.CIRCLE;
+
+    private boolean mShowWave;
+    private BitmapShader mWaveShader;
+    private Matrix mShaderMatrix;
+    private Paint mViewPaint;
+    private Paint mBorderPaint;
+    private float mDefaultAmplitude;
+    private float mDefaultWaterLevel;
+    private float mDefaultWaveLength;
+    private double mDefaultAngularFrequency;
+    private float mAmplitudeRatio = DEFAULT_AMPLITUDE_RATIO;
+    private float mWaveLengthRatio = DEFAULT_WAVE_LENGTH_RATIO;
+    private float mWaterLevelRatio = DEFAULT_WATER_LEVEL_RATIO;
+    private float mWaveShiftRatio = DEFAULT_WAVE_SHIFT_RATIO;
+
+    private int mBehindWaveColor = DEFAULT_BEHIND_WAVE_COLOR;
+    private int mFrontWaveColor = DEFAULT_FRONT_WAVE_COLOR;
+    private ShapeType mShapeType = DEFAULT_WAVE_SHAPE;
+
+    public enum ShapeType {
+        CIRCLE,
+        SQUARE
+    }
+
+    public WaveView(Context context) {
+        super(context);
+        init();
+    }
+
+    public WaveView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init();
+    }
+
+    public WaveView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        init();
+    }
+
+    /**
+     * 初始化
+     */
+    private void init() {
+        mShaderMatrix = new Matrix();
+        mViewPaint = new Paint();
+        mViewPaint.setAntiAlias(true);
+    }
+
+    /**
+     * 获取波纹摆动频率 (0-1,默认为0)
+     *
+     * @return
+     */
+    public float getWaveShiftRatio() {
+        return mWaveShiftRatio;
+    }
+
+    /**
+     * 设置波纹水平摆动频率
+     *
+     * @param waveShiftRatio (0-1,默认为0)
+     */
+    public void setWaveShiftRatio(float waveShiftRatio) {
+        if (mWaveShiftRatio != waveShiftRatio) {
+            mWaveShiftRatio = waveShiftRatio;
+            invalidate();
+        }
+    }
+
+    /**
+     * 获取波纹垂直摆动频率(0-1,默认为0.5)
+     *
+     * @return
+     */
+    public float getWaterLevelRatio() {
+        return mWaterLevelRatio;
+    }
+
+    /**
+     * 设置波纹垂直摆动频率 (0-1,默认为0.5)
+     *
+     * @param waterLevelRatio
+     */
+    public void setWaterLevelRatio(float waterLevelRatio) {
+        if (mWaterLevelRatio != waterLevelRatio) {
+            mWaterLevelRatio = waterLevelRatio;
+            invalidate();
+        }
+    }
+
+    /**
+     * 获取波纹振幅频率(0-1之间)
+     *
+     * @return
+     */
+    public float getAmplitudeRatio() {
+        return mAmplitudeRatio;
+    }
+
+    /**
+     * 设置波纹振幅频率(0-1之间)
+     *
+     * @param amplitudeRatio
+     */
+    public void setAmplitudeRatio(float amplitudeRatio) {
+        if (mAmplitudeRatio != amplitudeRatio) {
+            mAmplitudeRatio = amplitudeRatio;
+            invalidate();
+        }
+    }
+
+    /**
+     * 获取波纹水平方向的长度比率(0-1之间)
+     *
+     * @return
+     */
+    public float getWaveLengthRatio() {
+        return mWaveLengthRatio;
+    }
+
+    /**
+     * 设置波纹水平方向的长度比率 (0-1之间)
+     *
+     * @param waveLengthRatio
+     */
+    public void setWaveLengthRatio(float waveLengthRatio) {
+        mWaveLengthRatio = waveLengthRatio;
+    }
+
+    /**
+     * 是否显示
+     *
+     * @return
+     */
+    public boolean isShowWave() {
+        return mShowWave;
+    }
+
+    /**
+     * 设置是否显示
+     *
+     * @param showWave
+     */
+    public void setShowWave(boolean showWave) {
+        mShowWave = showWave;
+    }
+
+    /**
+     * 设置圆形或矩形外框边界
+     *
+     * @param width
+     * @param color
+     */
+    public void setBorder(int width, int color) {
+        if (mBorderPaint == null) {
+            mBorderPaint = new Paint();
+            mBorderPaint.setAntiAlias(true);
+            mBorderPaint.setStyle(Style.STROKE);
+        }
+        mBorderPaint.setColor(color);
+        mBorderPaint.setStrokeWidth(width);
+
+        invalidate();
+    }
+
+    /**
+     * 设置波纹颜色
+     *
+     * @param behindWaveColor
+     * @param frontWaveColor
+     */
+    public void setWaveColor(int behindWaveColor, int frontWaveColor) {
+        mBehindWaveColor = behindWaveColor;
+        mFrontWaveColor = frontWaveColor;
+
+        if (getWidth() > 0 && getHeight() > 0) {
+            mWaveShader = null;
+            createShader();
+            invalidate();
+        }
+    }
+
+    /**
+     * 设置外框样式
+     *
+     * @param shapeType
+     */
+    public void setShapeType(ShapeType shapeType) {
+        mShapeType = shapeType;
+        invalidate();
+    }
+
+    /**
+     * 重载尺寸改变
+     *
+     * @param w
+     * @param h
+     * @param oldw
+     * @param oldh
+     */
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        super.onSizeChanged(w, h, oldw, oldh);
+
+        createShader();
+    }
+
+    /**
+     * 通过sin函数计算x及y的波动和振幅
+     */
+    private void createShader() {
+        mDefaultAngularFrequency = 2.0f * Math.PI / DEFAULT_WAVE_LENGTH_RATIO / getWidth();
+        mDefaultAmplitude = getHeight() * DEFAULT_AMPLITUDE_RATIO;
+        mDefaultWaterLevel = getHeight() * DEFAULT_WATER_LEVEL_RATIO;
+        mDefaultWaveLength = getWidth();
+
+        Bitmap bitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(bitmap);
+
+        Paint wavePaint = new Paint();
+        wavePaint.setStrokeWidth(DEFAULT_STROKE_WIDTH);
+        wavePaint.setAntiAlias(true);
+
+        // 通过sin函数计算y
+        final int endX = getWidth() + 1;
+        final int endY = getHeight() + 1;
+        float[] waveY = new float[endX];
+
+        wavePaint.setColor(mBehindWaveColor);
+        for (int beginX = 0; beginX < endX; beginX++) {
+            double wx = beginX * mDefaultAngularFrequency;
+            float beginY = (float) (mDefaultWaterLevel + mDefaultAmplitude * Math.sin(wx));
+            canvas.drawLine(beginX, beginY, beginX, endY, wavePaint);
+
+            waveY[beginX] = beginY;
+        }
+
+        wavePaint.setColor(mFrontWaveColor);
+        final int wave2Shift = (int) (mDefaultWaveLength / 4);
+        for (int beginX = 0; beginX < endX; beginX++) {
+            canvas.drawLine(beginX, waveY[(beginX + wave2Shift) % endX], beginX, endY, wavePaint);
+        }
+
+        mWaveShader = new BitmapShader(bitmap, Shader.TileMode.REPEAT, Shader.TileMode.CLAMP);
+        mViewPaint.setShader(mWaveShader);
+    }
+
+    /**
+     * canvas绘制
+     *
+     * @param canvas
+     */
+    @Override
+    protected void onDraw(Canvas canvas) {
+        if (mShowWave && mWaveShader != null) {
+            if (mViewPaint.getShader() == null) {
+                mViewPaint.setShader(mWaveShader);
+            }
+
+            mShaderMatrix.setScale(
+                    mWaveLengthRatio / DEFAULT_WAVE_LENGTH_RATIO,
+                    mAmplitudeRatio / DEFAULT_AMPLITUDE_RATIO,
+                    0,
+                    mDefaultWaterLevel);
+
+            mShaderMatrix.postTranslate(
+                    mWaveShiftRatio * getWidth(),
+                    (DEFAULT_WATER_LEVEL_RATIO - mWaterLevelRatio) * getHeight());
+
+            mWaveShader.setLocalMatrix(mShaderMatrix);
+
+            float borderWidth = mBorderPaint == null ? 0f : mBorderPaint.getStrokeWidth();
+            switch (mShapeType) {
+                case CIRCLE:
+                    if (borderWidth > 0) {
+                        canvas.drawCircle(getWidth() / 2f, getHeight() / 2f,
+                                (getWidth() - borderWidth) / 2f - 1f, mBorderPaint);
+                    }
+                    float radius = getWidth() / 2f - borderWidth;
+                    canvas.drawCircle(getWidth() / 2f, getHeight() / 2f, radius, mViewPaint);
+                    break;
+                case SQUARE:
+                    if (borderWidth > 0) {
+                        canvas.drawRect(
+                                borderWidth / 2f,
+                                borderWidth / 2f,
+                                getWidth() - borderWidth / 2f - 0.5f,
+                                getHeight() - borderWidth / 2f - 0.5f,
+                                mBorderPaint);
+                    }
+                    canvas.drawRect(borderWidth, borderWidth, getWidth() - borderWidth,
+                            getHeight() - borderWidth, mViewPaint);
+                    break;
+            }
+        } else {
+            mViewPaint.setShader(null);
+        }
+    }
+}

+ 31 - 0
app_modular/facesdk/src/main/java/com/baidu/aip/face/ArgbPool.java

@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2017 Baidu, Inc. All Rights Reserved.
+ */
+package com.baidu.aip.face;
+
+import android.support.v4.util.Pools;
+
+public class ArgbPool {
+
+    Pools.SynchronizedPool<int[]> pool = new Pools.SynchronizedPool<>(5);
+
+    public ArgbPool() {
+
+    }
+
+    public int[] acquire(int width, int height) {
+        int[] argb = pool.acquire();
+        if (argb == null || argb.length != width * height) {
+            argb = new int[width * height];
+        }
+        return argb;
+    }
+
+    public void release(int[] data) {
+        try {
+            pool.release(data);
+        } catch (IllegalStateException ignored) {
+            // ignored
+        }
+    }
+}

+ 108 - 0
app_modular/facesdk/src/main/java/com/baidu/aip/face/CameraImageSource.java

@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2017 Baidu, Inc. All Rights Reserved.
+ */
+package com.baidu.aip.face;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+
+import com.baidu.aip.FaceDetector;
+import com.baidu.aip.ImageFrame;
+import com.baidu.aip.face.camera.Camera1Control;
+import com.baidu.aip.face.camera.ICameraControl;
+
+import android.content.Context;
+
+/**
+ * 封装了系统做机做为输入源。
+ */
+public class CameraImageSource extends ImageSource {
+
+    /**
+     * 相机控制类
+     */
+    private ICameraControl cameraControl;
+    private Context context;
+
+    public ICameraControl getCameraControl() {
+        return cameraControl;
+    }
+
+    private ArgbPool argbPool = new ArgbPool();
+
+    private int cameraFaceType = ICameraControl.CAMERA_FACING_FRONT;
+
+    public void setCameraFacing(int type) {
+        this.cameraFaceType = type;
+    }
+
+    public CameraImageSource(Context context) {
+        this.context = context;
+        cameraControl = new Camera1Control(getContext());
+        cameraControl.setCameraFacing(cameraFaceType);
+        cameraControl.setOnFrameListener(new ICameraControl.OnFrameListener<byte[]>() {
+            @Override
+            public void onPreviewFrame(byte[] data, int rotation, int width, int height) {
+                int[] argb = argbPool.acquire(width, height);
+
+                if (argb == null || argb.length != width * height) {
+                    argb = new int[width * height];
+                }
+
+                rotation = rotation < 0 ? 360 + rotation : rotation;
+                FaceDetector.yuvToARGB(data, width, height, argb, rotation, 0);
+
+               //   FaceSDK.getARGBFromYUVimg(data, argb, width, height, rotation, 0);
+
+                // liujinhui modify
+
+                // 旋转了90或270度。高宽需要替换
+                if (rotation % 180 == 90) {
+                    int temp = width;
+                    width = height;
+                    height = temp;
+                }
+
+                ImageFrame frame = new ImageFrame();
+                frame.setArgb(argb);
+                frame.setWidth(width);
+                frame.setHeight(height);
+                frame.setPool(argbPool);
+                ArrayList<OnFrameAvailableListener> listeners = getListeners();
+                for (OnFrameAvailableListener listener : listeners) {
+                    listener.onFrameAvailable(frame);
+                }
+            }
+        });
+    }
+
+    private int[] toIntArray(byte[] buf) {
+        final ByteBuffer buffer = ByteBuffer.wrap(buf)
+                .order(ByteOrder.LITTLE_ENDIAN);
+        final int[] ret = new int[buf.length / 4];
+        buffer.asIntBuffer().put(ret);
+        return ret;
+    }
+
+    @Override
+    public void start() {
+        super.start();
+        cameraControl.start();
+    }
+
+    @Override
+    public void stop() {
+        super.stop();
+        cameraControl.stop();
+    }
+
+    private Context getContext() {
+        return context;
+    }
+
+    @Override
+    public void setPreviewView(PreviewView previewView) {
+        cameraControl.setPreviewView(previewView);
+    }
+}

+ 47 - 0
app_modular/facesdk/src/main/java/com/baidu/aip/face/DetectRegionProcessor.java

@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2017 Baidu, Inc. All Rights Reserved.
+ */
+package com.baidu.aip.face;
+
+import com.baidu.aip.ImageFrame;
+
+import android.graphics.Rect;
+import android.graphics.RectF;
+
+/**
+ * 裁剪一定区域内的图片进行检测。
+ */
+public class DetectRegionProcessor implements FaceProcessor {
+
+    private RectF detectedRect;
+
+    private RectF originalCoordinate = new RectF();
+
+    /**
+     * 设置裁剪的区域。该区域内的图片被会裁剪进行检测,其余会被抛弃。
+     * @param rect 检测区域
+     */
+    public void setDetectedRect(RectF rect) {
+        detectedRect = rect;
+    }
+
+
+    private Rect cropRect = new Rect();
+
+    @Override
+    public boolean process(FaceDetectManager faceDetectManager, ImageFrame frame) {
+        if (detectedRect != null) {
+            originalCoordinate.set(detectedRect);
+            CameraImageSource cam = (CameraImageSource) faceDetectManager.getImageSource(); // TODO
+            cam.getCameraControl().getPreviewView().mapToOriginalRect(originalCoordinate);
+            cropRect.left = (int) originalCoordinate.left;
+            cropRect.top = (int) originalCoordinate.top;
+            cropRect.right = (int) originalCoordinate.right;
+            cropRect.bottom = (int) originalCoordinate.bottom;
+            frame.setArgb(FaceCropper.crop(frame.getArgb(), frame.getWidth(), cropRect));
+            frame.setWidth(cropRect.width());
+            frame.setHeight(cropRect.height());
+        }
+        return false;
+    }
+}

+ 91 - 0
app_modular/facesdk/src/main/java/com/baidu/aip/face/FaceCropper.java

@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2017 Baidu, Inc. All Rights Reserved.
+ */
+package com.baidu.aip.face;
+
+import com.baidu.idl.facesdk.FaceInfo;
+
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+
+/**
+ *人脸裁剪工具类。
+ */
+public class FaceCropper {
+
+    /**
+     * 高速裁剪狂,防止框超出图片范围。
+     * @param argb 图片argb数据
+     * @param width 图片宽度
+     * @param rect 裁剪框
+     */
+    public static void adjustRect(int[] argb, int width, Rect rect) {
+        rect.left = Math.max(rect.left ,0);
+        rect.right = Math.min(rect.right,width);
+        int height = argb.length / width;
+        rect.bottom = Math.min(rect.bottom,height);
+        rect.sort();
+    }
+
+    /**
+     * 裁剪argb中的一块儿,裁剪框如果超出图片范围会被调整,所以记得检查。
+     * @param argb 图片argb数据
+     * @param width 图片宽度
+     * @param rect 裁剪框
+     */
+    public static int[] crop(int[] argb, int width, Rect rect) {
+        adjustRect(argb, width, rect);
+        int[] image = new int[rect.width() * rect.height()];
+
+        for (int i = rect.top; i < rect.bottom; i++) {
+            int rowIndex = width * i;
+            try {
+                System.arraycopy(argb, rowIndex + rect.left, image, rect.width() * (i - rect.top), rect.width());
+            } catch (Exception e) {
+                e.printStackTrace();
+                return argb;
+            }
+        }
+        return image;
+    }
+
+    /**
+     * 裁剪图片中的人脸。
+     * @param argb argb图片数据
+     * @param faceInfo 人脸信息
+     * @param imageWidth 图片宽度
+     * @return 返回裁剪后的人脸图片
+     */
+   public static Bitmap getFace(int[] argb, FaceInfo faceInfo, int imageWidth) {
+        int[] points = new int[8];
+
+        faceInfo.getRectPoints(points);
+
+        int left = points[2];
+        int top = points[3];
+        int right = points[6];
+        int bottom = points[7];
+
+        int width = right - left;
+        int height = bottom - top;
+
+        width = width * 3 / 2;
+        height = height * 2;
+        //
+        left = faceInfo.mCenter_x - width / 2;
+        top = faceInfo.mCenter_y - height / 2;
+
+        height = height * 4 / 5;
+        //
+        left = Math.max(left, 0);
+        top = Math.max(top, 0);
+
+        Rect region = new Rect(left, top, left + width, top + height);
+        FaceCropper.adjustRect(argb, imageWidth, region);
+        int offset = region.top * imageWidth + region.left;
+        return Bitmap.createBitmap(argb,offset,imageWidth,region.width(),region.height(),
+                Bitmap.Config.ARGB_8888);
+    }
+
+
+}

+ 206 - 0
app_modular/facesdk/src/main/java/com/baidu/aip/face/FaceDetectManager.java

@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2017 Baidu, Inc. All Rights Reserved.
+ */
+package com.baidu.aip.face;
+
+import java.util.ArrayList;
+
+import com.baidu.aip.FaceSDKManager;
+import com.baidu.aip.ImageFrame;
+import com.baidu.aip.face.stat.Ast;
+import com.baidu.idl.facesdk.FaceInfo;
+import com.baidu.idl.facesdk.FaceSDK;
+import com.baidu.idl.facesdk.FaceTracker;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.HandlerThread;
+
+/**
+ * 封装了人脸检测的整体逻辑。
+ */
+public class FaceDetectManager {
+    private Context mContext;
+
+    /**
+     * 该回调用于回调,人脸检测结果。当没有人脸时,infos 为null,status为 FaceDetector.DETECT_CODE_NO_FACE_DETECTED
+     */
+    public interface OnFaceDetectListener {
+        void onDetectFace(int status, FaceInfo[] infos, ImageFrame imageFrame);
+    }
+
+    public FaceDetectManager(Context context) {
+        mContext = context;
+        Ast.getInstance().init(context.getApplicationContext(), "3.3.0.0", "facedetect");
+    }
+
+    /**
+     * 图片源,获取检测图片。
+     */
+    private ImageSource imageSource;
+    /**
+     * 人脸检测事件监听器
+     */
+    private OnFaceDetectListener listener;
+    private FaceFilter faceFilter = new FaceFilter();
+    private HandlerThread processThread;
+    private Handler processHandler;
+    private Handler uiHandler;
+    private ImageFrame lastFrame;
+    private int mPreviewDegree = 90;
+
+    private ArrayList<FaceProcessor> preProcessors = new ArrayList<>();
+
+    /**
+     * 设置人脸检测监听器,检测后的结果会回调。
+     *
+     * @param listener 监听器
+     */
+    public void setOnFaceDetectListener(OnFaceDetectListener listener) {
+        this.listener = listener;
+    }
+
+    /**
+     * 设置图片帧来源
+     *
+     * @param imageSource 图片来源
+     */
+    public void setImageSource(ImageSource imageSource) {
+        this.imageSource = imageSource;
+    }
+
+    /**
+     * @return 返回图片来源
+     */
+    public ImageSource getImageSource() {
+        return this.imageSource;
+    }
+
+    /**
+     * 增加处理回调,在人脸检测前会被回调。
+     *
+     * @param processor 图片帧处理回调
+     */
+    public void addPreProcessor(FaceProcessor processor) {
+        preProcessors.add(processor);
+    }
+
+    /**
+     * 设置人检跟踪回调。
+     *
+     * @param onTrackListener 人脸回调
+     */
+    public void setOnTrackListener(FaceFilter.OnTrackListener onTrackListener) {
+        faceFilter.setOnTrackListener(onTrackListener);
+    }
+
+    /**
+     * @return 返回过虑器
+     */
+    public FaceFilter getFaceFilter() {
+        return faceFilter;
+    }
+
+    public void start() {
+        this.imageSource.addOnFrameAvailableListener(onFrameAvailableListener);
+        processThread = new HandlerThread("process");
+        processThread.setPriority(10);
+        processThread.start();
+        processHandler = new Handler(processThread.getLooper());
+        uiHandler = new Handler();
+        this.imageSource.start();
+    }
+
+    private Runnable processRunnable = new Runnable() {
+        @Override
+        public void run() {
+            if (lastFrame == null) {
+                return;
+            }
+            android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO);
+            int[] argb;
+            int width;
+            int height;
+            ArgbPool pool;
+            synchronized (lastFrame) {
+                argb = lastFrame.getArgb();
+                width = lastFrame.getWidth();
+                height = lastFrame.getHeight();
+                pool = lastFrame.getPool();
+                lastFrame = null;
+            }
+            process(argb, width, height, pool);
+        }
+    };
+
+    public void stop() {
+        this.imageSource.stop();
+        this.imageSource.removeOnFrameAvailableListener(onFrameAvailableListener);
+        if (processThread != null) {
+            processThread.quit();
+            processThread = null;
+        }
+        Ast.getInstance().immediatelyUpload();
+    }
+
+    public void setPreviewDegree(int degree) {
+        this.mPreviewDegree = degree;
+    }
+
+
+    private void process(int[] argb, int width, int height, ArgbPool pool) {
+        int value;
+
+        ImageFrame frame = imageSource.borrowImageFrame();
+        frame.setArgb(argb);
+        frame.setWidth(width);
+        frame.setHeight(height);
+        frame.setPool(pool);
+        //        frame.retain();
+
+        for (FaceProcessor processor : preProcessors) {
+            if (processor.process(this, frame)) {
+                break;
+            }
+        }
+
+        //  long starttime = System.currentTimeMillis();
+
+//        FaceTracker.ErrCode errorCode = FaceSDKManager.getInstance().getFaceTracker().face_verification(
+//                argb,
+//                height, width,
+//                FaceSDK.ImgType.ARGB,
+//                FaceTracker.ActionType.RECOGNIZE,
+//                "baidu", "module", "sdk");
+        //  value = errorCode.ordinal();
+        // FaceInfo[] faces = FaceSDKManager.getInstance().getFaceTracker().get_TrackedFaceInfo();
+
+
+        value = FaceSDKManager.getInstance().getFaceTracker(mContext)
+                .prepare_max_face_data_for_verify(frame.getArgb(), frame.getHeight(), frame.getWidth(),
+                        FaceSDK.ImgType.ARGB.ordinal(), FaceTracker.ActionType.RECOGNIZE.ordinal());
+//         value = FaceSDKManager.getInstance().detect(frame.getArgb(), frame.getWidth(), frame.getHeight());
+        FaceInfo[] faces = FaceSDKManager.getInstance().getFaceTracker(mContext).get_TrackedFaceInfo();
+
+        if (value == 0) {
+            faceFilter.filter(faces, frame);
+        }
+        if (listener != null) {
+            listener.onDetectFace(value, faces, frame);
+        }
+        Ast.getInstance().faceHit("facelogin",  60 * 1000, faces);
+
+        frame.release();
+
+    }
+
+    private OnFrameAvailableListener onFrameAvailableListener = new OnFrameAvailableListener() {
+        @Override
+        public void onFrameAvailable(ImageFrame imageFrame) {
+            lastFrame = imageFrame;
+            processHandler.removeCallbacks(processRunnable);
+            processHandler.post(processRunnable);
+//            processRunnable.run();
+        }
+    };
+}

+ 267 - 0
app_modular/facesdk/src/main/java/com/baidu/aip/face/FaceFilter.java

@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2017 Baidu, Inc. All Rights Reserved.
+ */
+package com.baidu.aip.face;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.UUID;
+
+import com.baidu.aip.ImageFrame;
+import com.baidu.idl.facesdk.FaceInfo;
+
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.support.v4.util.Pools;
+import android.util.Log;
+import android.util.SparseArray;
+
+/**
+ * 过虑器,可用根据条件过虑帧。
+ */
+public class FaceFilter {
+
+    /**
+     * 人脸追踪回调。
+     */
+    public interface OnTrackListener {
+        /**
+         * 追踪到某张人脸
+         *
+         * @param trackedModel 人脸信息
+         */
+        void onTrack(TrackedModel trackedModel);
+    }
+
+    /**
+     * 人脸追踪事件。
+     */
+    public enum Event {
+        /**
+         * 人脸第一次进入检测区域。
+         */
+        OnEnter,
+        /**
+         * 人脸没有离开检测区域。人脸检测更新。
+         */
+        OnUpdate,
+        /**
+         * 该人脸离开了检测区域,或者丢失了跟踪。
+         */
+        OnLeave,
+    }
+
+    public class TrackedModel {
+        private String trackId;
+        private ImageFrame frame;
+        private FaceInfo info;
+        private Event event;
+
+        public ImageFrame getImageFrame() {
+            return getFrame();
+        }
+
+        /**
+         * 对应的事件
+         */
+        public Event getEvent() {
+            return event;
+        }
+
+        /**
+         * 是否符合过虑标准
+         *
+         * @return 符合过虑标准
+         */
+        public boolean meetCriteria() {
+            float pitch = Math.abs(getInfo().headPose[0]);
+            float yaw = Math.abs(getInfo().headPose[1]);
+            float roll = Math.abs(getInfo().headPose[2]);
+            return pitch < angle && yaw < angle && roll < angle;
+        }
+
+        public Bitmap cropFace() {
+            return cropFace(getFaceRect());
+        }
+
+        /**
+         * 裁剪人脸图片。
+         *
+         * @param rect 裁剪区域。如果区域超出人脸,区域会被调整。
+         *
+         * @return 裁剪后的人脸图片。
+         */
+
+        // TODO
+        public Bitmap cropFace(Rect rect) {
+            return FaceCropper.getFace(getFrame().getArgb(), info, getImageFrame().getWidth());
+//            int[] argb = FaceCropper.crop(getFrame().getArgb(), getFrame().getWidth(), rect);
+//            return Bitmap.createBitmap(argb, rect.width(), rect.height(), Bitmap.Config.ARGB_8888);
+        }
+
+        @Override
+        public int hashCode() {
+            return getInfo().face_id;
+        }
+
+        int[] points = new int[8];
+
+        /**
+         * 获取人脸框区域。
+         *
+         * @return 人脸框区域
+         */
+        // TODO padding?
+        public Rect getFaceRect() {
+            Rect rect = new Rect();
+            getInfo().getRectPoints(points);
+
+            int left = points[2];
+            int top = points[3];
+            int right = points[6];
+            int bottom = points[7];
+            //
+         //   int width = (right - left) * 4 / 3;
+         //   int height = (bottom - top) * 4 / 3;
+//
+//            left = getInfo().mCenter_x - width / 2;
+//            top = getInfo().mCenter_y - height / 2;
+//
+//            rect.top = top;
+//            rect.left = left;
+//            rect.right = left + width;
+//            rect.bottom = top + height;
+
+            int width = (right - left) * 8 / 3;
+            int height = (bottom - top) * 10 / 3;
+
+            left = getInfo().mCenter_x - width / 2;
+            top = getInfo().mCenter_y - height * 2 / 3;
+
+
+            rect.top = top < 0 ? 0 : top;
+            rect.left = left < 0 ? 0 : left;
+            rect.right = (left + width) > frame.getWidth() ? frame.getWidth() : (left + width) ;
+            rect.bottom = (top + height) > frame.getHeight() ? frame.getHeight() : (top + height);
+
+            return rect;
+        }
+
+        /**
+         * 标识一张人脸的追踪id。
+         */
+        public String getTrackId() {
+            return trackId;
+        }
+
+        public void setTrackId(String trackId) {
+            this.trackId = trackId;
+        }
+
+        /**
+         * 对应的帧
+         */
+        public ImageFrame getFrame() {
+            return frame;
+        }
+
+        public void setFrame(ImageFrame frame) {
+            this.frame = frame;
+        }
+
+        /**
+         * 人脸检测数据
+         */
+        public FaceInfo getInfo() {
+            return info;
+        }
+
+        public void setInfo(FaceInfo info) {
+            this.info = info;
+        }
+
+        public void setEvent(Event event) {
+            this.event = event;
+        }
+    }
+
+    private OnTrackListener onTrackListener;
+
+    private SparseArray<TrackedModel> trackingFaces = new SparseArray<>();
+    private Pools.SynchronizedPool<TrackedModel> pool = new Pools.SynchronizedPool<>(5);
+
+    private HashSet<TrackedModel> currentFrame = new HashSet<>();
+    private ArrayList<Integer> leftFaces = new ArrayList<>();
+
+    private int angle = 15;
+
+    /**
+     * 设置过虑角度。参见人脸欧拉角;
+     *
+     * @param angle 欧拉角
+     */
+    public void setAngle(int angle) {
+        this.angle = angle;
+    }
+
+    /**
+     * 设置跟踪监听器
+     *
+     * @param onTrackListener 跟踪监听器
+     */
+    public void setOnTrackListener(OnTrackListener onTrackListener) {
+        this.onTrackListener = onTrackListener;
+    }
+
+    public void filter(FaceInfo[] infos, ImageFrame frame) {
+        currentFrame.clear();
+        if (infos != null) {
+            for (FaceInfo faceInfo : infos) {
+                TrackedModel face = getTrackedModel(faceInfo, frame);
+                currentFrame.add(face);
+                face.setInfo(faceInfo);
+            }
+        }
+
+        leftFaces.clear();
+        for (int i = 0; i < trackingFaces.size(); i++) {
+            int key = trackingFaces.keyAt(i);
+            TrackedModel face = trackingFaces.get(key);
+            if (!currentFrame.contains(face)) {
+                leftFaces.add(key);
+            } else {
+                if (onTrackListener != null) {
+                    face.setFrame(frame);
+                    onTrackListener.onTrack(face);
+                }
+            }
+
+        }
+        for (Integer key : leftFaces) {
+            TrackedModel left = trackingFaces.get(key);
+            Log.e("xx", " left:" + left);
+            left.setEvent(Event.OnLeave);
+            trackingFaces.remove(key);
+            if (onTrackListener != null) {
+                onTrackListener.onTrack(left);
+            }
+            // TODO release argb?
+        }
+    }
+
+    private TrackedModel getTrackedModel(FaceInfo faceInfo, ImageFrame frame) {
+        TrackedModel face = trackingFaces.get(faceInfo.face_id);
+        if (face == null) {
+            face = pool.acquire();
+            if (face == null) {
+                face = new TrackedModel();
+            }
+            trackingFaces.append(faceInfo.face_id, face);
+            face.setTrackId(UUID.randomUUID().toString());
+            face.setEvent(Event.OnEnter);
+        }
+        face.setInfo(faceInfo);
+        face.setFrame(frame);
+        return face;
+    }
+}

+ 19 - 0
app_modular/facesdk/src/main/java/com/baidu/aip/face/FaceProcessor.java

@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2017 Baidu, Inc. All Rights Reserved.
+ */
+package com.baidu.aip.face;
+
+import com.baidu.aip.ImageFrame;
+
+/**
+ *  FaceDetectManager 人脸检测之前的回调。可以对图片进行预处理。如果ImageFrame中的argb数据为空,将不进行检测。
+ */
+public interface FaceProcessor {
+    /**
+     * FaceDetectManager 回调该方法,对图片帧进行处理。
+     * @param detectManager 回调的 FaceDetectManager
+     * @param frame 需要处理的图片帧。
+     * @return 返回true剩下的FaceProcessor将不会被回调。
+     */
+    boolean process(FaceDetectManager detectManager, ImageFrame frame);
+}

+ 121 - 0
app_modular/facesdk/src/main/java/com/baidu/aip/face/FileImageSource.java

@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2017 Baidu, Inc. All Rights Reserved.
+ */
+package com.baidu.aip.face;
+
+import java.io.IOException;
+
+import com.baidu.aip.FaceDetector;
+import com.baidu.aip.ImageFrame;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Matrix;
+import android.media.ExifInterface;
+
+/**
+ *  从图片读取并检测人脸。
+ */
+public class FileImageSource extends ImageSource {
+
+    private String filePath;
+
+    /**
+     * 设置检测图片的位置。
+     * @param filePath 图片路径
+     */
+    public void setFilePath(String filePath) {
+        this.filePath = filePath;
+    }
+
+    @Override
+    public void start() {
+        super.start();
+        Bitmap bitmap = getImageThumbnail(filePath, 960, 960);
+        int degree = getExifOrientation(filePath);
+
+        if (degree == 90 || degree == 180 || degree == 270) {
+            Matrix matrix = new Matrix();
+            matrix.postRotate(degree);
+            bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
+        }
+
+        // 文件转化为int[] argb
+        int[] argb = new int[bitmap.getWidth() * bitmap.getHeight()];
+        bitmap.getPixels(argb, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight());
+        FaceDetector.getInstance().clearTrackedFaces();
+
+        ImageFrame frame = new ImageFrame();
+        frame.setArgb(argb);
+        frame.setWidth(bitmap.getWidth());
+        frame.setHeight(bitmap.getHeight());
+
+        for (OnFrameAvailableListener listener : getListeners()) {
+            listener.onFrameAvailable(frame);
+        }
+        FaceDetector.getInstance().clearTrackedFaces();
+    }
+
+    private Bitmap getImageThumbnail(String imagePath, int width, int height) {
+        Bitmap bitmap;
+        BitmapFactory.Options options = new BitmapFactory.Options();
+        options.inJustDecodeBounds = true;
+        // 获取这个图片的宽和高,注意此处的bitmap为null
+        BitmapFactory.decodeFile(imagePath, options);
+        options.inJustDecodeBounds = false; // 设为 false
+        // 计算缩放比
+        int h = options.outHeight;
+        int w = options.outWidth;
+        int beWidth = w / width;
+        int beHeight = h / height;
+        int be = 1;
+        if (beWidth < beHeight) {
+            be = beWidth;
+        } else {
+            be = beHeight;
+        }
+        if (be <= 0) {
+            be = 1;
+        }
+        options.inSampleSize = be;
+        options.inPreferredConfig = Bitmap.Config.ARGB_8888;
+
+        // 重新读入图片,读取缩放后的bitmap,注意这次要把options.inJustDecodeBounds 设为 false
+        bitmap = BitmapFactory.decodeFile(imagePath, options);
+        return bitmap;
+    }
+
+    private static int getExifOrientation(String filepath) {
+        int degree = 0;
+        ExifInterface exif = null;
+
+        try {
+            exif = new ExifInterface(filepath);
+        } catch (IOException ex) {
+            // MmsLog.e(ISMS_TAG, "getExifOrientation():", ex);
+        }
+
+        if (exif != null) {
+            int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, -1);
+            if (orientation != -1) {
+                // We only recognize a subset of orientation tag values.
+                switch (orientation) {
+                    case ExifInterface.ORIENTATION_ROTATE_90:
+                        degree = 90;
+                        break;
+
+                    case ExifInterface.ORIENTATION_ROTATE_180:
+                        degree = 180;
+                        break;
+
+                    case ExifInterface.ORIENTATION_ROTATE_270:
+                        degree = 270;
+                        break;
+                    default:
+                        break;
+                }
+            }
+        }
+        return degree;
+    }
+}

+ 52 - 0
app_modular/facesdk/src/main/java/com/baidu/aip/face/ImageSource.java

@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2017 Baidu, Inc. All Rights Reserved.
+ */
+package com.baidu.aip.face;
+
+import java.util.ArrayList;
+
+import com.baidu.aip.ImageFrame;
+
+/**
+ * 该类封装了图片的图片源,给 @link(FaceDetectManager)提供一帧帧的图片用于人脸检测。
+ * 如CameraImageSource封装了,系统相机。
+ */
+public class ImageSource {
+
+    public ImageFrame borrowImageFrame() {
+        return new ImageFrame();
+    }
+
+    private ArrayList<OnFrameAvailableListener> listeners = new ArrayList<>();
+
+    /** 注册监听器,当有图片帧时会回调。*/
+    public void addOnFrameAvailableListener(OnFrameAvailableListener listener) {
+        this.listeners.add(listener);
+    }
+
+    /** 删除监听器*/
+    public void removeOnFrameAvailableListener(OnFrameAvailableListener listener) {
+        if (listener != null) {
+            this.listeners.remove(listener);
+        }
+    }
+
+    /** 获取监听器列表 */
+    protected ArrayList<OnFrameAvailableListener> getListeners() {
+        return listeners;
+    }
+
+    /** 打开图片源。*/
+    public void start() {
+
+    }
+
+    /** 停止图片源。*/
+    public void stop() {
+
+    }
+
+    /** 设置预览View用于显示预览图。*/
+    public void setPreviewView(PreviewView previewView) {
+    }
+}

+ 17 - 0
app_modular/facesdk/src/main/java/com/baidu/aip/face/OnFrameAvailableListener.java

@@ -0,0 +1,17 @@
+/*
+ * Copyright (C) 2017 Baidu, Inc. All Rights Reserved.
+ */
+package com.baidu.aip.face;
+
+import com.baidu.aip.ImageFrame;
+
+/**
+ * 当图片源有新的图片帧时会回调该类。
+ */
+public interface OnFrameAvailableListener {
+    /**
+     * 每当图片源有新一帧时图片源会回调该方法。
+     * @param frame 新的一帧
+     */
+    void onFrameAvailable(ImageFrame frame);
+}

+ 80 - 0
app_modular/facesdk/src/main/java/com/baidu/aip/face/PreviewView.java

@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2017 Baidu, Inc. All Rights Reserved.
+ */
+package com.baidu.aip.face;
+
+import android.graphics.RectF;
+import android.view.TextureView;
+
+/**
+ * 该类用于实现,图片源(@see ImageSource)数据预览。
+ */
+
+public interface PreviewView {
+
+    /**
+     * 图片帧缩放类型。
+     */
+    enum ScaleType {
+
+        /**
+         * 宽度与父控件一致,高度自适应
+         */
+        FIT_WIDTH,
+        /**
+         * 调试与父控件一致,宽度自适应
+         */
+        FIT_HEIGHT,
+        /**
+         * 全屏显示 ,保持显示比例,多余的部分会被裁剪掉。
+         */
+        CROP_INSIDE,
+    }
+
+    TextureView getTextureView();
+
+    /**
+     * 设置帧大小。
+     *
+     * @param width  帧宽度
+     * @param height 帧调试
+     */
+    void setPreviewSize(int width, int height);
+
+    /**
+     * 预览View中的坐标映射到,原始图片中。应用场景举例:裁剪框
+     *
+     * @param rect 预览View中的坐标
+     */
+    void mapToOriginalRect(RectF rect);
+
+    /**
+     * 原始图片中的坐标到预览View坐标中的映射。应用场景举例:预览页面显示人脸框。
+     *
+     * @param rectF 原始图中的坐标
+     */
+    void mapFromOriginalRect(RectF rectF);
+
+
+    void mapFromOriginalRectEx(RectF rectF);
+    /**
+     * 原始图片中的坐标到预览View坐标中的映射。应用场景举例:预览页面显示人脸框。
+     *
+     * @param rectF 原始图中的坐标
+     */
+    // void mapFromOriginalRectEx(RectF rectF);
+
+    /**
+     * 设置预览的缩放类型
+     *
+     * @param scaleType 缩放类型
+     */
+    void setScaleType(ScaleType scaleType);
+
+    /**
+     * 获取预览缩放类型
+     *
+     * @return 预览缩放类型
+     */
+    ScaleType getScaleType();
+}

+ 217 - 0
app_modular/facesdk/src/main/java/com/baidu/aip/face/TexturePreviewView.java

@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2017 Baidu, Inc. All Rights Reserved.
+ */
+package com.baidu.aip.face;
+
+import android.content.Context;
+import android.graphics.Matrix;
+import android.graphics.RectF;
+import android.os.Handler;
+import android.os.Looper;
+import android.support.annotation.AttrRes;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.util.AttributeSet;
+import android.view.TextureView;
+import android.widget.FrameLayout;
+
+/**
+ * 基于 系统TextureView实现的预览View;
+ */
+public class TexturePreviewView extends FrameLayout implements PreviewView {
+
+    private TextureView textureView;
+
+    private int videoWidth = 0;
+    private int videoHeight = 0;
+    private boolean mirrored = true;
+
+    public TexturePreviewView(@NonNull Context context) {
+        super(context);
+        init();
+    }
+
+    public TexturePreviewView(@NonNull Context context,
+                              @Nullable AttributeSet attrs) {
+        super(context, attrs);
+        init();
+    }
+
+    public TexturePreviewView(@NonNull Context context, @Nullable AttributeSet attrs,
+                              @AttrRes int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        init();
+    }
+
+    private void init() {
+        textureView = new TextureView(getContext());
+        addView(textureView);
+    }
+
+    /**
+     * 有些ImageSource如系统相机前置设置头为镜面效果。这样换算坐标的时候会不一样
+     * @param mirrored 是否为镜面效果。
+     */
+    public void setMirrored(boolean mirrored) {
+        this.mirrored = mirrored;
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+        int selfWidth = getWidth();
+        int selfHeight = getHeight();
+        if (videoWidth == 0 || videoHeight == 0 || selfWidth == 0 || selfHeight == 0) {
+            return;
+        }
+        ScaleType scaleType = resolveScaleType();
+        if (scaleType == ScaleType.FIT_HEIGHT) {
+            int targetWith = videoWidth * selfHeight / videoHeight;
+            int delta = (targetWith - selfWidth) / 2;
+            textureView.layout(left - delta, top, right + delta, bottom);
+        } else {
+            int targetHeight = videoHeight * selfWidth / videoWidth;
+            int delta = (targetHeight - selfHeight) / 2;
+            textureView.layout(left, top - delta, right, bottom + delta);
+        }
+    }
+
+    @Override
+    public TextureView getTextureView() {
+        return textureView;
+    }
+
+    @Override
+    public void setPreviewSize(int width, int height) {
+        if (this.videoWidth == width && this.videoHeight == height) {
+            return;
+        }
+        this.videoWidth = width;
+        this.videoHeight = height;
+        handler.post(new Runnable() {
+            @Override
+            public void run() {
+                requestLayout();
+            }
+        });
+
+    }
+
+    @Override
+    public void mapToOriginalRect(RectF rectF) {
+
+        int selfWidth = getWidth();
+        int selfHeight = getHeight();
+        if (videoWidth == 0 || videoHeight == 0 || selfWidth == 0 || selfHeight == 0) {
+            return;
+            // TODO
+        }
+
+        Matrix matrix = new Matrix();
+        ScaleType scaleType = resolveScaleType();
+        if (scaleType == ScaleType.FIT_HEIGHT) {
+            int targetWith = videoWidth * selfHeight / videoHeight;
+            int delta = (targetWith - selfWidth) / 2;
+            float ratio = 1.0f * videoHeight / selfHeight;
+            matrix.postTranslate(delta, 0);
+            matrix.postScale(ratio, ratio);
+        } else {
+            int targetHeight = videoHeight * selfWidth / videoWidth;
+            int delta = (targetHeight - selfHeight) / 2;
+
+            float ratio = 1.0f * videoWidth / selfWidth;
+            matrix.postTranslate(0, delta);
+            matrix.postScale(ratio, ratio);
+        }
+        matrix.mapRect(rectF);
+    }
+
+    @Override
+    public void mapFromOriginalRect(RectF rectF) {
+        int selfWidth = getWidth();
+        int selfHeight = getHeight();
+        if (videoWidth == 0 || videoHeight == 0 || selfWidth == 0 || selfHeight == 0) {
+            return;
+            // TODO
+        }
+
+        Matrix matrix = new Matrix();
+
+        ScaleType scaleType = resolveScaleType();
+        if (scaleType == ScaleType.FIT_HEIGHT) {
+            int targetWith = videoWidth * selfHeight / videoHeight;
+            int delta = (targetWith - selfWidth) / 2;
+
+            float ratio = 1.0f * selfHeight / videoHeight;
+
+            matrix.postScale(ratio, ratio);
+            matrix.postTranslate(-delta, 0);
+        } else {
+            int targetHeight = videoHeight * selfWidth / videoWidth;
+            int delta = (targetHeight - selfHeight) / 2;
+
+            float ratio = 1.0f * selfWidth / videoWidth;
+
+            matrix.postScale(ratio, ratio);
+            matrix.postTranslate(0, -delta);
+        }
+        matrix.mapRect(rectF);
+
+        if (mirrored) {
+            float left = selfWidth - rectF.right;
+            float right = left + rectF.width();
+            rectF.left = left;
+            rectF.right = right;
+        }
+    }
+
+    @Override
+    public void mapFromOriginalRectEx(RectF rectF) {
+        int selfWidth = getWidth();
+        int selfHeight = getHeight();
+        if (videoWidth == 0 || videoHeight == 0 || selfWidth == 0 || selfHeight == 0) {
+            return;
+            // TODO
+        }
+        Matrix matrix = new Matrix();
+        float ratio = 1.0f * selfWidth / videoWidth;
+        matrix.postScale(ratio, ratio);
+      //  matrix.postTranslate(0, 0);
+        matrix.mapRect(rectF);
+
+        if (mirrored) {
+            float left = selfWidth - rectF.right;
+            float right = left + rectF.width();
+            rectF.left = left;
+            rectF.right = right;
+        }
+    }
+
+    @Override
+    public void setScaleType(ScaleType scaleType) {
+//        this.scaleType = scaleType;
+    }
+
+    @Override
+    public ScaleType getScaleType() {
+        return scaleType;
+    }
+
+    private ScaleType resolveScaleType() {
+        if (getHeight() <= 0 || videoHeight <= 0) {
+            return ScaleType.CROP_INSIDE;
+        }
+        float selfRatio = 1.0f * getWidth() / getHeight();
+        float targetRatio = 1.0f * videoWidth / videoHeight;
+
+        ScaleType scaleType = this.scaleType;
+        if (this.scaleType == ScaleType.CROP_INSIDE) {
+            scaleType = selfRatio > targetRatio ? ScaleType.FIT_WIDTH : ScaleType.FIT_HEIGHT;
+        }
+        return scaleType;
+    }
+
+    private ScaleType scaleType = ScaleType.CROP_INSIDE;
+    private Handler handler = new Handler(Looper.getMainLooper());
+
+}

+ 415 - 0
app_modular/facesdk/src/main/java/com/baidu/aip/face/camera/Camera1Control.java

@@ -0,0 +1,415 @@
+/*
+ * Copyright (C) 2017 Baidu, Inc. All Rights Reserved.
+ */
+package com.baidu.aip.face.camera;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import com.baidu.aip.face.PreviewView;
+
+import android.Manifest;
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.graphics.Rect;
+import android.graphics.SurfaceTexture;
+import android.hardware.Camera;
+import android.os.Build;
+import android.support.annotation.RequiresApi;
+import android.support.v4.app.ActivityCompat;
+import android.support.v4.content.ContextCompat;
+import android.util.SparseIntArray;
+import android.view.Surface;
+import android.view.TextureView;
+import android.view.View;
+
+/**
+ * 5.0以下相机API的封装。
+ */
+@SuppressWarnings("deprecation")
+@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+public class Camera1Control implements ICameraControl {
+
+    private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
+    private static final int MAX_PREVIEW_SIZE = 2048;
+
+    static {
+        ORIENTATIONS.append(Surface.ROTATION_0, 90);
+        ORIENTATIONS.append(Surface.ROTATION_90, 0);
+        ORIENTATIONS.append(Surface.ROTATION_180, 270);
+        ORIENTATIONS.append(Surface.ROTATION_270, 180);
+    }
+
+
+    private int displayOrientation = 0;
+    private int cameraId = 0;
+    private int flashMode;
+    private AtomicBoolean takingPicture = new AtomicBoolean(false);
+
+    private Context context;
+    private Camera camera;
+
+    private Camera.Parameters parameters;
+    private PermissionCallback permissionCallback;
+    private Rect previewFrame = new Rect();
+
+    private int preferredWidth = 1280;
+    private int preferredHeight = 720;
+
+    @ICameraControl.CameraFacing
+    private int cameraFacing = CAMERA_FACING_FRONT;
+
+    private boolean usbCamera = false;
+
+    @Override
+    public void setDisplayOrientation(@CameraView.Orientation int displayOrientation) {
+        this.displayOrientation = displayOrientation;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void refreshPermission() {
+        startPreview(true);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setFlashMode(@FlashMode int flashMode) {
+        if (this.flashMode == flashMode) {
+            return;
+        }
+        this.flashMode = flashMode;
+        updateFlashMode(flashMode);
+    }
+
+    @Override
+    public int getFlashMode() {
+        return flashMode;
+    }
+
+    @Override
+    public void setCameraFacing(@CameraFacing int cameraFacing) {
+        this.cameraFacing = cameraFacing;
+    }
+
+    @Override
+    public void setUsbCamera(boolean usbCamera) {
+        this.usbCamera = usbCamera;
+    }
+
+    @Override
+    public void start() {
+        startCamera();
+    }
+
+    public void isUsbCamera() {
+
+    }
+
+    private SurfaceTexture surfaceTexture;
+
+    private void startCamera() {
+        if (ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA)
+                != PackageManager.PERMISSION_GRANTED) {
+            if (permissionCallback != null) {
+                permissionCallback.onRequestPermission();
+            }
+            return;
+        }
+        if (camera == null) {
+            Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
+            for (int i = 0; i < Camera.getNumberOfCameras(); i++) {
+                Camera.getCameraInfo(i, cameraInfo);
+                if (cameraInfo.facing == cameraFacing) {
+                    cameraId = i;
+                }
+            }
+            camera = Camera.open(cameraId);
+        }
+        if (parameters == null) {
+            parameters = camera.getParameters();
+            parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
+        }
+
+        surfaceTexture = new SurfaceTexture(11);
+        parameters.setRotation(90); // TODO
+        int rotation = ORIENTATIONS.get(displayOrientation);
+
+        camera.setDisplayOrientation(rotation);
+        try {
+            camera.setPreviewCallback(new Camera.PreviewCallback() {
+                @Override
+                public void onPreviewFrame(byte[] data, Camera camera) {
+//                    Log.e("onPreviewFrame", "onPreviewFrame");
+                    Camera.Size size = camera.getParameters().getPreviewSize();
+
+                    int rotation = getSurfaceOrientation();
+                    if (cameraFacing == ICameraControl.CAMERA_FACING_FRONT) {
+                        // android自带的摄像头和接usb摄像头,图片有180旋转,用usb摄像头注释下面的代码
+                        if (!usbCamera) {
+                            if (rotation == 90 || rotation == 270) {
+                                rotation = (rotation + 180) % 360;
+                            }
+                        }
+                    }
+
+                    if (rotation % 180 == 90) {
+                        previewView.setPreviewSize(size.height, size.width);
+                    } else {
+                        previewView.setPreviewSize(size.width, size.height);
+                    }
+
+                    onFrameListener.onPreviewFrame(data, rotation, size.width, size.height);
+                }
+            });
+            camera.setPreviewTexture(surfaceTexture);
+            if (textureView != null) {
+                surfaceTexture.detachFromGLContext();
+                textureView.setSurfaceTexture(surfaceTexture);
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        opPreviewSize(preferredWidth, preferredHeight);
+    }
+
+    private TextureView textureView;
+
+    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
+    public void setTextureView(TextureView textureView) {
+        this.textureView = textureView;
+        if (surfaceTexture != null) {
+            surfaceTexture.detachFromGLContext();
+            textureView.setSurfaceTexture(surfaceTexture);
+        }
+    }
+
+    @Override
+    public void stop() {
+        if (camera != null) {
+            camera.stopPreview();
+            camera.setPreviewCallback(null);
+            camera.release();
+            camera = null;
+        }
+    }
+
+    @Override
+    public void pause() {
+        if (camera != null) {
+            camera.stopPreview();
+        }
+        setFlashMode(FLASH_MODE_OFF);
+    }
+
+    @Override
+    public void resume() {
+        takingPicture.set(false);
+        if (camera == null) {
+            startCamera();
+        }
+    }
+
+    private OnFrameListener onFrameListener;
+
+    @Override
+    public void setOnFrameListener(OnFrameListener listener) {
+        this.onFrameListener = listener;
+    }
+
+    @Override
+    public void setPreferredPreviewSize(int width, int height) {
+        this.preferredWidth = Math.max(width, height);
+        this.preferredHeight = Math.min(width, height);
+    }
+
+    @Override
+    public View getDisplayView() {
+        return null;
+    }
+
+
+    private PreviewView previewView;
+
+    @Override
+    public void setPreviewView(PreviewView previewView) {
+        this.previewView = previewView;
+        setTextureView(previewView.getTextureView());
+    }
+
+    @Override
+    public PreviewView getPreviewView() {
+        return previewView;
+    }
+
+    @Override
+    public void takePicture(final OnTakePictureCallback onTakePictureCallback) {
+        if (takingPicture.get()) {
+            return;
+        }
+
+        switch (displayOrientation) {
+            case CameraView.ORIENTATION_PORTRAIT:
+                parameters.setRotation(90);
+                break;
+            case CameraView.ORIENTATION_HORIZONTAL:
+                parameters.setRotation(0);
+                break;
+            case CameraView.ORIENTATION_INVERT:
+                parameters.setRotation(180);
+                break;
+            default:
+                parameters.setRotation(90);
+                break;
+        }
+        Camera.Size picSize =
+                getOptimalSize(preferredWidth, preferredHeight, camera.getParameters().getSupportedPictureSizes());
+        parameters.setPictureSize(picSize.width, picSize.height);
+        camera.setParameters(parameters);
+        takingPicture.set(true);
+        camera.autoFocus(new Camera.AutoFocusCallback() {
+            @Override
+            public void onAutoFocus(boolean success, Camera camera) {
+                camera.cancelAutoFocus();
+                try {
+                    camera.takePicture(null, null, new Camera.PictureCallback() {
+                        @Override
+                        public void onPictureTaken(byte[] data, Camera camera) {
+                            camera.startPreview();
+                            takingPicture.set(false);
+                            if (onTakePictureCallback != null) {
+                                onTakePictureCallback.onPictureTaken(data);
+                            }
+                        }
+                    });
+                } catch (RuntimeException e) {
+                    e.printStackTrace();
+                    camera.startPreview();
+                    takingPicture.set(false);
+                }
+            }
+        });
+    }
+
+    @Override
+    public void setPermissionCallback(PermissionCallback callback) {
+        this.permissionCallback = callback;
+    }
+
+    public Camera1Control(Context context) {
+        this.context = context;
+    }
+
+    // 开启预览
+    private void startPreview(boolean checkPermission) {
+        if (ActivityCompat.checkSelfPermission(context, Manifest.permission.CAMERA)
+                != PackageManager.PERMISSION_GRANTED) {
+            if (checkPermission && permissionCallback != null) {
+                permissionCallback.onRequestPermission();
+            }
+            return;
+        }
+        camera.startPreview();
+    }
+
+    private void opPreviewSize(int width, @SuppressWarnings("unused") int height) {
+        Camera.Size optSize;
+        if (parameters != null && camera != null && width > 0) {
+            optSize = getOptimalSize(width, height, camera.getParameters().getSupportedPreviewSizes());
+
+            parameters.setPreviewSize(optSize.width, optSize.height);
+
+//            camera.setDisplayOrientation(getSurfaceOrientation());
+            //            camera.stopPreview();
+            try {
+                camera.setParameters(parameters);
+            } catch (RuntimeException e) {
+                e.printStackTrace();
+
+            }
+            camera.startPreview();
+        }
+    }
+
+    private Camera.Size getOptimalSize(int width, int height, List<Camera.Size> sizes) {
+
+        Camera.Size pictureSize = sizes.get(0);
+
+        List<Camera.Size> candidates = new ArrayList<>();
+
+        for (Camera.Size size : sizes) {
+            if (size.width >= width && size.height >= height && size.width * height == size.height * width) {
+                // 比例相同
+                candidates.add(size);
+            } else if (size.height >= width && size.width >= height && size.width * width == size.height * height) {
+                // 反比例
+                candidates.add(size);
+            }
+        }
+        if (!candidates.isEmpty()) {
+            return Collections.min(candidates, sizeComparator);
+        }
+
+        for (Camera.Size size : sizes) {
+            if (size.width > width && size.height > height) {
+                return size;
+            }
+        }
+
+        return pictureSize;
+    }
+
+    private Comparator<Camera.Size> sizeComparator = new Comparator<Camera.Size>() {
+        @Override
+        public int compare(Camera.Size lhs, Camera.Size rhs) {
+            return Long.signum((long) lhs.width * lhs.height - (long) rhs.width * rhs.height);
+        }
+    };
+
+    private void updateFlashMode(int flashMode) {
+        switch (flashMode) {
+            case FLASH_MODE_TORCH:
+                parameters.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);
+                break;
+            case FLASH_MODE_OFF:
+                parameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
+                break;
+            case ICameraControl.FLASH_MODE_AUTO:
+                parameters.setFlashMode(Camera.Parameters.FLASH_MODE_AUTO);
+                break;
+            default:
+                parameters.setFlashMode(Camera.Parameters.FLASH_MODE_AUTO);
+                break;
+        }
+        camera.setParameters(parameters);
+    }
+
+    protected int getSurfaceOrientation() {
+        @CameraView.Orientation
+        int orientation = displayOrientation;
+        switch (orientation) {
+            case CameraView.ORIENTATION_PORTRAIT:
+                return 90;
+            case CameraView.ORIENTATION_HORIZONTAL:
+                return 0;
+            case CameraView.ORIENTATION_INVERT:
+                return 180;
+            default:
+                return 90;
+        }
+    }
+
+    @Override
+    public Rect getPreviewFrame() {
+        return previewFrame;
+    }
+}

+ 769 - 0
app_modular/facesdk/src/main/java/com/baidu/aip/face/camera/Camera2Control.java

@@ -0,0 +1,769 @@
+/*
+ * Copyright (C) 2017 Baidu, Inc. All Rights Reserved.
+ */
+
+package com.baidu.aip.face.camera;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+import com.baidu.aip.face.PreviewView;
+
+import android.Manifest;
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.graphics.ImageFormat;
+import android.graphics.Matrix;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.SurfaceTexture;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CameraMetadata;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.TotalCaptureResult;
+import android.hardware.camera2.params.StreamConfigurationMap;
+import android.media.Image;
+import android.media.ImageReader;
+import android.media.ImageReader.OnImageAvailableListener;
+import android.os.Build;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.support.annotation.NonNull;
+import android.support.v4.content.ContextCompat;
+import android.util.Log;
+import android.util.Size;
+import android.util.SparseIntArray;
+import android.view.Surface;
+import android.view.TextureView;
+import android.view.View;
+import android.view.WindowManager;
+
+@TargetApi(Build.VERSION_CODES.LOLLIPOP)
+public class Camera2Control implements ICameraControl {
+
+    /**
+     * Conversion from screen rotation to JPEG orientation.
+     */
+    private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
+    private static final int MAX_PREVIEW_SIZE = 2048;
+
+    static {
+        ORIENTATIONS.append(Surface.ROTATION_0, 90);
+        ORIENTATIONS.append(Surface.ROTATION_90, 0);
+        ORIENTATIONS.append(Surface.ROTATION_180, 270);
+        ORIENTATIONS.append(Surface.ROTATION_270, 180);
+    }
+
+    private static final int STATE_PREVIEW = 0;
+    private static final int STATE_WAITING_FOR_LOCK = 1;
+    private static final int STATE_WAITING_FOR_CAPTURE = 2;
+    private static final int STATE_CAPTURING = 3;
+    private static final int STATE_PICTURE_TAKEN = 4;
+
+    private static final int MAX_PREVIEW_WIDTH = 1920;
+    private static final int MAX_PREVIEW_HEIGHT = 1080;
+
+    private int flashMode;
+    private int orientation = 0;
+    private int state = STATE_PREVIEW;
+
+    private Context context;
+    private OnTakePictureCallback onTakePictureCallback;
+    private PermissionCallback permissionCallback;
+    private SurfaceTexture surfaceTexture;
+
+    private String cameraId;
+    private TextureView textureView;
+    private Size previewSize;
+
+    private HandlerThread backgroundThread;
+    private Handler backgroundHandler;
+    private ImageReader imageReader;
+    private CameraCaptureSession captureSession;
+    private CameraDevice cameraDevice;
+
+    private CaptureRequest.Builder previewRequestBuilder;
+    private CaptureRequest previewRequest;
+
+    private Semaphore cameraLock = new Semaphore(1);
+    private int sensorOrientation;
+
+    private int camFacing = CameraCharacteristics.LENS_FACING_BACK;
+
+    private Handler handler = new Handler(Looper.getMainLooper());
+
+    private int preferredWidth = 1280;
+    private int preferredHeight = 720;
+
+    private boolean usbCamera = false;
+
+    public void switchCamera() {
+        if (camFacing == CameraCharacteristics.LENS_FACING_BACK) {
+            camFacing = CameraCharacteristics.LENS_FACING_FRONT;
+        } else {
+            camFacing = CameraCharacteristics.LENS_FACING_BACK;
+        }
+        //        openCamera(textureView.getWidth(), textureView.getHeight());
+        stop();
+        handler.postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                start();
+            }
+        }, 800);
+    }
+
+    @Override
+    public void start() {
+        startBackgroundThread();
+        openCamera(preferredWidth, preferredHeight);
+    }
+
+    @Override
+    public void stop() {
+        if (imageReader != null) {
+            imageReader.close();
+            closeCamera();
+            stopBackgroundThread();
+            imageReader = null;
+        }
+    }
+
+    @Override
+    public void pause() {
+        setFlashMode(FLASH_MODE_OFF);
+    }
+
+    @Override
+    public void resume() {
+        state = STATE_PREVIEW;
+    }
+
+    @Override
+    public void setOnFrameListener(OnFrameListener listener) {
+        this.onFrameListener = listener;
+    }
+
+    @Override
+    public void setPreferredPreviewSize(int width, int height) {
+        this.preferredWidth = Math.max(width, height);
+        this.preferredHeight = Math.min(width, height);
+    }
+
+    private OnFrameListener<Image> onFrameListener;
+
+    @Override
+    public View getDisplayView() {
+        return textureView;
+    }
+
+    private PreviewView previewView;
+
+    @Override
+    public void setPreviewView(PreviewView previewView) {
+        this.previewView = previewView;
+        textureView = previewView.getTextureView();
+        if (surfaceTexture != null) {
+            surfaceTexture.detachFromGLContext();
+            textureView.setSurfaceTexture(surfaceTexture);
+        }
+        textureView.setSurfaceTextureListener(surfaceTextureListener);
+    }
+
+    @Override
+    public PreviewView getPreviewView() {
+        return previewView;
+    }
+
+    @Override
+    public Rect getPreviewFrame() {
+        return null;
+    }
+
+    @Override
+    public void takePicture(OnTakePictureCallback callback) {
+        this.onTakePictureCallback = callback;
+        // 拍照第一步,对焦
+        lockFocus();
+    }
+
+    @Override
+    public void setPermissionCallback(PermissionCallback callback) {
+        this.permissionCallback = callback;
+    }
+
+    @Override
+    public void setDisplayOrientation(@CameraView.Orientation int displayOrientation) {
+        this.orientation = displayOrientation / 90;
+    }
+
+    @Override
+    public void refreshPermission() {
+        openCamera(preferredWidth, preferredHeight);
+    }
+
+    @Override
+    public void setFlashMode(@FlashMode int flashMode) {
+        if (this.flashMode == flashMode) {
+            return;
+        }
+        this.flashMode = flashMode;
+        try {
+            previewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
+                    CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
+            updateFlashMode(flashMode, previewRequestBuilder);
+            previewRequest = previewRequestBuilder.build();
+            captureSession.setRepeatingRequest(previewRequest, captureCallback, backgroundHandler);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public int getFlashMode() {
+        return flashMode;
+    }
+
+    @Override
+    public void setCameraFacing(int cameraFacing) {
+        camFacing = cameraFacing == CAMERA_FACING_BACK ? CameraCharacteristics.LENS_FACING_FRONT :
+                CameraCharacteristics.LENS_FACING_BACK;
+    }
+
+    @Override
+    public void setUsbCamera(boolean usbCamera) {
+        this.usbCamera = usbCamera;
+    }
+
+    public Camera2Control(Context activity) {
+        this.context = activity;
+    }
+
+    private final TextureView.SurfaceTextureListener surfaceTextureListener =
+            new TextureView.SurfaceTextureListener() {
+                @Override
+                public void onSurfaceTextureAvailable(SurfaceTexture texture, int width, int height) {
+                    //                    openCamera(width, height);
+                }
+
+                @Override
+                public void onSurfaceTextureSizeChanged(SurfaceTexture texture, int width, int height) {
+                    configureTransform(width, height);
+                }
+
+                @Override
+                public boolean onSurfaceTextureDestroyed(SurfaceTexture texture) {
+                    stop();
+                    return false;
+                }
+
+                @Override
+                public void onSurfaceTextureUpdated(SurfaceTexture texture) {
+                }
+            };
+
+    private void openCamera(int width, int height) {
+        // 6.0+的系统需要检查系统权限 。
+        if (ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA)
+                != PackageManager.PERMISSION_GRANTED) {
+            requestCameraPermission();
+            return;
+        }
+        setUpCameraOutputs(width, height);
+        configureTransform(width, height);
+        CameraManager manager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
+        try {
+            if (!cameraLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
+                throw new RuntimeException("Time out waiting to lock camera opening.");
+            }
+            manager.openCamera(cameraId, deviceStateCallback, backgroundHandler);
+        } catch (CameraAccessException e) {
+            e.printStackTrace();
+        } catch (InterruptedException e) {
+            throw new RuntimeException("Interrupted while trying to lock camera opening.", e);
+        }
+    }
+
+    private final CameraDevice.StateCallback deviceStateCallback = new CameraDevice.StateCallback() {
+        @Override
+        public void onOpened(@NonNull CameraDevice cameraDevice) {
+            cameraLock.release();
+            Camera2Control.this.cameraDevice = cameraDevice;
+            createCameraPreviewSession();
+        }
+
+        @Override
+        public void onDisconnected(@NonNull CameraDevice cameraDevice) {
+            cameraLock.release();
+            cameraDevice.close();
+            Camera2Control.this.cameraDevice = null;
+        }
+
+        @Override
+        public void onError(@NonNull CameraDevice cameraDevice, int error) {
+            cameraLock.release();
+            cameraDevice.close();
+            Camera2Control.this.cameraDevice = null;
+        }
+    };
+
+    private void createCameraPreviewSession() {
+        try {
+            if (surfaceTexture == null) {
+                surfaceTexture = new SurfaceTexture(11); // TODO
+            }
+
+            if (textureView != null) {
+                handler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        try {
+                            surfaceTexture.detachFromGLContext();
+                        } catch (Exception e) {
+                            e.printStackTrace();
+                        }
+                        if (textureView.getSurfaceTexture() != surfaceTexture) {
+                            textureView.setSurfaceTexture(surfaceTexture);
+                        }
+                    }
+                });
+            }
+
+            Surface surface = new Surface(surfaceTexture);
+            int rotation = ORIENTATIONS.get(orientation);
+            if (rotation % 180 == 90) {
+                surfaceTexture.setDefaultBufferSize(preferredWidth, preferredHeight);
+            } else {
+                surfaceTexture.setDefaultBufferSize(preferredHeight, preferredWidth);
+            }
+            previewRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+            previewRequestBuilder.addTarget(surface);
+
+            imageReader =
+                    ImageReader
+                            .newInstance(preferredWidth, preferredHeight, ImageFormat.YUV_420_888, 1);
+            imageReader.setOnImageAvailableListener(new OnImageAvailableListener() {
+                @Override
+                public void onImageAvailable(ImageReader reader) {
+                    Image image = reader.acquireLatestImage();
+
+                    int rotation = ORIENTATIONS.get(orientation);
+                    if (camFacing == ICameraControl.CAMERA_FACING_FRONT) {
+                        if (rotation == 90 || rotation == 270) {
+                            rotation = (rotation + 180) % 360;
+                        }
+                    }
+
+                    Log.e("xx", "sensorOrientation" + sensorOrientation);
+                    Log.e("xx", "sensorOrientation" + orientation * 90);
+                    if (rotation % 180 == 90) {
+                        previewView.setPreviewSize(image.getHeight(), image.getWidth());
+                    } else {
+                        previewView.setPreviewSize(image.getWidth(), image.getHeight());
+                    }
+
+                    if (onFrameListener != null) {
+                        onFrameListener.onPreviewFrame(image, rotation, image.getWidth(), image.getHeight());
+                    }
+                    image.close();
+                }
+            }, backgroundHandler);
+
+            previewRequestBuilder.addTarget(imageReader.getSurface());
+
+            updateFlashMode(this.flashMode, previewRequestBuilder);
+
+            cameraDevice.createCaptureSession(Arrays.asList(surface, imageReader.getSurface()),
+                    new CameraCaptureSession.StateCallback() {
+
+                        @Override
+                        public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) {
+                            // The camera is already closed
+                            if (null == cameraDevice) {
+                                return;
+                            }
+                            captureSession = cameraCaptureSession;
+                            try {
+                                previewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
+                                        CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
+
+                                previewRequest = previewRequestBuilder.build();
+                                captureSession.setRepeatingRequest(previewRequest,
+                                        captureCallback, backgroundHandler);
+                            } catch (CameraAccessException e) {
+                                e.printStackTrace();
+                            }
+                        }
+
+                        @Override
+                        public void onConfigureFailed(
+                                @NonNull CameraCaptureSession cameraCaptureSession) {
+                            Log.e("xx", "onConfigureFailed" + cameraCaptureSession);
+                        }
+                    }, backgroundHandler
+            );
+        } catch (CameraAccessException e) {
+            e.printStackTrace();
+        }
+    }
+
+    private CameraCaptureSession.CaptureCallback captureCallback =
+            new CameraCaptureSession.CaptureCallback() {
+                private void process(CaptureResult result) {
+                    switch (state) {
+                        case STATE_PREVIEW: {
+                            break;
+                        }
+                        case STATE_WAITING_FOR_LOCK: {
+                            Integer afState = result.get(CaptureResult.CONTROL_AF_STATE);
+                            if (afState == null || afState == CaptureResult.CONTROL_AF_STATE_PASSIVE_SCAN) {
+                                captureStillPicture();
+                            } else if (CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED == afState
+                                    || CaptureRequest.CONTROL_AF_STATE_INACTIVE == afState
+                                    || CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED == afState
+                                    || CaptureResult.CONTROL_AF_STATE_PASSIVE_FOCUSED == afState) {
+                                Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
+                                if (aeState == null
+                                        || aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) {
+                                    captureStillPicture();
+                                } else {
+                                    runPreCaptureSequence();
+                                }
+                            }
+                            break;
+                        }
+                        case STATE_WAITING_FOR_CAPTURE: {
+                            Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
+                            if (aeState == null
+                                    || aeState == CaptureResult.CONTROL_AE_STATE_PRECAPTURE
+                                    || aeState == CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED) {
+                                state = STATE_CAPTURING;
+                            } else {
+                                if (aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) {
+                                    captureStillPicture();
+                                }
+                            }
+                            break;
+                        }
+                        case STATE_CAPTURING: {
+                            Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
+                            if (aeState == null || aeState != CaptureResult.CONTROL_AE_STATE_PRECAPTURE) {
+                                captureStillPicture();
+                            }
+                            break;
+                        }
+                        default:
+                            break;
+                    }
+                }
+
+                @Override
+                public void onCaptureProgressed(@NonNull CameraCaptureSession session,
+                                                @NonNull CaptureRequest request,
+                                                @NonNull CaptureResult partialResult) {
+                    process(partialResult);
+                }
+
+                @Override
+                public void onCaptureCompleted(@NonNull CameraCaptureSession session,
+                                               @NonNull CaptureRequest request,
+                                               @NonNull TotalCaptureResult result) {
+                    process(result);
+                }
+
+            };
+
+    private Size getOptimalSize(Size[] choices, int textureViewWidth,
+                                int textureViewHeight, int maxWidth, int maxHeight, Size aspectRatio) {
+        List<Size> bigEnough = new ArrayList<>();
+        List<Size> notBigEnough = new ArrayList<>();
+        int w = aspectRatio.getWidth();
+        int h = aspectRatio.getHeight();
+        for (Size option : choices) {
+            if (option.getWidth() <= maxWidth && option.getHeight() <= maxHeight
+                    && option.getHeight() == option.getWidth() * h / w) {
+                if (option.getWidth() >= textureViewWidth
+                        && option.getHeight() >= textureViewHeight) {
+                    bigEnough.add(option);
+                } else {
+                    notBigEnough.add(option);
+                }
+            }
+        }
+
+        // Pick the smallest of those big enough. If there is no one big enough, pick the
+        // largest of those not big enough.
+        if (bigEnough.size() > 0) {
+            return Collections.min(bigEnough, sizeComparator);
+        }
+
+        for (Size option : choices) {
+            if (option.getWidth() > maxWidth && option.getHeight() > maxHeight) {
+                return option;
+            }
+        }
+
+        if (notBigEnough.size() > 0) {
+            return Collections.max(notBigEnough, sizeComparator);
+        }
+
+        return choices[0];
+    }
+
+    private Comparator<Size> sizeComparator = new Comparator<Size>() {
+        @Override
+        public int compare(Size lhs, Size rhs) {
+            return Long.signum((long) lhs.getWidth() * lhs.getHeight() - (long) rhs.getWidth() * rhs.getHeight());
+        }
+    };
+
+    private void requestCameraPermission() {
+        if (permissionCallback != null) {
+            permissionCallback.onRequestPermission();
+        }
+    }
+
+    @SuppressWarnings("SuspiciousNameCombination")
+    private void setUpCameraOutputs(int width, int height) {
+        CameraManager manager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
+        try {
+            for (String cameraId : manager.getCameraIdList()) {
+                CameraCharacteristics characteristics =
+                        manager.getCameraCharacteristics(cameraId);
+
+                Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING);
+                if (facing != null && facing == camFacing) {
+                    continue;
+                }
+
+                StreamConfigurationMap map = characteristics.get(
+                        CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+                if (map == null) {
+                    continue;
+                }
+
+                WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+                Point screenSize = new Point();
+                windowManager.getDefaultDisplay().getSize(screenSize);
+                int maxImageSize = Math.max(MAX_PREVIEW_SIZE, screenSize.y * 2 / 3);
+
+                Size size = getOptimalSize(map.getOutputSizes(ImageFormat.JPEG), textureView.getWidth(),
+                        textureView.getHeight(), maxImageSize, maxImageSize, new Size(4, 3));
+
+                int displayRotation = orientation;
+                // noinspection ConstantConditions
+                sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
+                boolean swappedDimensions = false;
+                switch (displayRotation) {
+                    case Surface.ROTATION_0:
+                    case Surface.ROTATION_180:
+                        if (sensorOrientation == 90 || sensorOrientation == 270) {
+                            swappedDimensions = true;
+                        }
+                        break;
+                    case Surface.ROTATION_90:
+                    case Surface.ROTATION_270:
+                        if (sensorOrientation == 0 || sensorOrientation == 180) {
+                            swappedDimensions = true;
+                        }
+                        break;
+                    default:
+                }
+                //                orientation = sensorOrientation;
+
+                int rotatedPreviewWidth = width;
+                int rotatedPreviewHeight = height;
+                int maxPreviewWidth = screenSize.x;
+                int maxPreviewHeight = screenSize.y;
+
+                if (swappedDimensions) {
+                    rotatedPreviewWidth = height;
+                    rotatedPreviewHeight = width;
+                    maxPreviewWidth = screenSize.y;
+                    maxPreviewHeight = screenSize.x;
+                }
+
+                maxPreviewWidth = Math.min(maxPreviewWidth, MAX_PREVIEW_WIDTH);
+                maxPreviewHeight = Math.min(maxPreviewHeight, MAX_PREVIEW_HEIGHT);
+
+                previewSize = getOptimalSize(map.getOutputSizes(SurfaceTexture.class),
+                        rotatedPreviewWidth, rotatedPreviewHeight, maxPreviewWidth,
+                        maxPreviewHeight, size);
+                this.cameraId = cameraId;
+
+                return;
+            }
+        } catch (CameraAccessException | NullPointerException e) {
+            e.printStackTrace();
+        }
+    }
+
+    private void closeCamera() {
+        try {
+            cameraLock.acquire();
+            if (null != captureSession) {
+                captureSession.close();
+                captureSession = null;
+            }
+            if (null != cameraDevice) {
+                cameraDevice.close();
+                cameraDevice = null;
+            }
+            if (null != imageReader) {
+                imageReader.close();
+                imageReader = null;
+            }
+        } catch (InterruptedException e) {
+            throw new RuntimeException("Interrupted while trying to lock camera closing.", e);
+        } finally {
+            cameraLock.release();
+        }
+    }
+
+    private void startBackgroundThread() {
+        backgroundThread = new HandlerThread("ocr_camera");
+        backgroundThread.start();
+        backgroundHandler = new Handler(backgroundThread.getLooper());
+    }
+
+    private void stopBackgroundThread() {
+        if (backgroundThread != null) {
+            backgroundThread.quitSafely();
+            backgroundThread = null;
+            backgroundHandler = null;
+        }
+    }
+
+    private Matrix matrix = new Matrix();
+
+    private void configureTransform(int viewWidth, int viewHeight) {
+        if (null == textureView || null == previewSize || null == context) {
+            return;
+        }
+        int rotation = orientation;
+
+        RectF viewRect = new RectF(0, 0, viewWidth, viewHeight);
+        RectF bufferRect = new RectF(0, 0, previewSize.getHeight(), previewSize.getWidth());
+        float centerX = viewRect.centerX();
+        float centerY = viewRect.centerY();
+        if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) {
+            bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());
+            matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
+            float scale = Math.max(
+                    (float) viewHeight / previewSize.getHeight(),
+                    (float) viewWidth / previewSize.getWidth());
+            matrix.postScale(scale, scale, centerX, centerY);
+            matrix.postRotate(90 * (rotation - 2), centerX, centerY);
+        } else if (Surface.ROTATION_180 == rotation) {
+            matrix.postRotate(180, centerX, centerY);
+        }
+        textureView.setTransform(matrix);
+    }
+
+    // 拍照前,先对焦
+    private void lockFocus() {
+        if (captureSession != null && state == STATE_PREVIEW) {
+            try {
+                previewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
+                        CameraMetadata.CONTROL_AF_TRIGGER_START);
+                state = STATE_WAITING_FOR_LOCK;
+                captureSession.capture(previewRequestBuilder.build(), captureCallback,
+                        backgroundHandler);
+            } catch (CameraAccessException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    private void runPreCaptureSequence() {
+        try {
+            previewRequestBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
+                    CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START);
+            state = STATE_WAITING_FOR_CAPTURE;
+            captureSession.capture(previewRequestBuilder.build(), captureCallback,
+                    backgroundHandler);
+        } catch (CameraAccessException e) {
+            e.printStackTrace();
+        }
+    }
+
+    // 拍照session
+    private void captureStillPicture() {
+        try {
+            if (null == context || null == cameraDevice) {
+                return;
+            }
+            final CaptureRequest.Builder captureBuilder =
+                    cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
+            captureBuilder.set(CaptureRequest.CONTROL_AF_MODE,
+                    CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
+
+            captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getOrientation(orientation));
+            updateFlashMode(this.flashMode, captureBuilder);
+            CameraCaptureSession.CaptureCallback captureCallback =
+                    new CameraCaptureSession.CaptureCallback() {
+                        @Override
+                        public void onCaptureCompleted(@NonNull CameraCaptureSession session,
+                                                       @NonNull CaptureRequest request,
+                                                       @NonNull TotalCaptureResult result) {
+                            unlockFocus();
+                        }
+                    };
+
+            // 停止预览
+            captureSession.stopRepeating();
+            captureSession.capture(captureBuilder.build(), captureCallback, backgroundHandler);
+            state = STATE_PICTURE_TAKEN;
+        } catch (CameraAccessException e) {
+            e.printStackTrace();
+        }
+    }
+
+    private int getOrientation(int rotation) {
+        return (ORIENTATIONS.get(rotation) + sensorOrientation + 270) % 360;
+    }
+
+    // 停止对焦
+    private void unlockFocus() {
+        try {
+            previewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
+                    CameraMetadata.CONTROL_AF_TRIGGER_CANCEL);
+            captureSession.capture(previewRequestBuilder.build(), captureCallback,
+                    backgroundHandler);
+            state = STATE_PREVIEW;
+            // 预览
+            captureSession.setRepeatingRequest(previewRequest, captureCallback,
+                    backgroundHandler);
+        } catch (CameraAccessException e) {
+            e.printStackTrace();
+        }
+    }
+
+    private void updateFlashMode(@FlashMode int flashMode, CaptureRequest.Builder builder) {
+        switch (flashMode) {
+            case FLASH_MODE_TORCH:
+                builder.set(CaptureRequest.FLASH_MODE, CameraMetadata.FLASH_MODE_TORCH);
+                break;
+            case FLASH_MODE_OFF:
+                builder.set(CaptureRequest.FLASH_MODE, CameraMetadata.FLASH_MODE_OFF);
+                break;
+            case ICameraControl.FLASH_MODE_AUTO:
+            default:
+                builder.set(CaptureRequest.FLASH_MODE, CameraMetadata.FLASH_MODE_SINGLE);
+                break;
+        }
+    }
+}

+ 156 - 0
app_modular/facesdk/src/main/java/com/baidu/aip/face/camera/CameraView.java

@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2017 Baidu, Inc. All Rights Reserved.
+ */
+package com.baidu.aip.face.camera;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.os.Build;
+import android.support.annotation.IntDef;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+
+/**
+ * 负责,相机的管理。同时提供,裁剪遮罩功能。
+ */
+public class CameraView extends FrameLayout {
+
+    /**
+     * 照相回调
+     */
+    interface OnTakePictureCallback {
+        void onPictureTaken(Bitmap bitmap);
+    }
+
+    /**
+     * 垂直方向 {@link #setOrientation(int)}
+     */
+    public static final int ORIENTATION_PORTRAIT = 0;
+    /**
+     * 水平方向 {@link #setOrientation(int)}
+     */
+    public static final int ORIENTATION_HORIZONTAL = 90;
+    /**
+     * 水平翻转方向 {@link #setOrientation(int)}
+     */
+    public static final int ORIENTATION_INVERT = 270;
+
+    @IntDef({ORIENTATION_PORTRAIT, ORIENTATION_HORIZONTAL, ORIENTATION_INVERT})
+    public @interface Orientation {
+
+    }
+
+    private CameraViewTakePictureCallback cameraViewTakePictureCallback = new CameraViewTakePictureCallback();
+
+    private ICameraControl cameraControl;
+
+    /**
+     * 相机预览View
+     */
+    private View displayView;
+    /**
+     * 身份证,银行卡,等裁剪用的遮罩
+     */
+//    private MaskView maskView;
+
+    /**
+     * 用于显示提示证 "请对齐身份证正面" 之类的
+     */
+    private ImageView hintView;
+
+    public ICameraControl getCameraControl() {
+        return cameraControl;
+    }
+
+    public void setOrientation(@Orientation int orientation) {
+        cameraControl.setDisplayOrientation(orientation);
+    }
+
+    public CameraView(Context context) {
+        super(context);
+        init();
+    }
+
+    public CameraView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init();
+    }
+
+    public CameraView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        init();
+    }
+
+    public void start() {
+        cameraControl.start();
+        setKeepScreenOn(true);
+    }
+
+    public void stop() {
+        cameraControl.stop();
+        setKeepScreenOn(false);
+    }
+
+
+    private void init() {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+            cameraControl = new Camera2Control(getContext());
+        } else {
+            cameraControl = new Camera1Control(getContext());
+        }
+        displayView = cameraControl.getDisplayView();
+        addView(displayView);
+
+
+        hintView = new ImageView(getContext());
+        addView(hintView);
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        displayView.layout(left, 0, right, bottom - top);
+    }
+
+//    @Override
+//    protected void onDetachedFromWindow() {
+//        super.onDetachedFromWindow();
+//        if (cameraViewTakePictureCallback.thread != null) {
+//            cameraViewTakePictureCallback.thread.quit();
+//        }
+//    }
+
+    private class CameraViewTakePictureCallback implements ICameraControl.OnTakePictureCallback {
+
+//        private File file;
+//        private OnTakePictureCallback callback;
+//
+//        HandlerThread thread = new HandlerThread("cropThread");
+//        Handler handler;
+//
+//        {
+//            thread.start();
+//            handler = new Handler(thread.getLooper());
+//        }
+
+        @Override
+        public void onPictureTaken(final byte[] data) {
+//            handler.post(new Runnable() {
+//                @Override
+//                public void run() {
+//                    try {
+//                        final int rotation = ImageUtil.getOrientation(data);
+//                        final File tempFile = File.createTempFile(String.valueOf(System.currentTimeMillis()), "jpg");
+//                        FileOutputStream fileOutputStream = new FileOutputStream(tempFile);
+//                        fileOutputStream.write(data);
+//                        fileOutputStream.flush();
+//                        fileOutputStream.close();
+//                    } catch (IOException e) {
+//                        e.printStackTrace();
+//                    }
+//                }
+//            });
+        }
+    }
+}

+ 135 - 0
app_modular/facesdk/src/main/java/com/baidu/aip/face/camera/ICameraControl.java

@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2017 Baidu, Inc. All Rights Reserved.
+ */
+package com.baidu.aip.face.camera;
+
+import com.baidu.aip.face.PreviewView;
+
+import android.graphics.Rect;
+import android.support.annotation.IntDef;
+import android.view.View;
+
+/**
+ * Android 5.0 相机的API发生很大的变化。些类屏蔽掉了 api的变化。相机的操作和功能,抽象剥离出来。
+ */
+public interface ICameraControl<T> {
+
+    interface OnFrameListener<T> {
+        void onPreviewFrame(T data, int rotation, int width, int height);
+    }
+
+    /**
+     * 闪光灯关 {@link #setFlashMode(int)}
+     */
+    int FLASH_MODE_OFF = 0;
+    /**
+     * 闪光灯开 {@link #setFlashMode(int)}
+     */
+    int FLASH_MODE_TORCH = 1;
+    /**
+     * 闪光灯自动 {@link #setFlashMode(int)}
+     */
+    int FLASH_MODE_AUTO = 2;
+
+    @IntDef({FLASH_MODE_TORCH, FLASH_MODE_OFF, FLASH_MODE_AUTO})
+    @interface FlashMode {
+
+    }
+
+    int CAMERA_FACING_BACK = 0;
+
+    int CAMERA_FACING_FRONT = 1;
+
+    @IntDef({CAMERA_FACING_FRONT, CAMERA_FACING_BACK})
+    @interface CameraFacing {
+
+    }
+
+    /**
+     * 照相回调。
+     */
+    interface OnTakePictureCallback {
+        void onPictureTaken(byte[] data);
+    }
+
+    /**
+     * 打开相机。
+     */
+    void start();
+
+    /**
+     * 关闭相机
+     */
+    void stop();
+
+    void pause();
+
+    void resume();
+
+    void setOnFrameListener(OnFrameListener listener);
+
+    void setPreferredPreviewSize(int width, int height);
+
+    /**
+     * 相机对应的预览视图。
+     *
+     * @return 预览视图
+     */
+    View getDisplayView();
+
+    void setPreviewView(PreviewView previewView);
+
+    PreviewView getPreviewView();
+
+    /**
+     * 看到的预览可能不是照片的全部。返回预览视图的全貌。
+     *
+     * @return 预览视图frame;
+     */
+    Rect getPreviewFrame();
+
+    /**
+     * 拍照。结果在回调中获取。
+     *
+     * @param callback 拍照结果回调
+     */
+    void takePicture(OnTakePictureCallback callback);
+
+    /**
+     * 设置权限回调,当手机没有拍照权限时,可在回调中获取。
+     *
+     * @param callback 权限回调
+     */
+    void setPermissionCallback(PermissionCallback callback);
+
+    /**
+     * 设置水平方向
+     *
+     * @param displayOrientation 参数值见 {@link CameraView.Orientation}
+     */
+    void setDisplayOrientation(@CameraView.Orientation int displayOrientation);
+
+    /**
+     * 获取到拍照权限时,调用些函数以继续。
+     */
+    void refreshPermission();
+
+    /**
+     * 设置闪光灯状态。
+     *
+     * @param flashMode {@link #FLASH_MODE_TORCH,#FLASH_MODE_OFF,#FLASH_MODE_AUTO}
+     */
+    void setFlashMode(@FlashMode int flashMode);
+
+    /**
+     * 获取当前闪光灯状态
+     *
+     * @return 当前闪光灯状态 参见 {@link #setFlashMode(int)}
+     */
+    @FlashMode
+    int getFlashMode();
+
+    void setCameraFacing(@CameraFacing int cameraFacing);
+
+    void setUsbCamera(boolean usbCamera);
+}

+ 8 - 0
app_modular/facesdk/src/main/java/com/baidu/aip/face/camera/PermissionCallback.java

@@ -0,0 +1,8 @@
+/*
+ * Copyright (C) 2017 Baidu, Inc. All Rights Reserved.
+ */
+package com.baidu.aip.face.camera;
+
+public interface PermissionCallback {
+    boolean onRequestPermission();
+}

BIN
app_modular/facesdk/src/main/jniLibs/arm64-v8a/libFaceSDK.so


BIN
app_modular/facesdk/src/main/jniLibs/arm64-v8a/libbaidu_license.so


BIN
app_modular/facesdk/src/main/jniLibs/armeabi-v7a/libFaceSDK.so


BIN
app_modular/facesdk/src/main/jniLibs/armeabi-v7a/libbaidu_license.so


BIN
app_modular/facesdk/src/main/jniLibs/x86/libFaceSDK.so


BIN
app_modular/facesdk/src/main/jniLibs/x86/libbaidu_license.so


+ 33 - 0
app_modular/facesdk/src/main/res/layout/activity_login_detected.xml

@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2017 Baidu, Inc. All Rights Reserved.
+  -->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/root_view"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_margin="@dimen/preview_margin">
+
+        <com.baidu.aip.face.TexturePreviewView
+            android:id="@+id/preview_view"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent" />
+    </RelativeLayout>
+
+
+    <FrameLayout
+        android:id="@+id/camera_layout"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="#ffffff" />
+
+    <com.baidu.aip.excep.widget.FaceRoundView
+        android:id="@+id/rect_view"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
+
+</RelativeLayout>

BIN
app_modular/facesdk/src/main/res/mipmap-xxhdpi/detect_close.png


BIN
app_modular/facesdk/src/main/res/mipmap-xxhdpi/icon_success.png


+ 11 - 0
app_modular/facesdk/src/main/res/values/dimens.xml

@@ -0,0 +1,11 @@
+<!--
+  ~ Copyright (C) 2017 Baidu, Inc. All Rights Reserved.
+  -->
+<resources>
+    <!-- Default screen margins, per the Android Design guidelines. -->
+    <dimen name="success_width">45dp</dimen>
+    <dimen name="detect_out">2dp</dimen>
+    <dimen name="preview_margin">50dp</dimen>
+    <dimen name="preview_land_margin">20dp</dimen>
+    <dimen name="head_width">200dp</dimen>
+</resources>

+ 75 - 0
app_modular/facesdk/src/main/res/values/strings.xml

@@ -0,0 +1,75 @@
+<!--
+  ~ Copyright (C) 2017 Baidu, Inc. All Rights Reserved.
+  -->
+<resources>
+
+
+
+    <string name="hint_move_into_frame">把脸移入框内</string>
+    <string name="hint_come_closer">再靠近一点儿</string>
+    <string name="hint_move_further">请离远一点儿</string>
+    <string name="hint_head_higher">头抬高些</string>
+    <string name="hint_head_down">头低一点</string>
+    <string name="hint_turn_left">向左转转</string>
+    <string name="hint_turn_right">向右转转</string>
+
+    <string name="hint_move_left">向左移动</string>
+    <string name="hint_move_up">向上移动</string>
+    <string name="hint_move_right">向右移动</string>
+    <string name="hint_move_down">向下移动</string>
+
+    <string name="hint_occlusion_in_left_eye">左眼有遮挡</string>
+    <string name="hint_occlusion_in_right_eye">右眼有遮挡</string>
+    <string name="hint_occlusion_in_nose">鼻子有遮挡</string>
+    <string name="hint_occlusion_in_mouth">嘴巴有遮挡</string>
+    <string name="hint_occlusion_in_left_cheek">左侧脸颊有遮挡</string>
+    <string name="hint_occlusion_in_right_cheek">右侧脸颊有遮挡</string>
+    <string name="hint_occlusion_in_chin">下颚有遮挡</string>
+    <string name="hint_occlusion_in_face">脸部有遮挡</string>
+
+    <string name="hint_light_low">光线再亮些</string>
+    <string name="hint_keep_still">请保持不动</string>
+    <string name="hint_timeout">检测超时</string>
+    <string name="hint_success">验证成功</string>
+    <string name="hint_very_good">非常好</string>
+
+    <string name="action_blink">眨眨眼</string>
+    <string name="action_blink_left_eye">请眨眨左边眼睛</string>
+    <string name="action_blink_right_eye">请眨眨右边眼睛</string>
+    <string name="action_open_mouth">张大嘴</string>
+    <string name="action_lean_head_left">向左摇头</string>
+    <string name="action_lean_head_right">向右摇头</string>
+    <string name="action_head_up">向上抬头</string>
+    <string name="action_head_down">向下低头</string>
+
+    <string name="detect_no_face">未检测到人脸</string>
+    <string name="detect_face_in">把脸移入框内</string>
+    <string name="detect_zoom_in">手机拿近一点</string>
+    <string name="detect_zoom_out">手机拿远一点</string>
+    <string name="detect_head_up">头抬高些</string>
+    <string name="detect_head_down">头放低些</string>
+    <string name="detect_head_left">向左调整下头部角度</string>
+    <string name="detect_head_right">向右调整下头部角度</string>
+    <string name="detect_occ_face">脸部有遮挡</string>
+    <string name="detect_occ_left_eye">左眼有遮挡</string>
+    <string name="detect_occ_right_eye">右眼有遮挡</string>
+    <string name="detect_occ_nose">鼻子有遮挡</string>
+    <string name="detect_occ_mouth">嘴部有遮挡</string>
+    <string name="detect_left_contour">左脸颊有遮挡</string>
+    <string name="detect_right_contour">右脸颊有遮挡</string>
+    <string name="detect_chin_contour">下巴有遮挡</string>
+    <string name="detect_low_light">光线再亮些</string>
+    <string name="detect_keep">请握稳手机,视线平视手机</string>
+    <string name="detect_standard">请正对手机</string>
+    <string name="detect_timeout">检测超时</string>
+    <string name="liveness_eye">眨眨眼</string>
+    <string name="liveness_eye_left">请眨眨左边眼睛</string>
+    <string name="liveness_eye_right">请眨眨右边眼睛</string>
+    <string name="liveness_mouth">张张嘴</string>
+    <string name="liveness_head_left">向左缓慢转头</string>
+    <string name="liveness_head_right">向右缓慢转头</string>
+    <string name="liveness_head_left_right">摇摇头</string>
+    <string name="liveness_head_up">缓慢抬头</string>
+    <string name="liveness_head_down">缓慢低头</string>
+    <string name="liveness_good">非常好</string>
+</resources>